Merge main into massive-terminal-upgrade

Resolves merge conflicts:
- apps/server/src/routes/terminal/common.ts: Keep randomBytes import, use @automaker/utils for createLogger
- apps/ui/eslint.config.mjs: Use main's explicit globals list with XMLHttpRequest and MediaQueryListEvent additions
- apps/ui/src/components/views/terminal-view.tsx: Keep our terminal improvements (killAllSessions, beforeunload, better error handling)
- apps/ui/src/config/terminal-themes.ts: Keep our search highlight colors for all themes
- apps/ui/src/store/app-store.ts: Keep our terminal settings persistence improvements (merge function)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
SuperComboGamer
2025-12-21 20:27:44 -05:00
393 changed files with 32473 additions and 17974 deletions

View File

@@ -1,207 +1,186 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import path from "path";
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", () => {
describe('security.ts', () => {
beforeEach(() => {
vi.resetModules();
});
describe("initAllowedPaths", () => {
it("should parse comma-separated directories from environment", async () => {
process.env.ALLOWED_PROJECT_DIRS = "/path1,/path2,/path3";
process.env.DATA_DIR = "";
describe('initAllowedPaths', () => {
it('should load ALLOWED_ROOT_DIRECTORY if set', async () => {
process.env.ALLOWED_ROOT_DIRECTORY = '/projects';
delete process.env.DATA_DIR;
const { initAllowedPaths, getAllowedPaths } = await import(
"@/lib/security.js"
);
const { initAllowedPaths, getAllowedPaths } = await import('@automaker/platform');
initAllowedPaths();
const allowed = getAllowedPaths();
expect(allowed).toContain(path.resolve("/path1"));
expect(allowed).toContain(path.resolve("/path2"));
expect(allowed).toContain(path.resolve("/path3"));
expect(allowed).toContain(path.resolve('/projects'));
});
it("should trim whitespace from paths", async () => {
process.env.ALLOWED_PROJECT_DIRS = " /path1 , /path2 , /path3 ";
process.env.DATA_DIR = "";
it('should 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"
);
const { initAllowedPaths, getAllowedPaths } = await import('@automaker/platform');
initAllowedPaths();
const allowed = getAllowedPaths();
expect(allowed).toContain(path.resolve("/path1"));
expect(allowed).toContain(path.resolve("/path2"));
expect(allowed).toContain(path.resolve('/data/dir'));
});
it("should always include DATA_DIR if set", async () => {
process.env.ALLOWED_PROJECT_DIRS = "";
process.env.DATA_DIR = "/data/dir";
it('should include both ALLOWED_ROOT_DIRECTORY and DATA_DIR if both set', async () => {
process.env.ALLOWED_ROOT_DIRECTORY = '/projects';
process.env.DATA_DIR = '/data';
const { initAllowedPaths, getAllowedPaths } = await import(
"@/lib/security.js"
);
const { initAllowedPaths, getAllowedPaths } = await import('@automaker/platform');
initAllowedPaths();
const allowed = getAllowedPaths();
expect(allowed).toContain(path.resolve("/data/dir"));
expect(allowed).toContain(path.resolve('/projects'));
expect(allowed).toContain(path.resolve('/data'));
expect(allowed).toHaveLength(2);
});
it("should handle empty ALLOWED_PROJECT_DIRS", async () => {
process.env.ALLOWED_PROJECT_DIRS = "";
process.env.DATA_DIR = "/data";
it('should return empty array when no paths configured', async () => {
delete process.env.ALLOWED_ROOT_DIRECTORY;
delete process.env.DATA_DIR;
const { initAllowedPaths, getAllowedPaths } = await import(
"@/lib/security.js"
);
const { initAllowedPaths, getAllowedPaths } = await import('@automaker/platform');
initAllowedPaths();
const allowed = getAllowedPaths();
expect(allowed).toHaveLength(1);
expect(allowed[0]).toBe(path.resolve("/data"));
});
it("should skip empty entries in comma list", async () => {
process.env.ALLOWED_PROJECT_DIRS = "/path1,,/path2, ,/path3";
process.env.DATA_DIR = "";
const { initAllowedPaths, getAllowedPaths } = await import(
"@/lib/security.js"
);
initAllowedPaths();
const allowed = getAllowedPaths();
expect(allowed).toHaveLength(3);
expect(allowed).toHaveLength(0);
});
});
describe("addAllowedPath", () => {
it("should add path to allowed list", async () => {
process.env.ALLOWED_PROJECT_DIRS = "";
process.env.DATA_DIR = "";
describe('isPathAllowed', () => {
it('should allow paths within ALLOWED_ROOT_DIRECTORY', async () => {
process.env.ALLOWED_ROOT_DIRECTORY = '/allowed/project';
process.env.DATA_DIR = '';
const { initAllowedPaths, addAllowedPath, getAllowedPaths } =
await import("@/lib/security.js");
const { initAllowedPaths, isPathAllowed } = await import('@automaker/platform');
initAllowedPaths();
addAllowedPath("/new/path");
// Paths within allowed directory should be allowed
expect(isPathAllowed('/allowed/project/file.txt')).toBe(true);
expect(isPathAllowed('/allowed/project/subdir/file.txt')).toBe(true);
const allowed = getAllowedPaths();
expect(allowed).toContain(path.resolve("/new/path"));
// 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 resolve relative paths before adding", async () => {
process.env.ALLOWED_PROJECT_DIRS = "";
process.env.DATA_DIR = "";
it('should allow all paths when no restrictions are configured', async () => {
delete process.env.DATA_DIR;
delete process.env.ALLOWED_ROOT_DIRECTORY;
const { initAllowedPaths, addAllowedPath, getAllowedPaths } =
await import("@/lib/security.js");
const { initAllowedPaths, isPathAllowed } = await import('@automaker/platform');
initAllowedPaths();
addAllowedPath("./relative/path");
// 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);
});
const allowed = getAllowedPaths();
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('@automaker/platform');
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('@automaker/platform');
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('@automaker/platform');
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('@automaker/platform');
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();
expect(allowed).toContain(path.resolve(cwd, "./relative/path"));
process.env.ALLOWED_ROOT_DIRECTORY = cwd;
process.env.DATA_DIR = '';
const { initAllowedPaths, validatePath } = await import('@automaker/platform');
initAllowedPaths();
const result = validatePath('./file.txt');
expect(result).toBe(path.resolve(cwd, './file.txt'));
});
});
describe("isPathAllowed", () => {
it("should allow all paths (permissions disabled)", async () => {
process.env.ALLOWED_PROJECT_DIRS = "/allowed/project";
process.env.DATA_DIR = "";
describe('getAllowedPaths', () => {
it('should return array of allowed paths', async () => {
process.env.ALLOWED_ROOT_DIRECTORY = '/projects';
process.env.DATA_DIR = '/data';
const { initAllowedPaths, isPathAllowed } = await import(
"@/lib/security.js"
);
initAllowedPaths();
// All paths are now allowed regardless of configuration
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 any path (permissions disabled)", async () => {
process.env.ALLOWED_PROJECT_DIRS = "/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 not throw error for any path (permissions disabled)", async () => {
process.env.ALLOWED_PROJECT_DIRS = "/allowed";
process.env.DATA_DIR = "";
const { initAllowedPaths, validatePath } = await import(
"@/lib/security.js"
);
initAllowedPaths();
// All paths are now allowed, no errors thrown
expect(() => validatePath("/disallowed/file.txt")).not.toThrow();
expect(validatePath("/disallowed/file.txt")).toBe(
path.resolve("/disallowed/file.txt")
);
});
it("should resolve relative paths", async () => {
const cwd = process.cwd();
process.env.ALLOWED_PROJECT_DIRS = 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_PROJECT_DIRS = "/path1,/path2";
process.env.DATA_DIR = "/data";
const { initAllowedPaths, getAllowedPaths } = await import(
"@/lib/security.js"
);
const { initAllowedPaths, getAllowedPaths } = await import('@automaker/platform');
initAllowedPaths();
const result = getAllowedPaths();
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
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_PROJECT_DIRS = "/test";
process.env.DATA_DIR = "";
it('should return resolved paths', async () => {
process.env.ALLOWED_ROOT_DIRECTORY = '/test';
process.env.DATA_DIR = '';
const { initAllowedPaths, getAllowedPaths } = await import(
"@/lib/security.js"
);
const { initAllowedPaths, getAllowedPaths } = await import('@automaker/platform');
initAllowedPaths();
const result = getAllowedPaths();
expect(result[0]).toBe(path.resolve("/test"));
expect(result[0]).toBe(path.resolve('/test'));
});
});
});