diff --git a/src/docker-manager.test.ts b/src/docker-manager.test.ts index a9f6b2b7..9f2be636 100644 --- a/src/docker-manager.test.ts +++ b/src/docker-manager.test.ts @@ -1568,6 +1568,7 @@ describe('docker-manager', () => { const agent = result.services.agent; const env = agent.environment as Record; expect(env.ANTHROPIC_BASE_URL).toBe('http://172.30.0.30:10001'); + expect(env.CLAUDE_CODE_API_KEY_HELPER).toBe('/usr/local/bin/get-claude-key.sh'); }); it('should set both BASE_URL variables when both keys are provided', () => { @@ -1577,6 +1578,7 @@ describe('docker-manager', () => { const env = agent.environment as Record; expect(env.OPENAI_BASE_URL).toBe('http://172.30.0.30:10000'); expect(env.ANTHROPIC_BASE_URL).toBe('http://172.30.0.30:10001'); + expect(env.CLAUDE_CODE_API_KEY_HELPER).toBe('/usr/local/bin/get-claude-key.sh'); }); it('should not set OPENAI_BASE_URL in agent when only Anthropic key is provided', () => { @@ -1586,6 +1588,7 @@ describe('docker-manager', () => { const env = agent.environment as Record; expect(env.OPENAI_BASE_URL).toBeUndefined(); expect(env.ANTHROPIC_BASE_URL).toBe('http://172.30.0.30:10001'); + expect(env.CLAUDE_CODE_API_KEY_HELPER).toBe('/usr/local/bin/get-claude-key.sh'); }); it('should not set ANTHROPIC_BASE_URL in agent when only OpenAI key is provided', () => { @@ -1614,6 +1617,22 @@ describe('docker-manager', () => { expect(env.no_proxy).toContain('172.30.0.30'); }); + it('should set CLAUDE_CODE_API_KEY_HELPER when Anthropic key is provided', () => { + const configWithProxy = { ...mockConfig, enableApiProxy: true, anthropicApiKey: 'sk-ant-test-key' }; + const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy); + const agent = result.services.agent; + const env = agent.environment as Record; + expect(env.CLAUDE_CODE_API_KEY_HELPER).toBe('/usr/local/bin/get-claude-key.sh'); + }); + + it('should not set CLAUDE_CODE_API_KEY_HELPER when only OpenAI key is provided', () => { + const configWithProxy = { ...mockConfig, enableApiProxy: true, openaiApiKey: 'sk-test-key' }; + const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy); + const agent = result.services.agent; + const env = agent.environment as Record; + expect(env.CLAUDE_CODE_API_KEY_HELPER).toBeUndefined(); + }); + it('should not leak ANTHROPIC_API_KEY to agent when api-proxy is enabled', () => { // Simulate the key being in process.env (as it would be in real usage) const origKey = process.env.ANTHROPIC_API_KEY; diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 24c164fe..6494a817 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -997,6 +997,11 @@ export function generateDockerCompose( if (config.anthropicApiKey) { environment.ANTHROPIC_BASE_URL = `http://${networkConfig.proxyIp}:10001`; logger.debug(`Anthropic API will be proxied through sidecar at http://${networkConfig.proxyIp}:10001`); + + // Set API key helper for Claude Code CLI to use credential isolation + // The helper script returns a placeholder key; real authentication happens via ANTHROPIC_BASE_URL + environment.CLAUDE_CODE_API_KEY_HELPER = '/usr/local/bin/get-claude-key.sh'; + logger.debug('Claude Code API key helper configured: /usr/local/bin/get-claude-key.sh'); } logger.info('API proxy sidecar enabled - API keys will be held securely in sidecar container'); diff --git a/src/types.ts b/src/types.ts index bf73cbbc..1b9782c4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -398,6 +398,7 @@ export interface WrapperConfig { * variables are set in the agent container: * - OPENAI_BASE_URL=http://api-proxy:10000 (set when OPENAI_API_KEY is provided) * - ANTHROPIC_BASE_URL=http://api-proxy:10001 (set when ANTHROPIC_API_KEY is provided) + * - CLAUDE_CODE_API_KEY_HELPER=/usr/local/bin/get-claude-key.sh (set when ANTHROPIC_API_KEY is provided) * * API keys are passed via environment variables: * - OPENAI_API_KEY - Optional OpenAI API key for Codex