Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions containers/agent/api-proxy-health-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,15 @@ if [ -n "$OPENAI_BASE_URL" ]; then
echo "[health-check] Checking OpenAI API proxy configuration..."

# Verify credentials are NOT in agent environment
# Note: CODEX_API_KEY check is temporarily disabled - Codex receives credentials directly
if [ -n "$OPENAI_API_KEY" ] || [ -n "$OPENAI_KEY" ]; then
echo "[health-check][ERROR] OpenAI API key found in agent environment!"
if [ -n "$OPENAI_API_KEY" ] || [ -n "$CODEX_API_KEY" ] || [ -n "$OPENAI_KEY" ]; then
echo "[health-check][ERROR] OpenAI/Codex API key found in agent environment!"
echo "[health-check][ERROR] Credential isolation failed - keys should only be in api-proxy container"
echo "[health-check][ERROR] OPENAI_API_KEY=${OPENAI_API_KEY:+<present>}"
# echo "[health-check][ERROR] CODEX_API_KEY=${CODEX_API_KEY:+<present>}" # Temporarily disabled - Codex uses direct credentials
echo "[health-check][ERROR] CODEX_API_KEY=${CODEX_API_KEY:+<present>}"
echo "[health-check][ERROR] OPENAI_KEY=${OPENAI_KEY:+<present>}"
exit 1
fi
echo "[health-check] ✓ OpenAI credentials NOT in agent environment (correct)"
# Note: CODEX_API_KEY is intentionally passed through for Codex agent compatibility
echo "[health-check] ✓ OpenAI/Codex credentials NOT in agent environment (correct)"

# Perform health check using BASE_URL
echo "[health-check] Testing connectivity to OpenAI API proxy at $OPENAI_BASE_URL..."
Expand Down
35 changes: 16 additions & 19 deletions src/docker-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1545,13 +1545,12 @@ describe('docker-manager', () => {
expect(dependsOn['api-proxy'].condition).toBe('service_healthy');
});

