Finding 1 [CRITICAL] — sessionRunner leaks full process.env to child
Extract buildChildEnv() with an explicit allowlist of safe OS/runtime vars.
Child process no longer inherits ANTHROPIC_API_KEY, OPENAI_API_KEY, DB
credentials, or any other secret present in the parent shell environment.
Only CLAUDE_CODE_* bridge vars, PATH, HOME, and standard OS env are passed.
Finding 2 [HIGH] — USER_TYPE=ant activatable by external users
Add isAntEmployee() -> false constant in src/utils/buildConfig.ts.
Replace all three direct process.env.USER_TYPE === 'ant' checks in
setup.ts and onChangeAppState.ts so no external user can activate
Anthropic-internal code paths (commit attribution, system prompt clearing,
dangerously-skip-permissions bypass) by setting USER_TYPE in their shell.
Finding 3 [HIGH] — memoryScan.ts unlimited directory walk
Add MAX_DEPTH=3 guard on readdir({ recursive: true }) results.
Deep or symlink-looped memory directories no longer cause an unbounded
blocking walk before the MAX_MEMORY_FILES cap takes effect.
Finding 5 [HIGH] — buildSdkUrl uses string.includes for protocol detection
Replace apiBaseUrl.includes('localhost') with new URL(apiBaseUrl).hostname
comparison so a remote URL containing 'localhost' in its path no longer
incorrectly gets ws:// (unencrypted) instead of wss://.
Finding 6 [HIGH] — upstream proxy writes unvalidated CA cert to disk
Add isValidPemContent() validation before writeFile in the CA cert download
path. A compromised proxy sending non-PEM data (HTML, JSON, scripts) is now
rejected before it can be appended to the system CA bundle.
Each fix is covered by new unit tests (25 tests across 5 new test files).
All 52 tests pass. Build verified clean on v0.1.7.
Fixes #42
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
86 lines
2.7 KiB
TypeScript
86 lines
2.7 KiB
TypeScript
import { expect, test } from 'bun:test'
|
|
import { buildChildEnv } from './sessionRunner.ts'
|
|
|
|
// Finding #42-1: sessionRunner spreads the full parent process.env into the
|
|
// child process environment, leaking API keys, DB credentials, proxy secrets.
|
|
// Only CLAUDE_CODE_OAUTH_TOKEN was stripped. Fix: explicit allowlist.
|
|
|
|
const baseOpts = {
|
|
accessToken: 'test-access-token',
|
|
useCcrV2: false as const,
|
|
}
|
|
|
|
test('buildChildEnv does not leak ANTHROPIC_API_KEY to child', () => {
|
|
const parentEnv = {
|
|
PATH: '/usr/bin',
|
|
HOME: '/home/user',
|
|
ANTHROPIC_API_KEY: 'sk-ant-secret-key',
|
|
CLAUDE_CODE_SESSION_ACCESS_TOKEN: 'will-be-overwritten',
|
|
}
|
|
const env = buildChildEnv(parentEnv, baseOpts)
|
|
expect(env.ANTHROPIC_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('buildChildEnv does not leak OPENAI_API_KEY to child', () => {
|
|
const parentEnv = {
|
|
PATH: '/usr/bin',
|
|
HOME: '/home/user',
|
|
OPENAI_API_KEY: 'sk-openai-secret',
|
|
}
|
|
const env = buildChildEnv(parentEnv, baseOpts)
|
|
expect(env.OPENAI_API_KEY).toBeUndefined()
|
|
})
|
|
|
|
test('buildChildEnv does not leak arbitrary secrets to child', () => {
|
|
const parentEnv = {
|
|
PATH: '/usr/bin',
|
|
HOME: '/home/user',
|
|
DB_PASSWORD: 'super-secret',
|
|
AWS_SECRET_ACCESS_KEY: 'aws-secret',
|
|
GITHUB_TOKEN: 'ghp_token',
|
|
}
|
|
const env = buildChildEnv(parentEnv, baseOpts)
|
|
expect(env.DB_PASSWORD).toBeUndefined()
|
|
expect(env.AWS_SECRET_ACCESS_KEY).toBeUndefined()
|
|
expect(env.GITHUB_TOKEN).toBeUndefined()
|
|
})
|
|
|
|
test('buildChildEnv includes PATH and HOME from parent', () => {
|
|
const parentEnv = {
|
|
PATH: '/usr/bin:/usr/local/bin',
|
|
HOME: '/home/user',
|
|
ANTHROPIC_API_KEY: 'sk-secret',
|
|
}
|
|
const env = buildChildEnv(parentEnv, baseOpts)
|
|
expect(env.PATH).toBe('/usr/bin:/usr/local/bin')
|
|
expect(env.HOME).toBe('/home/user')
|
|
})
|
|
|
|
test('buildChildEnv sets CLAUDE_CODE_SESSION_ACCESS_TOKEN from opts', () => {
|
|
const env = buildChildEnv({ PATH: '/usr/bin' }, { ...baseOpts, accessToken: 'my-token' })
|
|
expect(env.CLAUDE_CODE_SESSION_ACCESS_TOKEN).toBe('my-token')
|
|
})
|
|
|
|
test('buildChildEnv sets CLAUDE_CODE_ENVIRONMENT_KIND to bridge', () => {
|
|
const env = buildChildEnv({ PATH: '/usr/bin' }, baseOpts)
|
|
expect(env.CLAUDE_CODE_ENVIRONMENT_KIND).toBe('bridge')
|
|
})
|
|
|
|
test('buildChildEnv does not pass CLAUDE_CODE_OAUTH_TOKEN to child', () => {
|
|
const parentEnv = {
|
|
PATH: '/usr/bin',
|
|
CLAUDE_CODE_OAUTH_TOKEN: 'oauth-token-to-strip',
|
|
}
|
|
const env = buildChildEnv(parentEnv, baseOpts)
|
|
expect(env.CLAUDE_CODE_OAUTH_TOKEN).toBeUndefined()
|
|
})
|
|
|
|
test('buildChildEnv sets CCR v2 vars when useCcrV2 is true', () => {
|
|
const env = buildChildEnv(
|
|
{ PATH: '/usr/bin' },
|
|
{ accessToken: 'tok', useCcrV2: true, workerEpoch: 42 },
|
|
)
|
|
expect(env.CLAUDE_CODE_USE_CCR_V2).toBe('1')
|
|
expect(env.CLAUDE_CODE_WORKER_EPOCH).toBe('42')
|
|
})
|