/** * 子进程环境净化单源测试(v0.7.3 P3-2) * * buildSafeChildEnv 是 run_command 与 MCP stdio 启动的唯一净化实现: * E1 敏感后缀剔除;E2 敏感精确名单剔除;E3 非敏感变量保留(黑名单不误伤); * E4 runtime 注入覆盖;E5 空值剔除。 */ import { describe, it, expect } from 'vitest'; import { buildSafeChildEnv } from '../safe-env'; const SAMPLE_ENV: Record = { PATH: '/usr/bin', LANG: 'zh_CN.UTF-8', DEEPSEEK_API_KEY: 'sk-secret', MY_SERVICE_TOKEN: 't', APP_PASSWORD: 'p', GITEA_PASSWORD: 'g', GITHUB_TOKEN: 'gh', HOME: '/home/u', }; describe('buildSafeChildEnv', () => { it('E1: 敏感后缀变量全部剔除(_API_KEY/_TOKEN/_PASSWORD/_SECRET/...)', () => { const env = buildSafeChildEnv({ source: SAMPLE_ENV }); expect(env['DEEPSEEK_API_KEY']).toBeUndefined(); expect(env['MY_SERVICE_TOKEN']).toBeUndefined(); expect(env['APP_PASSWORD']).toBeUndefined(); expect(env['GITHUB_TOKEN']).toBeUndefined(); }); it('E2: 精确名单剔除(大小写不敏感后缀 + 精确 key)', () => { const env = buildSafeChildEnv({ source: { GITEA_PASSWORD: 'g', DATABASE_PASSWORD: 'd' } }); expect(env['GITEA_PASSWORD']).toBeUndefined(); expect(env['DATABASE_PASSWORD']).toBeUndefined(); }); it('E3: 非敏感变量保留(黑名单不误伤 GIT_*/代理/路径类变量)', () => { const env = buildSafeChildEnv({ source: { ...SAMPLE_ENV, GIT_AUTHOR_NAME: 'metona', HTTPS_PROXY: 'http://p:7890' }, }); expect(env['PATH']).toBe('/usr/bin'); expect(env['LANG']).toBe('zh_CN.UTF-8'); expect(env['HOME']).toBe('/home/u'); expect(env['GIT_AUTHOR_NAME']).toBe('metona'); expect(env['HTTPS_PROXY']).toBe('http://p:7890'); }); it('E4: runtime 注入覆盖净化结果(调用方运行时差异显式可控)', () => { const env = buildSafeChildEnv({ source: SAMPLE_ENV, runtime: { NODE_ENV: 'production', LANG: 'C.utf8' }, }); expect(env['NODE_ENV']).toBe('production'); // runtime 覆盖同名的净化保留值 expect(env['LANG']).toBe('C.utf8'); }); it('E5: 空值/undefined 变量剔除', () => { const env = buildSafeChildEnv({ source: { EMPTY: '', UNSET: undefined, KEEP: 'x' } }); expect('EMPTY' in env).toBe(false); expect('UNSET' in env).toBe(false); expect(env['KEEP']).toBe('x'); }); it('E6: 无 source 时回退 process.env(运行路径契约;至少保留 PATH 类基本变量)', () => { const env = buildSafeChildEnv({ runtime: { NODE_ENV: 'production' } }); expect(env['NODE_ENV']).toBe('production'); }); it('E7: 精确名单包含的 key 即使值非空也剔除', () => { const env = buildSafeChildEnv({ source: { DEEPSEEK_API_KEY: 'sk-d', AGNES_API_KEY: 'sk-a', MIMO_API_KEY: 'sk-m' }, }); expect(env['DEEPSEEK_API_KEY']).toBeUndefined(); expect(env['AGNES_API_KEY']).toBeUndefined(); expect(env['MIMO_API_KEY']).toBeUndefined(); }); it('E8: 后缀匹配大小写不敏感(小写后缀同样剔除)', () => { const env = buildSafeChildEnv({ source: { my_api_key: 'k1', service_token: 't', db_password: 'p', api_secret: 's', }, }); expect(env['my_api_key']).toBeUndefined(); expect(env['service_token']).toBeUndefined(); expect(env['db_password']).toBeUndefined(); expect(env['api_secret']).toBeUndefined(); }); it('E9: 后缀匹配要求 key 以"_后缀"结尾(非后缀含子串不误伤)', () => { const env = buildSafeChildEnv({ source: { API_KEYSTORE_PATH: '/tmp/keys', // 含 KEYSTORE 但非 _API_KEY 结尾 → 保留 TOKENIZER_MODEL: 'bert', // 含 TOKEN 但非 _TOKEN 结尾 → 保留 SECRETARY_NAME: 'x', // 含 SECRET 但非 _SECRET 结尾 → 保留 }, }); expect(env['API_KEYSTORE_PATH']).toBe('/tmp/keys'); expect(env['TOKENIZER_MODEL']).toBe('bert'); expect(env['SECRETARY_NAME']).toBe('x'); }); it('E10: _PRIVATE_KEY / _CREDENTIALS 等长后缀精确匹配', () => { const env = buildSafeChildEnv({ source: { SSH_PRIVATE_KEY: 'pk', AWS_CREDENTIALS: 'creds', DB_CREDENTIAL: 'cred', GITHUB_PASSWORD: 'pw', }, }); expect(env['SSH_PRIVATE_KEY']).toBeUndefined(); expect(env['AWS_CREDENTIALS']).toBeUndefined(); expect(env['DB_CREDENTIAL']).toBeUndefined(); expect(env['GITHUB_PASSWORD']).toBeUndefined(); }); it('E11: runtime 注入的敏感变量绕过净化(调用方显式控制,覆盖黑名单)', () => { const env = buildSafeChildEnv({ source: { API_KEY_EXTERNAL: 'secret' }, runtime: { API_KEY_EXTERNAL: 'override' }, }); expect(env['API_KEY_EXTERNAL']).toBe('override'); }); it('E12: 返回对象是普通对象且不含原型链自有属性泄漏', () => { const env = buildSafeChildEnv({ source: { HAS_OWN_PROP: 'x' } }); expect(Object.getPrototypeOf(env)).toBe(Object.prototype); // toString 等原型方法不是 env 的自有属性 expect(Object.prototype.hasOwnProperty.call(env, 'toString')).toBe(false); expect(Object.prototype.hasOwnProperty.call(env, 'constructor')).toBe(false); }); it('E13: runtime 注入后源中同名敏感变量被覆盖且不再触发剔除', () => { const env = buildSafeChildEnv({ source: { DEEPSEEK_API_KEY: 'sk-raw' }, runtime: { DEEPSEEK_API_KEY: 'sk-safe-runtime' }, }); expect(env['DEEPSEEK_API_KEY']).toBe('sk-safe-runtime'); }); it('E14: 大量变量混合场景整体净化结果正确(端到端快照)', () => { const env = buildSafeChildEnv({ source: { PATH: '/bin', HOME: '/home/u', OPENAI_API_KEY: 'sk-openai', ANTHROPIC_AUTH_TOKEN: 'tok', GITEA_PASSWORD: 'g', HTTPS_PROXY: 'http://p:1', LANG: 'en', }, }); expect(env['PATH']).toBe('/bin'); expect(env['HOME']).toBe('/home/u'); expect(env['HTTPS_PROXY']).toBe('http://p:1'); expect(env['LANG']).toBe('en'); expect(env['OPENAI_API_KEY']).toBeUndefined(); expect(env['ANTHROPIC_AUTH_TOKEN']).toBeUndefined(); expect(env['GITEA_PASSWORD']).toBeUndefined(); }); });