it('should not set OPENAI_BASE_URL in agent when OpenAI key is provided (temporarily disabled)', () => {
it('should set OPENAI_BASE_URL in agent when 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<string, string>;
// OPENAI_BASE_URL temporarily disabled for Codex - will be re-enabled in future
expect(env.OPENAI_BASE_URL).toBeUndefined();
expect(env.OPENAI_BASE_URL).toBe('http://172.30.0.30:10000/v1');
});

it('should configure HTTP_PROXY and HTTPS_PROXY in api-proxy to route through Squid', () => {
Expand All @@ -1573,13 +1572,12 @@ describe('docker-manager', () => {
expect(env.CLAUDE_CODE_API_KEY_HELPER).toBe('/usr/local/bin/get-claude-key.sh');
});

it('should only set ANTHROPIC_BASE_URL when both keys are provided (OPENAI_BASE_URL temporarily disabled)', () => {
it('should set both ANTHROPIC_BASE_URL and OPENAI_BASE_URL when both keys are provided', () => {
const configWithProxy = { ...mockConfig, enableApiProxy: true, openaiApiKey: 'sk-test-openai-key', anthropicApiKey: 'sk-ant-test-key' };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const agent = result.services.agent;
const env = agent.environment as Record<string, string>;
// OPENAI_BASE_URL temporarily disabled for Codex - will be re-enabled in future
expect(env.OPENAI_BASE_URL).toBeUndefined();
expect(env.OPENAI_BASE_URL).toBe('http://172.30.0.30:10000/v1');
expect(env.ANTHROPIC_BASE_URL).toBe('http://172.30.0.30:10001');
expect(env.ANTHROPIC_AUTH_TOKEN).toBe('placeholder-token-for-credential-isolation');
expect(env.CLAUDE_CODE_API_KEY_HELPER).toBe('/usr/local/bin/get-claude-key.sh');
Expand All @@ -1596,14 +1594,13 @@ describe('docker-manager', () => {
expect(env.CLAUDE_CODE_API_KEY_HELPER).toBe('/usr/local/bin/get-claude-key.sh');
});

it('should not set ANTHROPIC_BASE_URL or OPENAI_BASE_URL in agent when only OpenAI key is provided (OPENAI_BASE_URL temporarily disabled)', () => {
it('should set OPENAI_BASE_URL and not set ANTHROPIC_BASE_URL 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<string, string>;
expect(env.ANTHROPIC_BASE_URL).toBeUndefined();
// OPENAI_BASE_URL temporarily disabled for Codex - will be re-enabled in future
expect(env.OPENAI_BASE_URL).toBeUndefined();
expect(env.OPENAI_BASE_URL).toBe('http://172.30.0.30:10000/v1');
});

it('should set AWF_API_PROXY_IP in agent environment', () => {
Expand Down Expand Up @@ -1674,8 +1671,8 @@ describe('docker-manager', () => {
const env = agent.environment as Record<string, string>;
// Agent should NOT have the raw API key — only the sidecar gets it
expect(env.OPENAI_API_KEY).toBeUndefined();
// OPENAI_BASE_URL temporarily disabled for Codex - will be re-enabled in future
expect(env.OPENAI_BASE_URL).toBeUndefined();
// Agent should have OPENAI_BASE_URL to proxy through sidecar
expect(env.OPENAI_BASE_URL).toBe('http://172.30.0.30:10000/v1');
} finally {
if (origKey !== undefined) {
process.env.OPENAI_API_KEY = origKey;
Expand All @@ -1685,20 +1682,20 @@ describe('docker-manager', () => {
}
});

it('should pass CODEX_API_KEY to agent even when api-proxy is enabled with envAll', () => {
it('should not leak CODEX_API_KEY to agent when api-proxy is enabled with envAll', () => {
// Simulate the key being in process.env AND envAll enabled
// CODEX_API_KEY is intentionally passed through (unlike other keys) for Codex agent compatibility
// CODEX_API_KEY is now excluded when api-proxy is enabled for credential isolation
const origKey = process.env.CODEX_API_KEY;
process.env.CODEX_API_KEY = 'sk-codex-secret';
try {
const configWithProxy = { ...mockConfig, enableApiProxy: true, openaiApiKey: 'sk-test', envAll: true };
const result = generateDockerCompose(configWithProxy, mockNetworkConfigWithProxy);
const agent = result.services.agent;
const env = agent.environment as Record<string, string>;
// CODEX_API_KEY is intentionally passed to agent for Codex compatibility
expect(env.CODEX_API_KEY).toBe('sk-codex-secret');
// OPENAI_BASE_URL temporarily disabled for Codex - will be re-enabled in future
expect(env.OPENAI_BASE_URL).toBeUndefined();
// CODEX_API_KEY should NOT be passed to agent when api-proxy is enabled
expect(env.CODEX_API_KEY).toBeUndefined();
// OPENAI_BASE_URL should be set when api-proxy is enabled with openaiApiKey
expect(env.OPENAI_BASE_URL).toBe('http://172.30.0.30:10000/v1');
Comment on lines +1685 to +1698
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage currently validates that CODEX_API_KEY is excluded when enableApiProxy is true and openaiApiKey is provided, but it doesn’t cover the important edge case where enableApiProxy is true and only process.env.CODEX_API_KEY is set (no openaiApiKey). Given the new exclusion logic, adding an explicit test for that scenario would prevent regressions and clarify intended behavior (pass-through vs proxy configuration vs error).

Copilot uses AI. Check for mistakes.
} finally {
if (origKey !== undefined) {
process.env.CODEX_API_KEY = origKey;
Expand All @@ -1719,8 +1716,8 @@ describe('docker-manager', () => {
const env = agent.environment as Record<string, string>;
// Even with envAll, agent should NOT have OPENAI_API_KEY when api-proxy is enabled
expect(env.OPENAI_API_KEY).toBeUndefined();
// OPENAI_BASE_URL temporarily disabled for Codex - will be re-enabled in future
expect(env.OPENAI_BASE_URL).toBeUndefined();
// Agent should have OPENAI_BASE_URL to proxy through sidecar
expect(env.OPENAI_BASE_URL).toBe('http://172.30.0.30:10000/v1');
} finally {
if (origKey !== undefined) {
process.env.OPENAI_API_KEY = origKey;
Expand Down
14 changes: 6 additions & 8 deletions src/docker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,10 +328,10 @@ export function generateDockerCompose(

// When api-proxy is enabled, exclude API keys from agent environment
// (they are held securely in the api-proxy sidecar instead)
// Note: CODEX_API_KEY is intentionally NOT excluded - Codex needs direct credential access
if (config.enableApiProxy) {
EXCLUDED_ENV_VARS.add('OPENAI_API_KEY');
EXCLUDED_ENV_VARS.add('OPENAI_KEY');
EXCLUDED_ENV_VARS.add('CODEX_API_KEY');
EXCLUDED_ENV_VARS.add('ANTHROPIC_API_KEY');
EXCLUDED_ENV_VARS.add('CLAUDE_API_KEY');
}
Expand Down Expand Up @@ -418,9 +418,8 @@ export function generateDockerCompose(
if (process.env.GITHUB_PERSONAL_ACCESS_TOKEN) environment.GITHUB_PERSONAL_ACCESS_TOKEN = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
// API keys for LLM providers — skip when api-proxy is enabled
// (the sidecar holds the keys; the agent uses *_BASE_URL instead)
// Exception: CODEX_API_KEY is always passed through for Codex agent compatibility
if (process.env.OPENAI_API_KEY && !config.enableApiProxy) environment.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
if (process.env.CODEX_API_KEY) environment.CODEX_API_KEY = process.env.CODEX_API_KEY;
if (process.env.CODEX_API_KEY && !config.enableApiProxy) environment.CODEX_API_KEY = process.env.CODEX_API_KEY;
if (process.env.ANTHROPIC_API_KEY && !config.enableApiProxy) environment.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
if (process.env.USER) environment.USER = process.env.USER;
if (process.env.TERM) environment.TERM = process.env.TERM;
Expand Down Expand Up @@ -1009,11 +1008,10 @@ export function generateDockerCompose(
// Use IP address instead of hostname for BASE_URLs since Docker DNS may not resolve
// container names in chroot mode
environment.AWF_API_PROXY_IP = networkConfig.proxyIp;
// OPENAI_BASE_URL temporarily disabled for Codex - will be re-enabled in future
// if (config.openaiApiKey) {
// environment.OPENAI_BASE_URL = `http://${networkConfig.proxyIp}:10000/v1`;
// logger.debug(`OpenAI API will be proxied through sidecar at http://${networkConfig.proxyIp}:10000/v1`);
// }
if (config.openaiApiKey) {
environment.OPENAI_BASE_URL = `http://${networkConfig.proxyIp}:10000/v1`;
logger.debug(`OpenAI API will be proxied through sidecar at http://${networkConfig.proxyIp}:10000/v1`);
}
Comment on lines +1011 to +1014
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OPENAI_BASE_URL is only set when config.openaiApiKey is present. In combination with excluding CODEX_API_KEY when enableApiProxy is true, this creates a failure mode where Codex credentials are removed but no proxy endpoint is configured (e.g., proxy enabled + CODEX_API_KEY present + openaiApiKey absent). Consider aligning the condition for setting OPENAI_BASE_URL with the condition for excluding Codex/OpenAI keys (or emit a hard error when enableApiProxy is enabled without an OpenAI key).

This issue also appears on line 331 of the same file.

Suggested change
if (config.openaiApiKey) {
environment.OPENAI_BASE_URL = `http://${networkConfig.proxyIp}:10000/v1`;
logger.debug(`OpenAI API will be proxied through sidecar at http://${networkConfig.proxyIp}:10000/v1`);
}
environment.OPENAI_BASE_URL = `http://${networkConfig.proxyIp}:10000/v1`;
logger.debug(`OpenAI API will be proxied through sidecar at http://${networkConfig.proxyIp}:10000/v1`);

Copilot uses AI. Check for mistakes.
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`);
Expand Down
Loading