import { describe, it, expect } from 'vitest'; import { resolveWritePath, isSecretPath, WriteGuardError } from '../write_guard.js'; const PROJECT_ROOT = '/opt/projects/my-app'; describe('resolveWritePath', () => { it('resolves a relative path correctly', () => { const result = resolveWritePath(PROJECT_ROOT, 'src/index.ts'); expect(result).toBe('/opt/projects/my-app/src/index.ts'); }); it('resolves nested relative path', () => { const result = resolveWritePath(PROJECT_ROOT, 'src/lib/utils.ts'); expect(result).toBe('/opt/projects/my-app/src/lib/utils.ts'); }); it('throws on ../ escape', () => { expect(() => resolveWritePath(PROJECT_ROOT, '../../../etc/passwd')).toThrow(WriteGuardError); expect(() => resolveWritePath(PROJECT_ROOT, '../../../etc/passwd')).toThrow('path escapes project root'); }); it('throws on absolute path outside project root', () => { expect(() => resolveWritePath(PROJECT_ROOT, '/etc/shadow')).toThrow(WriteGuardError); expect(() => resolveWritePath(PROJECT_ROOT, '/tmp/exploit')).toThrow('path escapes project root'); }); it('allows absolute path inside project root', () => { const result = resolveWritePath(PROJECT_ROOT, '/opt/projects/my-app/src/new.ts'); expect(result).toBe('/opt/projects/my-app/src/new.ts'); }); it('denies .env files', () => { expect(() => resolveWritePath(PROJECT_ROOT, '.env')).toThrow(WriteGuardError); expect(() => resolveWritePath(PROJECT_ROOT, '.env')).toThrow('cannot write to secret file'); }); it('denies .env.local', () => { expect(() => resolveWritePath(PROJECT_ROOT, '.env.local')).toThrow(WriteGuardError); }); it('denies .env.production', () => { expect(() => resolveWritePath(PROJECT_ROOT, '.env.production')).toThrow(WriteGuardError); }); it('denies *.pem files', () => { expect(() => resolveWritePath(PROJECT_ROOT, 'certs/server.pem')).toThrow(WriteGuardError); expect(() => resolveWritePath(PROJECT_ROOT, 'certs/server.pem')).toThrow('cannot write to secret file'); }); it('denies *.key files', () => { expect(() => resolveWritePath(PROJECT_ROOT, 'ssl/private.key')).toThrow(WriteGuardError); }); it('denies id_rsa', () => { expect(() => resolveWritePath(PROJECT_ROOT, '.ssh/id_rsa')).toThrow(WriteGuardError); }); it('denies id_ed25519', () => { expect(() => resolveWritePath(PROJECT_ROOT, '.ssh/id_ed25519')).toThrow(WriteGuardError); }); it('denies credentials.json', () => { expect(() => resolveWritePath(PROJECT_ROOT, 'credentials.json')).toThrow(WriteGuardError); }); it('passes a normal file inside project', () => { const result = resolveWritePath(PROJECT_ROOT, 'src/components/Button.tsx'); expect(result).toBe('/opt/projects/my-app/src/components/Button.tsx'); }); it('passes a non-existent nested file (no realpath)', () => { // This is the key difference from BooChat's pathGuard: no realpath means // files that don't exist yet still pass validation const result = resolveWritePath(PROJECT_ROOT, 'src/new-dir/new-file.ts'); expect(result).toBe('/opt/projects/my-app/src/new-dir/new-file.ts'); }); it('throws on null/empty path', () => { expect(() => resolveWritePath(PROJECT_ROOT, '')).toThrow(WriteGuardError); expect(() => resolveWritePath(PROJECT_ROOT, '')).toThrow('file path is required'); }); it('normalizes ../ within project root and still allows', () => { const result = resolveWritePath(PROJECT_ROOT, 'src/../lib/utils.ts'); expect(result).toBe('/opt/projects/my-app/lib/utils.ts'); }); it('rejects path that looks inside root but normalizes outside', () => { expect(() => resolveWritePath(PROJECT_ROOT, 'src/../../other-project/hack.ts')).toThrow(WriteGuardError); }); }); describe('isSecretPath', () => { it('detects .env', () => { expect(isSecretPath('.env')).toBe(true); }); it('detects nested .env', () => { expect(isSecretPath('config/.env')).toBe(true); }); it('detects *.pfx', () => { expect(isSecretPath('certs/client.pfx')).toBe(true); }); it('does not flag normal source files', () => { expect(isSecretPath('src/index.ts')).toBe(false); expect(isSecretPath('README.md')).toBe(false); expect(isSecretPath('package.json')).toBe(false); }); it('returns false for empty string', () => { expect(isSecretPath('')).toBe(false); }); });