11import { generateDockerCompose , WrapperConfig , baseConfig , mockNetworkConfig , useTempWorkDir } from './service-test-setup.test-utils' ;
22import { mockNetworkConfigWithProxy } from './api-proxy-service.test-utils' ;
3+ import { NetworkConfig } from './squid-service' ;
34
45// Create mock functions (must remain per-file — jest.mock() is hoisted before imports)
56
@@ -10,6 +11,28 @@ jest.mock('execa', () => require('../test-helpers/mock-execa.test-utils').execaM
1011let mockConfig : WrapperConfig ;
1112
1213describe ( 'API proxy sidecar: API key isolation' , ( ) => {
14+ const withEnvVar = ( name : keyof NodeJS . ProcessEnv , value : string , assertion : ( ) => void ) : void => {
15+ const original = process . env [ name ] ;
16+ process . env [ name ] = value ;
17+ try {
18+ assertion ( ) ;
19+ } finally {
20+ if ( original !== undefined ) {
21+ process . env [ name ] = original ;
22+ } else {
23+ delete process . env [ name ] ;
24+ }
25+ }
26+ } ;
27+
28+ const getAgentEnvironment = (
29+ config : WrapperConfig ,
30+ networkConfig : NetworkConfig = mockNetworkConfigWithProxy
31+ ) : NodeJS . ProcessEnv => {
32+ const result = generateDockerCompose ( config , networkConfig ) ;
33+ return result . services . agent . environment ?? { } ;
34+ } ;
35+
1336 useTempWorkDir (
1437 baseConfig ,
1538 ( config ) => {
@@ -20,165 +43,95 @@ describe('API proxy sidecar: API key isolation', () => {
2043
2144 it ( 'should not leak ANTHROPIC_API_KEY to agent when api-proxy is enabled' , ( ) => {
2245 // Simulate the key being in process.env (as it would be in real usage)
23- const origKey = process . env . ANTHROPIC_API_KEY ;
24- process . env . ANTHROPIC_API_KEY = 'sk-ant-secret-key' ;
25- try {
46+ withEnvVar ( 'ANTHROPIC_API_KEY' , 'sk-ant-secret-key' , ( ) => {
2647 const configWithProxy = { ...mockConfig , enableApiProxy : true , anthropicApiKey : 'sk-ant-secret-key' } ;
27- const result = generateDockerCompose ( configWithProxy , mockNetworkConfigWithProxy ) ;
28- const agent = result . services . agent ;
29- const env = agent . environment as Record < string , string > ;
48+ const env = getAgentEnvironment ( configWithProxy ) ;
3049 // Agent should NOT have the raw API key — only the sidecar gets it
3150 expect ( env . ANTHROPIC_API_KEY ) . toBeUndefined ( ) ;
3251 // Agent should have the BASE_URL to reach the sidecar instead
3352 expect ( env . ANTHROPIC_BASE_URL ) . toBe ( 'http://172.30.0.30:10001' ) ;
3453 // Agent should have placeholder token for Claude Code compatibility
3554 expect ( env . ANTHROPIC_AUTH_TOKEN ) . toBe ( 'sk-ant-placeholder-key-for-credential-isolation' ) ;
36- } finally {
37- if ( origKey !== undefined ) {
38- process . env . ANTHROPIC_API_KEY = origKey ;
39- } else {
40- delete process . env . ANTHROPIC_API_KEY ;
41- }
42- }
55+ } ) ;
4356 } ) ;
4457
4558 it ( 'should not leak OPENAI_API_KEY to agent when api-proxy is enabled' , ( ) => {
4659 // Simulate the key being in process.env (as it would be in real usage)
47- const origKey = process . env . OPENAI_API_KEY ;
48- process . env . OPENAI_API_KEY = 'sk-secret-key' ;
49- try {
60+ withEnvVar ( 'OPENAI_API_KEY' , 'sk-secret-key' , ( ) => {
5061 const configWithProxy = { ...mockConfig , enableApiProxy : true , openaiApiKey : 'sk-secret-key' } ;
51- const result = generateDockerCompose ( configWithProxy , mockNetworkConfigWithProxy ) ;
52- const agent = result . services . agent ;
53- const env = agent . environment as Record < string , string > ;
62+ const env = getAgentEnvironment ( configWithProxy ) ;
5463 // Agent should NOT have the real API key — only the sidecar holds it.
5564 // A placeholder is injected so Codex/OpenAI clients route through OPENAI_BASE_URL
5665 // (Codex v0.121+ bypasses OPENAI_BASE_URL when no key is present in the env).
5766 expect ( env . OPENAI_API_KEY ) . toBe ( 'sk-placeholder-for-api-proxy' ) ;
5867 expect ( env . OPENAI_API_KEY ) . not . toBe ( 'sk-secret-key' ) ;
5968 // Agent should have OPENAI_BASE_URL to proxy through sidecar
6069 expect ( env . OPENAI_BASE_URL ) . toBe ( 'http://172.30.0.30:10000' ) ;
61- } finally {
62- if ( origKey !== undefined ) {
63- process . env . OPENAI_API_KEY = origKey ;
64- } else {
65- delete process . env . OPENAI_API_KEY ;
66- }
67- }
70+ } ) ;
6871 } ) ;
6972
7073 it ( 'should not leak CODEX_API_KEY to agent when api-proxy is enabled with envAll' , ( ) => {
7174 // Simulate the key being in process.env AND envAll enabled.
7275 // The host's real CODEX_API_KEY must not reach the agent; a placeholder is
7376 // injected instead so Codex routes through OPENAI_BASE_URL (api-proxy).
74- const origKey = process . env . CODEX_API_KEY ;
75- process . env . CODEX_API_KEY = 'sk-codex-secret' ;
76- try {
77+ withEnvVar ( 'CODEX_API_KEY' , 'sk-codex-secret' , ( ) => {
7778 const configWithProxy = { ...mockConfig , enableApiProxy : true , openaiApiKey : 'sk-test' , envAll : true } ;
78- const result = generateDockerCompose ( configWithProxy , mockNetworkConfigWithProxy ) ;
79- const agent = result . services . agent ;
80- const env = agent . environment as Record < string , string > ;
79+ const env = getAgentEnvironment ( configWithProxy ) ;
8180 // CODEX_API_KEY placeholder is set; the real host key must not be present
8281 expect ( env . CODEX_API_KEY ) . toBe ( 'sk-placeholder-for-api-proxy' ) ;
8382 expect ( env . CODEX_API_KEY ) . not . toBe ( 'sk-codex-secret' ) ;
8483 // OPENAI_BASE_URL should be set when api-proxy is enabled with openaiApiKey
8584 expect ( env . OPENAI_BASE_URL ) . toBe ( 'http://172.30.0.30:10000' ) ;
86- } finally {
87- if ( origKey !== undefined ) {
88- process . env . CODEX_API_KEY = origKey ;
89- } else {
90- delete process . env . CODEX_API_KEY ;
91- }
92- }
85+ } ) ;
9386 } ) ;
9487
9588 it ( 'should not leak OPENAI_API_KEY to agent when api-proxy is enabled with envAll' , ( ) => {
9689 // Simulate envAll scenario (smoke-codex uses --env-all).
9790 // Even with envAll, the real key must not reach the agent; a placeholder is used instead.
98- const origKey = process . env . OPENAI_API_KEY ;
99- process . env . OPENAI_API_KEY = 'sk-openai-secret' ;
100- try {
91+ withEnvVar ( 'OPENAI_API_KEY' , 'sk-openai-secret' , ( ) => {
10192 const configWithProxy = { ...mockConfig , enableApiProxy : true , openaiApiKey : 'sk-openai-secret' , envAll : true } ;
102- const result = generateDockerCompose ( configWithProxy , mockNetworkConfigWithProxy ) ;
103- const agent = result . services . agent ;
104- const env = agent . environment as Record < string , string > ;
93+ const env = getAgentEnvironment ( configWithProxy ) ;
10594 // Placeholder is set; real key must not be passed to agent
10695 expect ( env . OPENAI_API_KEY ) . toBe ( 'sk-placeholder-for-api-proxy' ) ;
10796 expect ( env . OPENAI_API_KEY ) . not . toBe ( 'sk-openai-secret' ) ;
10897 // Agent should have OPENAI_BASE_URL to proxy through sidecar
10998 expect ( env . OPENAI_BASE_URL ) . toBe ( 'http://172.30.0.30:10000' ) ;
110- } finally {
111- if ( origKey !== undefined ) {
112- process . env . OPENAI_API_KEY = origKey ;
113- } else {
114- delete process . env . OPENAI_API_KEY ;
115- }
116- }
99+ } ) ;
117100 } ) ;
118101
119102 it ( 'should not leak ANTHROPIC_API_KEY to agent when api-proxy is enabled with envAll' , ( ) => {
120- const origKey = process . env . ANTHROPIC_API_KEY ;
121- process . env . ANTHROPIC_API_KEY = 'sk-ant-secret' ;
122- try {
103+ withEnvVar ( 'ANTHROPIC_API_KEY' , 'sk-ant-secret' , ( ) => {
123104 const configWithProxy = { ...mockConfig , enableApiProxy : true , anthropicApiKey : 'sk-ant-secret' , envAll : true } ;
124- const result = generateDockerCompose ( configWithProxy , mockNetworkConfigWithProxy ) ;
125- const agent = result . services . agent ;
126- const env = agent . environment as Record < string , string > ;
105+ const env = getAgentEnvironment ( configWithProxy ) ;
127106 // Even with envAll, agent should NOT have ANTHROPIC_API_KEY when api-proxy is enabled
128107 expect ( env . ANTHROPIC_API_KEY ) . toBeUndefined ( ) ;
129108 expect ( env . ANTHROPIC_BASE_URL ) . toBe ( 'http://172.30.0.30:10001' ) ;
130109 // But should have placeholder token for Claude Code compatibility
131110 expect ( env . ANTHROPIC_AUTH_TOKEN ) . toBe ( 'sk-ant-placeholder-key-for-credential-isolation' ) ;
132- } finally {
133- if ( origKey !== undefined ) {
134- process . env . ANTHROPIC_API_KEY = origKey ;
135- } else {
136- delete process . env . ANTHROPIC_API_KEY ;
137- }
138- }
111+ } ) ;
139112 } ) ;
140113
141114 it ( 'should pass GITHUB_API_URL to agent when api-proxy is enabled with envAll' , ( ) => {
142115 // GITHUB_API_URL must remain in the agent environment even when api-proxy is enabled.
143116 // The Copilot CLI needs it to locate the GitHub API (token exchange, user info, etc.).
144117 // Copilot-specific calls route through COPILOT_API_URL → api-proxy regardless.
145118 // See: github/gh-aw#20875
146- const origUrl = process . env . GITHUB_API_URL ;
147- process . env . GITHUB_API_URL = 'https://api.github.com' ;
148- try {
119+ withEnvVar ( 'GITHUB_API_URL' , 'https://api.github.com' , ( ) => {
149120 const configWithProxy = { ...mockConfig , enableApiProxy : true , copilotGithubToken : 'ghp_test_token' , envAll : true } ;
150- const result = generateDockerCompose ( configWithProxy , mockNetworkConfigWithProxy ) ;
151- const agent = result . services . agent ;
152- const env = agent . environment as Record < string , string > ;
121+ const env = getAgentEnvironment ( configWithProxy ) ;
153122 // GITHUB_API_URL should be passed to agent even when api-proxy is enabled
154123 expect ( env . GITHUB_API_URL ) . toBe ( 'https://api.github.com' ) ;
155124 // COPILOT_API_URL should also be set to route Copilot calls through the api-proxy
156125 expect ( env . COPILOT_API_URL ) . toBe ( 'http://172.30.0.30:10002' ) ;
157- } finally {
158- if ( origUrl !== undefined ) {
159- process . env . GITHUB_API_URL = origUrl ;
160- } else {
161- delete process . env . GITHUB_API_URL ;
162- }
163- }
126+ } ) ;
164127 } ) ;
165128
166129 it ( 'should pass GITHUB_API_URL to agent when api-proxy is NOT enabled with envAll' , ( ) => {
167- const origUrl = process . env . GITHUB_API_URL ;
168- process . env . GITHUB_API_URL = 'https://api.github.com' ;
169- try {
130+ withEnvVar ( 'GITHUB_API_URL' , 'https://api.github.com' , ( ) => {
170131 const configNoProxy = { ...mockConfig , enableApiProxy : false , envAll : true } ;
171- const result = generateDockerCompose ( configNoProxy , mockNetworkConfig ) ;
172- const agent = result . services . agent ;
173- const env = agent . environment as Record < string , string > ;
132+ const env = getAgentEnvironment ( configNoProxy , mockNetworkConfig ) ;
174133 // When api-proxy is NOT enabled, GITHUB_API_URL should be passed through
175134 expect ( env . GITHUB_API_URL ) . toBe ( 'https://api.github.com' ) ;
176- } finally {
177- if ( origUrl !== undefined ) {
178- process . env . GITHUB_API_URL = origUrl ;
179- } else {
180- delete process . env . GITHUB_API_URL ;
181- }
182- }
135+ } ) ;
183136 } ) ;
184137} ) ;
0 commit comments