Files
automaker/apps/server/tests/unit/lib/security.test.ts
Test User 9cf12b9006 refactor: enhance security and streamline file handling
This commit introduces several improvements to the security and file handling mechanisms across the application. Key changes include:

- Updated the Dockerfile to pin the GitHub CLI version for reproducible builds.
- Refactored the secure file system operations to ensure consistent path validation and type handling.
- Removed legacy path management functions and streamlined the allowed paths logic in the security module.
- Enhanced route handlers to validate path parameters against the ALLOWED_ROOT_DIRECTORY, improving security against unauthorized access.
- Updated the settings service to focus solely on the Anthropic API key, removing references to Google and OpenAI keys.

These changes aim to enhance security, maintainability, and clarity in the codebase.

Tests: All unit tests passing.
2025-12-20 22:08:28 -05:00

191 lines
6.6 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from "vitest";
import path from "path";
/**
* Note: security.ts maintains module-level state (allowed paths Set).
* We need to reset modules and reimport for each test to get fresh state.
*/
describe("security.ts", () => {
beforeEach(() => {
vi.resetModules();
});
describe("initAllowedPaths", () => {
it("should load ALLOWED_ROOT_DIRECTORY if set", async () => {
process.env.ALLOWED_ROOT_DIRECTORY = "/projects";
process.env.DATA_DIR = "";
const { initAllowedPaths, getAllowedPaths, getAllowedRootDirectory } =
await import("@/lib/security.js");
initAllowedPaths();
const allowed = getAllowedPaths();
expect(allowed).toContain(path.resolve("/projects"));
expect(getAllowedRootDirectory()).toBe(path.resolve("/projects"));
});
it("should always include DATA_DIR if set", async () => {
delete process.env.ALLOWED_ROOT_DIRECTORY;
process.env.DATA_DIR = "/data/dir";
const { initAllowedPaths, getAllowedPaths } =
await import("@/lib/security.js");
initAllowedPaths();
const allowed = getAllowedPaths();
expect(allowed).toContain(path.resolve("/data/dir"));
});
it("should handle both ALLOWED_ROOT_DIRECTORY and DATA_DIR", async () => {
process.env.ALLOWED_ROOT_DIRECTORY = "/projects";
process.env.DATA_DIR = "/data";
const { initAllowedPaths, getAllowedPaths } =
await import("@/lib/security.js");
initAllowedPaths();
const allowed = getAllowedPaths();
expect(allowed).toContain(path.resolve("/projects"));
expect(allowed).toContain(path.resolve("/data"));
expect(allowed).toHaveLength(2);
});
});
describe("isPathAllowed", () => {
it("should allow paths within ALLOWED_ROOT_DIRECTORY", async () => {
process.env.ALLOWED_ROOT_DIRECTORY = "/allowed/project";
process.env.DATA_DIR = "";
const { initAllowedPaths, isPathAllowed } =
await import("@/lib/security.js");
initAllowedPaths();
// Paths within allowed directory should be allowed
expect(isPathAllowed("/allowed/project/file.txt")).toBe(true);
expect(isPathAllowed("/allowed/project/subdir/file.txt")).toBe(true);
// Paths outside allowed directory should be denied
expect(isPathAllowed("/not/allowed/file.txt")).toBe(false);
expect(isPathAllowed("/tmp/file.txt")).toBe(false);
expect(isPathAllowed("/etc/passwd")).toBe(false);
});
it("should allow all paths when no restrictions are configured", async () => {
delete process.env.DATA_DIR;
delete process.env.ALLOWED_ROOT_DIRECTORY;
const { initAllowedPaths, isPathAllowed } =
await import("@/lib/security.js");
initAllowedPaths();
// All paths should be allowed when no restrictions are configured
expect(isPathAllowed("/allowed/project/file.txt")).toBe(true);
expect(isPathAllowed("/not/allowed/file.txt")).toBe(true);
expect(isPathAllowed("/tmp/file.txt")).toBe(true);
expect(isPathAllowed("/etc/passwd")).toBe(true);
expect(isPathAllowed("/any/path")).toBe(true);
});
it("should allow all paths when DATA_DIR is set but ALLOWED_ROOT_DIRECTORY is not", async () => {
process.env.DATA_DIR = "/data";
delete process.env.ALLOWED_ROOT_DIRECTORY;
const { initAllowedPaths, isPathAllowed } =
await import("@/lib/security.js");
initAllowedPaths();
// DATA_DIR should be allowed
expect(isPathAllowed("/data/settings.json")).toBe(true);
// But all other paths should also be allowed when ALLOWED_ROOT_DIRECTORY is not set
expect(isPathAllowed("/allowed/project/file.txt")).toBe(true);
expect(isPathAllowed("/not/allowed/file.txt")).toBe(true);
expect(isPathAllowed("/tmp/file.txt")).toBe(true);
expect(isPathAllowed("/etc/passwd")).toBe(true);
expect(isPathAllowed("/any/path")).toBe(true);
});
});
describe("validatePath", () => {
it("should return resolved path for allowed paths", async () => {
process.env.ALLOWED_ROOT_DIRECTORY = "/allowed";
process.env.DATA_DIR = "";
const { initAllowedPaths, validatePath } =
await import("@/lib/security.js");
initAllowedPaths();
const result = validatePath("/allowed/file.txt");
expect(result).toBe(path.resolve("/allowed/file.txt"));
});
it("should throw error for paths outside allowed directories", async () => {
process.env.ALLOWED_ROOT_DIRECTORY = "/allowed";
process.env.DATA_DIR = "";
const { initAllowedPaths, validatePath } =
await import("@/lib/security.js");
initAllowedPaths();
// Disallowed paths should throw PathNotAllowedError
expect(() => validatePath("/disallowed/file.txt")).toThrow();
});
it("should not throw error for any path when no restrictions are configured", async () => {
delete process.env.DATA_DIR;
delete process.env.ALLOWED_ROOT_DIRECTORY;
const { initAllowedPaths, validatePath } =
await import("@/lib/security.js");
initAllowedPaths();
// All paths are allowed when no restrictions configured
expect(() => validatePath("/disallowed/file.txt")).not.toThrow();
expect(validatePath("/disallowed/file.txt")).toBe(
path.resolve("/disallowed/file.txt")
);
});
it("should resolve relative paths within allowed directory", async () => {
const cwd = process.cwd();
process.env.ALLOWED_ROOT_DIRECTORY = cwd;
process.env.DATA_DIR = "";
const { initAllowedPaths, validatePath } =
await import("@/lib/security.js");
initAllowedPaths();
const result = validatePath("./file.txt");
expect(result).toBe(path.resolve(cwd, "./file.txt"));
});
});
describe("getAllowedPaths", () => {
it("should return array of allowed paths", async () => {
process.env.ALLOWED_ROOT_DIRECTORY = "/projects";
process.env.DATA_DIR = "/data";
const { initAllowedPaths, getAllowedPaths } =
await import("@/lib/security.js");
initAllowedPaths();
const result = getAllowedPaths();
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(2);
expect(result).toContain(path.resolve("/projects"));
expect(result).toContain(path.resolve("/data"));
});
it("should return resolved paths", async () => {
process.env.ALLOWED_ROOT_DIRECTORY = "/test";
process.env.DATA_DIR = "";
const { initAllowedPaths, getAllowedPaths } =
await import("@/lib/security.js");
initAllowedPaths();
const result = getAllowedPaths();
expect(result[0]).toBe(path.resolve("/test"));
});
});
});