import { describe, it, expect } from 'vitest'; import { extractCodexConfigEnvKeys, filterCodexEnv } from '../utils/codex-client'; import { SENSITIVE_LOG_PATTERN, ALLOWED_MEDIA_TYPES, resolveMediaExtension } from '../api/ai/chat'; // --------------------------------------------------------------------------- // 1. Codex client env allowlist // --------------------------------------------------------------------------- describe('codex client env allowlist', () => { it('should strip dangerous env vars', () => { const env = { PATH: '/usr/bin', HOME: '/home/user', AWS_SECRET_KEY: 'supersecret', DATABASE_URL: 'postgres://...', ANTHROPIC_API_KEY: 'sk-ant-xxx', GITHUB_TOKEN: 'ghp_xxx', }; const filtered = filterCodexEnv(env); expect(filtered).not.toHaveProperty('AWS_SECRET_KEY'); expect(filtered).not.toHaveProperty('DATABASE_URL'); expect(filtered).not.toHaveProperty('ANTHROPIC_API_KEY'); expect(filtered).not.toHaveProperty('GITHUB_TOKEN'); }); it('should keep PATH, HOME, and shell vars', () => { const env = { PATH: '/usr/bin', HOME: '/home/user', TERM: 'xterm-256color', LANG: 'en_US.UTF-8', SHELL: '/bin/zsh', TMPDIR: '/tmp', }; const filtered = filterCodexEnv(env); expect(filtered.PATH).toBe('/usr/bin'); expect(filtered.HOME).toBe('/home/user'); expect(filtered.TERM).toBe('xterm-256color'); expect(filtered.LANG).toBe('en_US.UTF-8'); expect(filtered.SHELL).toBe('/bin/zsh'); expect(filtered.TMPDIR).toBe('/tmp'); }); it('should keep OPENAI_* and CODEX_* vars', () => { const env = { PATH: '/usr/bin', HOME: '/home/user', OPENAI_API_KEY: 'sk-openai-xxx', OPENAI_ORG_ID: 'org-xxx', CODEX_TOKEN: 'codex-xxx', CODEX_SANDBOX: 'read-only', }; const filtered = filterCodexEnv(env); expect(filtered.OPENAI_API_KEY).toBe('sk-openai-xxx'); expect(filtered.OPENAI_ORG_ID).toBe('org-xxx'); expect(filtered.CODEX_TOKEN).toBe('codex-xxx'); expect(filtered.CODEX_SANDBOX).toBe('read-only'); }); it('should keep custom provider env keys declared in codex config', () => { const env = { PATH: '/usr/bin', HOME: '/home/user', agw_CODE: 'sk-agw-xxx', PACKYCODE: 'sk-packy-xxx', OTHER_SECRET: 'bad', }; const extraAllowed = extractCodexConfigEnvKeys(` [model_providers.agw] env_key = "agw_CODE" [model_providers.packycode] env_key = "PACKYCODE" `); const filtered = filterCodexEnv(env, extraAllowed); expect(filtered.agw_CODE).toBe('sk-agw-xxx'); expect(filtered.PACKYCODE).toBe('sk-packy-xxx'); expect(filtered).not.toHaveProperty('OTHER_SECRET'); }); it('should not leak vars with similar prefixes', () => { const env = { PATH: '/usr/bin', HOME: '/home/user', OPENAI_API_KEY: 'ok', OPENAI_COMPAT: 'ok', OPEN_SECRET: 'bad', CODEX_MODE: 'ok', CODE_SECRET: 'bad', }; const filtered = filterCodexEnv(env); expect(filtered).not.toHaveProperty('OPEN_SECRET'); expect(filtered).not.toHaveProperty('CODE_SECRET'); expect(filtered.OPENAI_API_KEY).toBe('ok'); expect(filtered.CODEX_MODE).toBe('ok'); }); }); // --------------------------------------------------------------------------- // 2. Debug tail sanitization // --------------------------------------------------------------------------- describe('debug tail sanitization', () => { it('should match ANTHROPIC_API_KEY leak', () => { expect(SENSITIVE_LOG_PATTERN.test('ANTHROPIC_API_KEY=sk-ant-abc123')).toBe(true); }); it('should match Authorization Bearer header', () => { expect(SENSITIVE_LOG_PATTERN.test('Authorization: Bearer token123')).toBe(true); expect(SENSITIVE_LOG_PATTERN.test('authorization: Bearer xyz')).toBe(true); }); it('should match api_key and api-key patterns', () => { expect(SENSITIVE_LOG_PATTERN.test('api_key=secret')).toBe(true); expect(SENSITIVE_LOG_PATTERN.test('api-key: secret')).toBe(true); expect(SENSITIVE_LOG_PATTERN.test('apikey=secret')).toBe(true); }); it('should NOT match normal log lines', () => { expect(SENSITIVE_LOG_PATTERN.test('Using API endpoint https://api.anthropic.com')).toBe(false); expect(SENSITIVE_LOG_PATTERN.test('Model: claude-sonnet-4-5-20250929')).toBe(false); expect(SENSITIVE_LOG_PATTERN.test('Request completed in 1200ms')).toBe(false); expect(SENSITIVE_LOG_PATTERN.test('Connecting to upstream server...')).toBe(false); }); }); // --------------------------------------------------------------------------- // 3. Media type allowlist // --------------------------------------------------------------------------- describe('media type allowlist', () => { it('should allow standard image types', () => { expect(ALLOWED_MEDIA_TYPES.has('image/png')).toBe(true); expect(ALLOWED_MEDIA_TYPES.has('image/jpeg')).toBe(true); expect(ALLOWED_MEDIA_TYPES.has('image/gif')).toBe(true); expect(ALLOWED_MEDIA_TYPES.has('image/webp')).toBe(true); }); it('should reject non-image types', () => { expect(ALLOWED_MEDIA_TYPES.has('image/svg+xml')).toBe(false); expect(ALLOWED_MEDIA_TYPES.has('application/pdf')).toBe(false); expect(ALLOWED_MEDIA_TYPES.has('text/html')).toBe(false); }); it('should resolve extensions correctly', () => { expect(resolveMediaExtension('image/png')).toBe('png'); expect(resolveMediaExtension('image/jpeg')).toBe('jpeg'); expect(resolveMediaExtension('image/gif')).toBe('gif'); expect(resolveMediaExtension('image/webp')).toBe('webp'); }); it('should fall back to png for disallowed types', () => { expect(resolveMediaExtension('image/x-sh')).toBe('png'); expect(resolveMediaExtension('image/svg+xml')).toBe('png'); expect(resolveMediaExtension('application/pdf')).toBe('png'); expect(resolveMediaExtension('text/html')).toBe('png'); expect(resolveMediaExtension('')).toBe('png'); }); });