From d8be285861b59acf000d3f5b9e86a24f0e43d8e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:23:48 +0000 Subject: [PATCH 01/14] Initial plan From a1ed818e500c13423ea617a128a3890b3425d327 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:29:28 +0000 Subject: [PATCH 02/14] feat(api-proxy): add copilot api proxy support - Add copilotApiKey to WrapperConfig type - Update CLI to read COPILOT_API_KEY from environment - Add GitHub Copilot proxy endpoint on port 10002 in api-proxy server - Pass COPILOT_API_KEY to api-proxy container - Set COPILOT_API_URL and COPILOT_TOKEN in agent container - Exclude COPILOT_API_KEY from agent environment when api-proxy enabled Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/api-proxy/server.js | 31 +++++++++++++++++++++++++++++-- src/cli.ts | 17 ++++++++++++----- src/docker-manager.ts | 12 ++++++++++++ src/types.ts | 22 +++++++++++++++++++--- 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/containers/api-proxy/server.js b/containers/api-proxy/server.js index 8f381224..7b5b41b4 100644 --- a/containers/api-proxy/server.js +++ b/containers/api-proxy/server.js @@ -45,6 +45,7 @@ function sanitizeForLog(str) { // Read API keys from environment (set by docker-compose) const OPENAI_API_KEY = process.env.OPENAI_API_KEY; const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; +const COPILOT_API_KEY = process.env.COPILOT_API_KEY; // Squid proxy configuration (set via HTTP_PROXY/HTTPS_PROXY in docker-compose) const HTTPS_PROXY = process.env.HTTPS_PROXY || process.env.HTTP_PROXY; @@ -57,6 +58,9 @@ if (OPENAI_API_KEY) { if (ANTHROPIC_API_KEY) { console.log('[API Proxy] Anthropic API key configured'); } +if (COPILOT_API_KEY) { + console.log('[API Proxy] GitHub Copilot API key configured'); +} // Create proxy agent for routing through Squid const proxyAgent = HTTPS_PROXY ? new HttpsProxyAgent(HTTPS_PROXY) : undefined; @@ -169,7 +173,7 @@ if (OPENAI_API_KEY) { status: 'healthy', service: 'awf-api-proxy', squid_proxy: HTTPS_PROXY || 'not configured', - providers: { openai: true, anthropic: !!ANTHROPIC_API_KEY }, + providers: { openai: true, anthropic: !!ANTHROPIC_API_KEY, copilot: !!COPILOT_API_KEY }, })); return; } @@ -193,7 +197,7 @@ if (OPENAI_API_KEY) { status: 'healthy', service: 'awf-api-proxy', squid_proxy: HTTPS_PROXY || 'not configured', - providers: { openai: false, anthropic: !!ANTHROPIC_API_KEY }, + providers: { openai: false, anthropic: !!ANTHROPIC_API_KEY, copilot: !!COPILOT_API_KEY }, })); return; } @@ -231,6 +235,29 @@ if (ANTHROPIC_API_KEY) { }); } + +// GitHub Copilot API proxy (port 10002) +if (COPILOT_API_KEY) { + const copilotServer = http.createServer((req, res) => { + // Health check endpoint + if (req.url === '/health' && req.method === 'GET') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ status: 'healthy', service: 'copilot-proxy' })); + return; + } + + // Log and proxy the request + console.log(`[Copilot Proxy] ${sanitizeForLog(req.method)} ${sanitizeForLog(req.url)}`); + console.log(`[Copilot Proxy] Injecting Authorization header with COPILOT_API_KEY`); + proxyRequest(req, res, 'api.githubcopilot.com', { + 'Authorization': `Bearer ${COPILOT_API_KEY}`, + }); + }); + + copilotServer.listen(10002, '0.0.0.0', () => { + console.log('[API Proxy] GitHub Copilot proxy listening on port 10002'); + }); +} // Graceful shutdown process.on('SIGTERM', () => { console.log('[API Proxy] Received SIGTERM, shutting down gracefully...'); diff --git a/src/cli.ts b/src/cli.ts index 60b6b8a8..19279b56 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -262,12 +262,14 @@ export interface ApiProxyValidationResult { * @param enableApiProxy - Whether --enable-api-proxy flag was provided * @param hasOpenaiKey - Whether an OpenAI API key is present * @param hasAnthropicKey - Whether an Anthropic API key is present + * @param hasCopilotKey - Whether a GitHub Copilot API key is present * @returns ApiProxyValidationResult with warnings and debug messages */ export function validateApiProxyConfig( enableApiProxy: boolean, hasOpenaiKey?: boolean, - hasAnthropicKey?: boolean + hasAnthropicKey?: boolean, + hasCopilotKey?: boolean ): ApiProxyValidationResult { if (!enableApiProxy) { return { enabled: false, warnings: [], debugMessages: [] }; @@ -276,9 +278,9 @@ export function validateApiProxyConfig( const warnings: string[] = []; const debugMessages: string[] = []; - if (!hasOpenaiKey && !hasAnthropicKey) { + if (!hasOpenaiKey && !hasAnthropicKey && !hasCopilotKey) { warnings.push('⚠️ API proxy enabled but no API keys found in environment'); - warnings.push(' Set OPENAI_API_KEY or ANTHROPIC_API_KEY to use the proxy'); + warnings.push(' Set OPENAI_API_KEY, ANTHROPIC_API_KEY, or COPILOT_API_KEY to use the proxy'); } if (hasOpenaiKey) { debugMessages.push('OpenAI API key detected - will be held securely in sidecar'); @@ -286,6 +288,9 @@ export function validateApiProxyConfig( if (hasAnthropicKey) { debugMessages.push('Anthropic API key detected - will be held securely in sidecar'); } + if (hasCopilotKey) { + debugMessages.push('GitHub Copilot API key detected - will be held securely in sidecar'); + } return { enabled: true, warnings, debugMessages }; } @@ -987,6 +992,7 @@ program enableApiProxy: options.enableApiProxy, openaiApiKey: process.env.OPENAI_API_KEY, anthropicApiKey: process.env.ANTHROPIC_API_KEY, + copilotApiKey: process.env.COPILOT_API_KEY, }; // Warn if --env-all is used @@ -1025,7 +1031,8 @@ program const apiProxyValidation = validateApiProxyConfig( config.enableApiProxy || false, !!config.openaiApiKey, - !!config.anthropicApiKey + !!config.anthropicApiKey, + !!config.copilotApiKey ); for (const warning of apiProxyValidation.warnings) { logger.warn(warning); @@ -1038,7 +1045,7 @@ program // to prevent sensitive data from flowing to logger (CodeQL sensitive data logging) const redactedConfig: Record = {}; for (const [key, value] of Object.entries(config)) { - if (key === 'openaiApiKey' || key === 'anthropicApiKey') continue; + if (key === 'openaiApiKey' || key === 'anthropicApiKey' || key === 'copilotApiKey') continue; redactedConfig[key] = key === 'agentCommand' ? redactSecrets(value as string) : value; } logger.debug('Configuration:', JSON.stringify(redactedConfig, null, 2)); diff --git a/src/docker-manager.ts b/src/docker-manager.ts index ce6f9a14..c681f8ed 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -334,6 +334,7 @@ export function generateDockerCompose( EXCLUDED_ENV_VARS.add('CODEX_API_KEY'); EXCLUDED_ENV_VARS.add('ANTHROPIC_API_KEY'); EXCLUDED_ENV_VARS.add('CLAUDE_API_KEY'); + EXCLUDED_ENV_VARS.add('COPILOT_API_KEY'); } // Start with required/overridden environment variables @@ -421,6 +422,7 @@ export function generateDockerCompose( if (process.env.OPENAI_API_KEY && !config.enableApiProxy) environment.OPENAI_API_KEY = process.env.OPENAI_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.COPILOT_API_KEY && !config.enableApiProxy) environment.COPILOT_API_KEY = process.env.COPILOT_API_KEY; if (process.env.USER) environment.USER = process.env.USER; if (process.env.TERM) environment.TERM = process.env.TERM; if (process.env.XDG_CONFIG_HOME) environment.XDG_CONFIG_HOME = process.env.XDG_CONFIG_HOME; @@ -950,6 +952,7 @@ export function generateDockerCompose( // Pass API keys securely to sidecar (not visible to agent) ...(config.openaiApiKey && { OPENAI_API_KEY: config.openaiApiKey }), ...(config.anthropicApiKey && { ANTHROPIC_API_KEY: config.anthropicApiKey }), + ...(config.copilotApiKey && { COPILOT_API_KEY: config.copilotApiKey }), // Route through Squid to respect domain whitelisting HTTP_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`, HTTPS_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`, @@ -1013,6 +1016,15 @@ export function generateDockerCompose( 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'); } + if (config.copilotApiKey) { + environment.COPILOT_API_URL = `http://${networkConfig.proxyIp}:10002`; + logger.debug(`GitHub Copilot API will be proxied through sidecar at http://${networkConfig.proxyIp}:10002`); + + // Set placeholder token for GitHub Copilot CLI compatibility + // Real authentication happens via COPILOT_API_URL pointing to api-proxy + environment.COPILOT_TOKEN = 'placeholder-token-for-credential-isolation'; + logger.debug('COPILOT_TOKEN set to placeholder value for credential isolation'); + } logger.info('API proxy sidecar enabled - API keys will be held securely in sidecar container'); logger.info('API proxy will route through Squid to respect domain whitelisting'); diff --git a/src/types.ts b/src/types.ts index ece8415d..0d4d344f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -385,24 +385,27 @@ export interface WrapperConfig { * Enable API proxy sidecar for holding authentication credentials * * When true, deploys a Node.js proxy sidecar container that: - * - Holds OpenAI and Anthropic API keys securely + * - Holds OpenAI, Anthropic, and GitHub Copilot API keys securely * - Automatically injects authentication headers * - Routes all traffic through Squid to respect domain whitelisting * - Proxies requests to LLM providers * - * The sidecar exposes two endpoints accessible from the agent container: + * The sidecar exposes three endpoints accessible from the agent container: * - http://api-proxy:10000 - OpenAI API proxy (for Codex) * - http://api-proxy:10001 - Anthropic API proxy (for Claude) + * - http://api-proxy:10002 - GitHub Copilot API proxy * * When the corresponding API key is provided, the following environment * variables are set in the agent container: * - OPENAI_BASE_URL=http://api-proxy:10000/v1 (set when OPENAI_API_KEY is provided) * - ANTHROPIC_BASE_URL=http://api-proxy:10001 (set when ANTHROPIC_API_KEY is provided) + * - COPILOT_API_URL=http://api-proxy:10002 (set when COPILOT_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 * - ANTHROPIC_API_KEY - Optional Anthropic API key for Claude + * - COPILOT_API_KEY - Optional GitHub Copilot API key * * @default false * @example @@ -410,7 +413,8 @@ export interface WrapperConfig { * # Enable API proxy with keys from environment * export OPENAI_API_KEY="sk-..." * export ANTHROPIC_API_KEY="sk-ant-..." - * awf --enable-api-proxy --allow-domains api.openai.com,api.anthropic.com -- command + * export COPILOT_API_KEY="ghu_..." + * awf --enable-api-proxy --allow-domains api.openai.com,api.anthropic.com,api.githubcopilot.com -- command * ``` */ enableApiProxy?: boolean; @@ -438,6 +442,18 @@ export interface WrapperConfig { * @default undefined */ anthropicApiKey?: string; + + /** + * GitHub Copilot API key (used by API proxy sidecar) + * + * When enableApiProxy is true, this key is injected into the Node.js sidecar + * container and used to authenticate requests to api.githubcopilot.com. + * + * The key is NOT exposed to the agent container - only the proxy URL is provided. + * + * @default undefined + */ + copilotApiKey?: string; } /** From 2de280c527e9b9b0eb323c75a70085c3f970ce65 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:30:38 +0000 Subject: [PATCH 03/14] test(api-proxy): add copilot proxy integration tests Add integration tests for GitHub Copilot API proxy: - Health check endpoint test - COPILOT_API_URL environment variable test - COPILOT_TOKEN placeholder test - Health providers reporting test Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- tests/integration/api-proxy.test.ts | 78 +++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/integration/api-proxy.test.ts b/tests/integration/api-proxy.test.ts index 5fc24164..f0c2bcfa 100644 --- a/tests/integration/api-proxy.test.ts +++ b/tests/integration/api-proxy.test.ts @@ -172,4 +172,82 @@ describe('API Proxy Sidecar', () => { // Port 10001 should also be healthy expect(result.stdout).toContain('anthropic-proxy'); }, 180000); + + test('should start api-proxy sidecar with Copilot key and pass healthcheck', async () => { + const result = await runner.runWithSudo( + `curl -s http://${API_PROXY_IP}:10002/health`, + { + allowDomains: ['api.githubcopilot.com'], + enableApiProxy: true, + buildLocal: true, + logLevel: 'debug', + timeout: 120000, + env: { + COPILOT_API_KEY: 'ghu_fake-test-key-12345', + }, + } + ); + + expect(result).toSucceed(); + expect(result.stdout).toContain('"status":"healthy"'); + expect(result.stdout).toContain('copilot-proxy'); + }, 180000); + + test('should set COPILOT_API_URL in agent when Copilot key is provided', async () => { + const result = await runner.runWithSudo( + 'bash -c "echo COPILOT_API_URL=$COPILOT_API_URL"', + { + allowDomains: ['api.githubcopilot.com'], + enableApiProxy: true, + buildLocal: true, + logLevel: 'debug', + timeout: 120000, + env: { + COPILOT_API_KEY: 'ghu_fake-test-key-12345', + }, + } + ); + + expect(result).toSucceed(); + expect(result.stdout).toContain(`COPILOT_API_URL=http://${API_PROXY_IP}:10002`); + }, 180000); + + test('should set COPILOT_TOKEN to placeholder in agent when Copilot key is provided', async () => { + const result = await runner.runWithSudo( + 'bash -c "echo COPILOT_TOKEN=$COPILOT_TOKEN"', + { + allowDomains: ['api.githubcopilot.com'], + enableApiProxy: true, + buildLocal: true, + logLevel: 'debug', + timeout: 120000, + env: { + COPILOT_API_KEY: 'ghu_fake-test-key-12345', + }, + } + ); + + expect(result).toSucceed(); + expect(result.stdout).toContain('COPILOT_TOKEN=placeholder-token-for-credential-isolation'); + }, 180000); + + test('should report copilot in health providers when Copilot key is provided', async () => { + // When Copilot key is provided, the main health endpoint should report copilot: true + const result = await runner.runWithSudo( + `curl -s http://${API_PROXY_IP}:10000/health`, + { + allowDomains: ['api.githubcopilot.com'], + enableApiProxy: true, + buildLocal: true, + logLevel: 'debug', + timeout: 120000, + env: { + COPILOT_API_KEY: 'ghu_fake-test-key-12345', + }, + } + ); + + expect(result).toSucceed(); + expect(result.stdout).toContain('"copilot":true'); + }, 180000); }); From d563021406f67cb258e6d3b0f8e2da69ee770653 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:40:13 +0000 Subject: [PATCH 04/14] feat(examples): enable api-proxy and debug logging in copilot example - Enable --enable-api-proxy flag to use Copilot API proxy - Set AWF_ONE_SHOT_TOKEN_DEBUG=1 for debug output - Add COPILOT_API_KEY environment variable check - Update domain list to use api.githubcopilot.com (proxied) - Add --log-level debug for verbose output Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- examples/github-copilot.sh | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/examples/github-copilot.sh b/examples/github-copilot.sh index 8a28cc72..73c6aca2 100644 --- a/examples/github-copilot.sh +++ b/examples/github-copilot.sh @@ -1,20 +1,28 @@ #!/bin/bash -# Example: Using GitHub Copilot CLI with the firewall +# Example: Using GitHub Copilot CLI with the firewall and API proxy # -# This example shows how to run GitHub Copilot CLI through the firewall. -# Copilot requires access to several GitHub domains. +# This example shows how to run GitHub Copilot CLI through the firewall +# with credential isolation via the API proxy sidecar. # # Prerequisites: # - GitHub Copilot CLI installed: npm install -g @github/copilot -# - GITHUB_TOKEN environment variable set +# - COPILOT_API_KEY environment variable set (for API proxy) +# - GITHUB_TOKEN environment variable set (for GitHub API access) # # Usage: sudo -E ./examples/github-copilot.sh set -e -echo "=== AWF GitHub Copilot CLI Example ===" +echo "=== AWF GitHub Copilot CLI Example (with API Proxy) ===" echo "" +# Check for COPILOT_API_KEY +if [ -z "$COPILOT_API_KEY" ]; then + echo "Error: COPILOT_API_KEY environment variable is not set" + echo "Set it with: export COPILOT_API_KEY='your_copilot_api_key'" + exit 1 +fi + # Check for GITHUB_TOKEN if [ -z "$GITHUB_TOKEN" ]; then echo "Error: GITHUB_TOKEN environment variable is not set" @@ -22,18 +30,23 @@ if [ -z "$GITHUB_TOKEN" ]; then exit 1 fi -echo "Running GitHub Copilot CLI through the firewall..." +# Enable one-shot-token debug logging +export AWF_ONE_SHOT_TOKEN_DEBUG=1 + +echo "Running GitHub Copilot CLI with API proxy and debug logging enabled..." echo "" -# Run Copilot CLI with required domains -# Use sudo -E to preserve environment variables (especially GITHUB_TOKEN) +# Run Copilot CLI with API proxy enabled +# Use sudo -E to preserve environment variables (COPILOT_API_KEY, GITHUB_TOKEN, AWF_ONE_SHOT_TOKEN_DEBUG) # Required domains: +# - api.githubcopilot.com: Copilot API endpoint (proxied via api-proxy) # - github.com: GitHub API access # - api.github.com: GitHub REST API -# - api.enterprise.githubcopilot.com: Copilot API endpoint # - registry.npmjs.org: NPM package registry (for npx) sudo -E awf \ - --allow-domains github.com,api.github.com,api.enterprise.githubcopilot.com,registry.npmjs.org \ + --enable-api-proxy \ + --allow-domains api.githubcopilot.com,github.com,api.github.com,registry.npmjs.org \ + --log-level debug \ -- 'npx @github/copilot --prompt "What is 2+2?" --no-mcp' echo "" From 1fa603d46690151648a1681b9a244048e666df6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:52:42 +0000 Subject: [PATCH 05/14] feat(workflows): enable copilot api-proxy in smoke test - Add --enable-api-proxy flag to awf command - Set COPILOT_API_KEY environment variable from secrets - Set AWF_ONE_SHOT_TOKEN_DEBUG=1 for debug logging - Update validation to check both COPILOT_GITHUB_TOKEN and COPILOT_API_KEY Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- .github/workflows/smoke-copilot.lock.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 4c829c17..6c36c5a5 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -155,10 +155,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret + - name: Validate COPILOT_GITHUB_TOKEN and COPILOT_API_KEY secrets id: validate-secret - run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN COPILOT_API_KEY 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: + COPILOT_API_KEY: ${{ secrets.COPILOT_API_KEY }} COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Install GitHub Copilot CLI run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405 @@ -710,11 +711,13 @@ jobs: timeout-minutes: 5 run: | set -o pipefail - sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains '*.githubusercontent.com,*.jsr.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,bun.sh,cdn.playwright.dev,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deb.nodesource.com,deno.land,get.pnpm.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,lfs.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,playwright.download.prss.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,skimdb.npmjs.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.npmjs.com,www.npmjs.org,yarnpkg.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --build-local \ + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains '*.githubusercontent.com,*.jsr.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,bun.sh,cdn.playwright.dev,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deb.nodesource.com,deno.land,get.pnpm.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,lfs.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,playwright.download.prss.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,skimdb.npmjs.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.npmjs.com,www.npmjs.org,yarnpkg.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --build-local --enable-api-proxy \ -- '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' \ 2>&1 | tee /tmp/gh-aw/agent-stdio.log env: + AWF_ONE_SHOT_TOKEN_DEBUG: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_API_KEY: ${{ secrets.COPILOT_API_KEY }} COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} @@ -1029,10 +1032,11 @@ jobs: run: | mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - - name: Validate COPILOT_GITHUB_TOKEN secret + - name: Validate COPILOT_GITHUB_TOKEN and COPILOT_API_KEY secrets id: validate-secret - run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN COPILOT_API_KEY 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: + COPILOT_API_KEY: ${{ secrets.COPILOT_API_KEY }} COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Install GitHub Copilot CLI run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405 From 5d0c17e5a9879dfc282f91880c5cd82e1f55dee4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:03:00 +0000 Subject: [PATCH 06/14] fix(docker-manager): configure one-shot-token protection for sensitive tokens Set AWF_ONE_SHOT_TOKENS to protect COPILOT_GITHUB_TOKEN and other sensitive tokens. The C implementation of one-shot-token requires this env var to be configured with the list of tokens to protect. Without it, tokens like COPILOT_GITHUB_TOKEN were not being cached/protected and remained fully accessible in the agent. Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- src/docker-manager.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/docker-manager.ts b/src/docker-manager.ts index c681f8ed..0a851460 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -347,6 +347,9 @@ export function generateDockerCompose( SQUID_PROXY_PORT: SQUID_PORT.toString(), HOME: homeDir, PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + // Configure one-shot-token library with sensitive tokens to protect + // These tokens are cached on first access and unset from /proc/self/environ + AWF_ONE_SHOT_TOKENS: 'COPILOT_GITHUB_TOKEN,GITHUB_TOKEN,GH_TOKEN,GITHUB_API_TOKEN,GITHUB_PAT,GH_ACCESS_TOKEN,OPENAI_API_KEY,OPENAI_KEY,ANTHROPIC_API_KEY,CLAUDE_API_KEY,CODEX_API_KEY,COPILOT_API_KEY', }; // When host access is enabled, bypass the proxy for the host gateway IPs. From ea5b8f63f432a1cc31301e36c6ff0efe4c0dcb03 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:20:34 +0000 Subject: [PATCH 07/14] fix(workflows): include api-proxy logs in smoke-copilot artifacts - Add /tmp/gh-aw/sandbox/firewall/api-proxy-logs/ to artifact upload - Fix permissions for api-proxy-logs directory before upload - API proxy logs were being written to a sibling directory of firewall logs Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- .github/workflows/smoke-copilot.lock.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 6c36c5a5..2ef528c7 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -834,6 +834,7 @@ jobs: # Fix permissions on firewall logs so they can be uploaded as artifacts # AWF runs with sudo, creating files owned by root sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/api-proxy-logs 2>/dev/null || true awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" - name: Upload cache-memory data as artifact uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 @@ -855,6 +856,7 @@ jobs: /tmp/gh-aw/aw_info.json /tmp/gh-aw/mcp-logs/ /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/api-proxy-logs/ /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ if-no-files-found: ignore From 06459c64963e8fb93a2e3a80589e5092341eafb2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:30:19 +0000 Subject: [PATCH 08/14] fix(docker-manager): exclude copilot_github_token from agent when api-proxy enabled COPILOT_GITHUB_TOKEN is now excluded from the agent environment when --enable-api-proxy is used, ensuring no GitHub tokens are exposed to the agent container. This follows the credential isolation pattern where sensitive credentials are not passed to the agent when using the API proxy. Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- src/docker-manager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 0a851460..390f3837 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -335,6 +335,7 @@ export function generateDockerCompose( EXCLUDED_ENV_VARS.add('ANTHROPIC_API_KEY'); EXCLUDED_ENV_VARS.add('CLAUDE_API_KEY'); EXCLUDED_ENV_VARS.add('COPILOT_API_KEY'); + EXCLUDED_ENV_VARS.add('COPILOT_GITHUB_TOKEN'); } // Start with required/overridden environment variables From e2fc2fba7090766e977df06c81b8d6b6dcdd89c0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:31:48 +0000 Subject: [PATCH 09/14] feat(agent): add copilot api proxy health check with url logging Add health check for GitHub Copilot API proxy configuration that: - Logs COPILOT_API_URL value at container startup - Verifies COPILOT_API_KEY and COPILOT_GITHUB_TOKEN are excluded - Validates COPILOT_TOKEN is placeholder value - Tests connectivity to the proxy endpoint Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/agent/api-proxy-health-check.sh | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/containers/agent/api-proxy-health-check.sh b/containers/agent/api-proxy-health-check.sh index e83b723f..ba2bffb1 100755 --- a/containers/agent/api-proxy-health-check.sh +++ b/containers/agent/api-proxy-health-check.sh @@ -91,6 +91,49 @@ if [ -n "$OPENAI_BASE_URL" ]; then fi fi +# Check GitHub Copilot configuration +if [ -n "$COPILOT_API_URL" ]; then + API_PROXY_CONFIGURED=true + echo "[health-check] Checking GitHub Copilot API proxy configuration..." + echo "[health-check] COPILOT_API_URL=$COPILOT_API_URL" + + # Verify credentials are NOT in agent environment + if [ -n "$COPILOT_API_KEY" ] || [ -n "$COPILOT_GITHUB_TOKEN" ]; then + echo "[health-check][ERROR] Copilot credentials found in agent environment!" + echo "[health-check][ERROR] Credential isolation failed - keys should only be in api-proxy container" + echo "[health-check][ERROR] COPILOT_API_KEY=${COPILOT_API_KEY:+}" + echo "[health-check][ERROR] COPILOT_GITHUB_TOKEN=${COPILOT_GITHUB_TOKEN:+}" + exit 1 + fi + echo "[health-check] ✓ Copilot credentials NOT in agent environment (correct)" + + # Verify COPILOT_TOKEN is placeholder (if present) + if [ -n "$COPILOT_TOKEN" ]; then + if [ "$COPILOT_TOKEN" != "placeholder-token-for-credential-isolation" ]; then + echo "[health-check][ERROR] COPILOT_TOKEN contains non-placeholder value!" + echo "[health-check][ERROR] Token should be 'placeholder-token-for-credential-isolation'" + exit 1 + fi + echo "[health-check] ✓ COPILOT_TOKEN is placeholder value (correct)" + fi + + # Perform health check using API URL + echo "[health-check] Testing connectivity to GitHub Copilot API proxy at $COPILOT_API_URL..." + + # Extract host and port from API URL (format: http://IP:PORT) + PROXY_HOST=$(echo "$COPILOT_API_URL" | sed -E 's|^https?://([^:]+):.*|\1|') + PROXY_PORT=$(echo "$COPILOT_API_URL" | sed -E 's|^https?://[^:]+:([0-9]+).*|\1|') + + # Test TCP connectivity with timeout + if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$PROXY_HOST/$PROXY_PORT" 2>/dev/null; then + echo "[health-check] ✓ GitHub Copilot API proxy is reachable at $COPILOT_API_URL" + else + echo "[health-check][ERROR] Cannot connect to GitHub Copilot API proxy at $COPILOT_API_URL" + echo "[health-check][ERROR] Proxy may not be running or network is blocked" + exit 1 + fi +fi + # Summary if [ "$API_PROXY_CONFIGURED" = "true" ]; then echo "[health-check] ==========================================" From 47f5a758e74d3df3c1d188c904b795bd66d8f41c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:43:05 +0000 Subject: [PATCH 10/14] feat(docker-manager): set copilot_github_token placeholder for one-shot protection Instead of excluding COPILOT_GITHUB_TOKEN entirely, set it to a placeholder value that gets protected by the one-shot-token library (already in hard-coded defaults). Changes: - Remove COPILOT_GITHUB_TOKEN from EXCLUDED_ENV_VARS - Set COPILOT_GITHUB_TOKEN=placeholder-token-for-credential-isolation in agent - Update health check to validate placeholder value (not absence) - COPILOT_GITHUB_TOKEN is already in both C and Rust one-shot-token defaults Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/agent/api-proxy-health-check.sh | 19 ++++++++++++++----- src/docker-manager.ts | 6 +++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/containers/agent/api-proxy-health-check.sh b/containers/agent/api-proxy-health-check.sh index ba2bffb1..d1c72f65 100755 --- a/containers/agent/api-proxy-health-check.sh +++ b/containers/agent/api-proxy-health-check.sh @@ -98,14 +98,23 @@ if [ -n "$COPILOT_API_URL" ]; then echo "[health-check] COPILOT_API_URL=$COPILOT_API_URL" # Verify credentials are NOT in agent environment - if [ -n "$COPILOT_API_KEY" ] || [ -n "$COPILOT_GITHUB_TOKEN" ]; then - echo "[health-check][ERROR] Copilot credentials found in agent environment!" - echo "[health-check][ERROR] Credential isolation failed - keys should only be in api-proxy container" + if [ -n "$COPILOT_API_KEY" ]; then + echo "[health-check][ERROR] COPILOT_API_KEY found in agent environment!" + echo "[health-check][ERROR] Credential isolation failed - API key should only be in api-proxy container" echo "[health-check][ERROR] COPILOT_API_KEY=${COPILOT_API_KEY:+}" - echo "[health-check][ERROR] COPILOT_GITHUB_TOKEN=${COPILOT_GITHUB_TOKEN:+}" exit 1 fi - echo "[health-check] ✓ Copilot credentials NOT in agent environment (correct)" + echo "[health-check] ✓ COPILOT_API_KEY NOT in agent environment (correct)" + + # Verify COPILOT_GITHUB_TOKEN is placeholder (protected by one-shot-token) + if [ -n "$COPILOT_GITHUB_TOKEN" ]; then + if [ "$COPILOT_GITHUB_TOKEN" != "placeholder-token-for-credential-isolation" ]; then + echo "[health-check][ERROR] COPILOT_GITHUB_TOKEN contains non-placeholder value!" + echo "[health-check][ERROR] Token should be 'placeholder-token-for-credential-isolation'" + exit 1 + fi + echo "[health-check] ✓ COPILOT_GITHUB_TOKEN is placeholder value (correct)" + fi # Verify COPILOT_TOKEN is placeholder (if present) if [ -n "$COPILOT_TOKEN" ]; then diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 390f3837..9a0d6aee 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -335,7 +335,6 @@ export function generateDockerCompose( EXCLUDED_ENV_VARS.add('ANTHROPIC_API_KEY'); EXCLUDED_ENV_VARS.add('CLAUDE_API_KEY'); EXCLUDED_ENV_VARS.add('COPILOT_API_KEY'); - EXCLUDED_ENV_VARS.add('COPILOT_GITHUB_TOKEN'); } // Start with required/overridden environment variables @@ -1028,6 +1027,11 @@ export function generateDockerCompose( // Real authentication happens via COPILOT_API_URL pointing to api-proxy environment.COPILOT_TOKEN = 'placeholder-token-for-credential-isolation'; logger.debug('COPILOT_TOKEN set to placeholder value for credential isolation'); + + // Set placeholder for COPILOT_GITHUB_TOKEN (protected by one-shot-token library) + // Real authentication happens via COPILOT_API_URL pointing to api-proxy + environment.COPILOT_GITHUB_TOKEN = 'placeholder-token-for-credential-isolation'; + logger.debug('COPILOT_GITHUB_TOKEN set to placeholder value for credential isolation'); } logger.info('API proxy sidecar enabled - API keys will be held securely in sidecar container'); From 1a3b7429f826fb2c0c198308e85979cefaa6c864 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:27:32 +0000 Subject: [PATCH 11/14] fix(docker-manager): set copilot_github_token placeholder before env-all Move COPILOT_GITHUB_TOKEN placeholder initialization to before --env-all logic to prevent the real token from overriding the placeholder value. Changes: - Set COPILOT_GITHUB_TOKEN placeholder early (right after environment init) - Add copilot-pre-exec-check.sh script for pre-execution validation - Run validation before Copilot CLI executes in smoke test - Validates COPILOT_API_URL is set and COPILOT_GITHUB_TOKEN is placeholder Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- .github/workflows/smoke-copilot.lock.yml | 2 +- containers/agent/Dockerfile | 3 +- containers/agent/copilot-pre-exec-check.sh | 57 ++++++++++++++++++++++ src/docker-manager.ts | 13 +++-- 4 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 containers/agent/copilot-pre-exec-check.sh diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 2ef528c7..f4741263 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -712,7 +712,7 @@ jobs: run: | set -o pipefail sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains '*.githubusercontent.com,*.jsr.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,bun.sh,cdn.playwright.dev,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deb.nodesource.com,deno.land,get.pnpm.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,lfs.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,playwright.download.prss.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,skimdb.npmjs.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.npmjs.com,www.npmjs.org,yarnpkg.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --build-local --enable-api-proxy \ - -- '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' \ + -- 'bash -c "/usr/local/bin/copilot-pre-exec-check.sh && /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir \"${GITHUB_WORKSPACE}\" --disable-builtin-mcps --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt \"$(cat /tmp/gh-aw/aw-prompts/prompt.txt)\"${GH_AW_MODEL_AGENT_COPILOT:+ --model \"$GH_AW_MODEL_AGENT_COPILOT\"}"' \ 2>&1 | tee /tmp/gh-aw/agent-stdio.log env: AWF_ONE_SHOT_TOKEN_DEBUG: 1 diff --git a/containers/agent/Dockerfile b/containers/agent/Dockerfile index 305a4f78..ab5b0417 100644 --- a/containers/agent/Dockerfile +++ b/containers/agent/Dockerfile @@ -68,7 +68,8 @@ COPY entrypoint.sh /usr/local/bin/entrypoint.sh COPY pid-logger.sh /usr/local/bin/pid-logger.sh COPY api-proxy-health-check.sh /usr/local/bin/api-proxy-health-check.sh COPY get-claude-key.sh /usr/local/bin/get-claude-key.sh -RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/local/bin/pid-logger.sh /usr/local/bin/api-proxy-health-check.sh /usr/local/bin/get-claude-key.sh +COPY copilot-pre-exec-check.sh /usr/local/bin/copilot-pre-exec-check.sh +RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/local/bin/pid-logger.sh /usr/local/bin/api-proxy-health-check.sh /usr/local/bin/get-claude-key.sh /usr/local/bin/copilot-pre-exec-check.sh # Copy pre-built one-shot-token library from rust-builder stage # This prevents tokens from being read multiple times (e.g., by malicious code) diff --git a/containers/agent/copilot-pre-exec-check.sh b/containers/agent/copilot-pre-exec-check.sh new file mode 100644 index 00000000..3265582c --- /dev/null +++ b/containers/agent/copilot-pre-exec-check.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# copilot-pre-exec-check.sh +# Pre-execution validation for GitHub Copilot with API proxy +# Validates that environment is correctly configured before running Copilot CLI +# +# Usage: Run this before executing Copilot CLI commands +# Returns: 0 if checks pass, 1 if checks fail (should prevent execution) + +set -e + +echo "[copilot-check] GitHub Copilot Pre-Execution Validation" +echo "[copilot-check] ==================================================" + +# Check if COPILOT_API_URL is set +if [ -z "$COPILOT_API_URL" ]; then + echo "[copilot-check][ERROR] COPILOT_API_URL is not set!" + echo "[copilot-check][ERROR] API proxy may not be enabled or configured" + exit 1 +fi +echo "[copilot-check] ✓ COPILOT_API_URL is set: $COPILOT_API_URL" + +# Check if COPILOT_GITHUB_TOKEN is set to placeholder +if [ -z "$COPILOT_GITHUB_TOKEN" ]; then + echo "[copilot-check][ERROR] COPILOT_GITHUB_TOKEN is not set!" + echo "[copilot-check][ERROR] Expected placeholder value for credential isolation" + exit 1 +fi + +if [ "$COPILOT_GITHUB_TOKEN" != "placeholder-token-for-credential-isolation" ]; then + echo "[copilot-check][ERROR] COPILOT_GITHUB_TOKEN is not set to placeholder value!" + echo "[copilot-check][ERROR] Current value starts with: ${COPILOT_GITHUB_TOKEN:0:5}..." + echo "[copilot-check][ERROR] Expected: placeholder-token-for-credential-isolation" + echo "[copilot-check][ERROR] Real token detected - credential isolation failed" + exit 1 +fi +echo "[copilot-check] ✓ COPILOT_GITHUB_TOKEN is placeholder value (correct)" + +# Test connectivity to COPILOT_API_URL +echo "[copilot-check] Testing connectivity to $COPILOT_API_URL..." + +# Extract host and port from API URL (format: http://IP:PORT) +PROXY_HOST=$(echo "$COPILOT_API_URL" | sed -E 's|^https?://([^:]+):.*|\1|') +PROXY_PORT=$(echo "$COPILOT_API_URL" | sed -E 's|^https?://[^:]+:([0-9]+).*|\1|') + +# Test TCP connectivity with timeout +if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$PROXY_HOST/$PROXY_PORT" 2>/dev/null; then + echo "[copilot-check] ✓ GitHub Copilot API proxy is reachable" +else + echo "[copilot-check][ERROR] Cannot connect to GitHub Copilot API proxy at $COPILOT_API_URL" + echo "[copilot-check][ERROR] Proxy may not be running or network is blocked" + exit 1 +fi + +echo "[copilot-check] ==================================================" +echo "[copilot-check] ✓ All pre-execution checks passed" +echo "[copilot-check] ✓ Ready to run GitHub Copilot CLI" +echo "[copilot-check] ==================================================" diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 9a0d6aee..0c75bd83 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -352,6 +352,13 @@ export function generateDockerCompose( AWF_ONE_SHOT_TOKENS: 'COPILOT_GITHUB_TOKEN,GITHUB_TOKEN,GH_TOKEN,GITHUB_API_TOKEN,GITHUB_PAT,GH_ACCESS_TOKEN,OPENAI_API_KEY,OPENAI_KEY,ANTHROPIC_API_KEY,CLAUDE_API_KEY,CODEX_API_KEY,COPILOT_API_KEY', }; + // When api-proxy is enabled with Copilot, set placeholder tokens early + // so --env-all won't override them with real values from host environment + if (config.enableApiProxy && config.copilotApiKey) { + environment.COPILOT_GITHUB_TOKEN = 'placeholder-token-for-credential-isolation'; + logger.debug('COPILOT_GITHUB_TOKEN set to placeholder value (early) to prevent --env-all override'); + } + // When host access is enabled, bypass the proxy for the host gateway IPs. // MCP Streamable HTTP (SSE) traffic through Squid crashes it (comm.cc:1583), // so MCP gateway traffic must go directly to the host, not through Squid. @@ -1028,10 +1035,8 @@ export function generateDockerCompose( environment.COPILOT_TOKEN = 'placeholder-token-for-credential-isolation'; logger.debug('COPILOT_TOKEN set to placeholder value for credential isolation'); - // Set placeholder for COPILOT_GITHUB_TOKEN (protected by one-shot-token library) - // Real authentication happens via COPILOT_API_URL pointing to api-proxy - environment.COPILOT_GITHUB_TOKEN = 'placeholder-token-for-credential-isolation'; - logger.debug('COPILOT_GITHUB_TOKEN set to placeholder value for credential isolation'); + // Note: COPILOT_GITHUB_TOKEN placeholder is set early (before --env-all) + // to prevent override by host environment variable } logger.info('API proxy sidecar enabled - API keys will be held securely in sidecar container'); From 1df8d2b93d0c4c696a84989da36d1af12397fae4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:41:38 +0000 Subject: [PATCH 12/14] fix(workflows): remove copilot pre-exec check, rely on health check Remove copilot-pre-exec-check.sh script and revert to simple execution pattern that matches Claude/Codex workflows. The existing api-proxy-health-check.sh already validates configuration at container startup. Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- .github/workflows/smoke-copilot.lock.yml | 2 +- containers/agent/Dockerfile | 3 +- containers/agent/copilot-pre-exec-check.sh | 57 ---------------------- 3 files changed, 2 insertions(+), 60 deletions(-) delete mode 100644 containers/agent/copilot-pre-exec-check.sh diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index f4741263..2ef528c7 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -712,7 +712,7 @@ jobs: run: | set -o pipefail sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains '*.githubusercontent.com,*.jsr.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,bun.sh,cdn.playwright.dev,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,deb.nodesource.com,deno.land,get.pnpm.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,lfs.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,playwright.download.prss.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,skimdb.npmjs.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.npmjs.com,www.npmjs.org,yarnpkg.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --build-local --enable-api-proxy \ - -- 'bash -c "/usr/local/bin/copilot-pre-exec-check.sh && /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir \"${GITHUB_WORKSPACE}\" --disable-builtin-mcps --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt \"$(cat /tmp/gh-aw/aw-prompts/prompt.txt)\"${GH_AW_MODEL_AGENT_COPILOT:+ --model \"$GH_AW_MODEL_AGENT_COPILOT\"}"' \ + -- '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' \ 2>&1 | tee /tmp/gh-aw/agent-stdio.log env: AWF_ONE_SHOT_TOKEN_DEBUG: 1 diff --git a/containers/agent/Dockerfile b/containers/agent/Dockerfile index ab5b0417..305a4f78 100644 --- a/containers/agent/Dockerfile +++ b/containers/agent/Dockerfile @@ -68,8 +68,7 @@ COPY entrypoint.sh /usr/local/bin/entrypoint.sh COPY pid-logger.sh /usr/local/bin/pid-logger.sh COPY api-proxy-health-check.sh /usr/local/bin/api-proxy-health-check.sh COPY get-claude-key.sh /usr/local/bin/get-claude-key.sh -COPY copilot-pre-exec-check.sh /usr/local/bin/copilot-pre-exec-check.sh -RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/local/bin/pid-logger.sh /usr/local/bin/api-proxy-health-check.sh /usr/local/bin/get-claude-key.sh /usr/local/bin/copilot-pre-exec-check.sh +RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/local/bin/pid-logger.sh /usr/local/bin/api-proxy-health-check.sh /usr/local/bin/get-claude-key.sh # Copy pre-built one-shot-token library from rust-builder stage # This prevents tokens from being read multiple times (e.g., by malicious code) diff --git a/containers/agent/copilot-pre-exec-check.sh b/containers/agent/copilot-pre-exec-check.sh deleted file mode 100644 index 3265582c..00000000 --- a/containers/agent/copilot-pre-exec-check.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -# copilot-pre-exec-check.sh -# Pre-execution validation for GitHub Copilot with API proxy -# Validates that environment is correctly configured before running Copilot CLI -# -# Usage: Run this before executing Copilot CLI commands -# Returns: 0 if checks pass, 1 if checks fail (should prevent execution) - -set -e - -echo "[copilot-check] GitHub Copilot Pre-Execution Validation" -echo "[copilot-check] ==================================================" - -# Check if COPILOT_API_URL is set -if [ -z "$COPILOT_API_URL" ]; then - echo "[copilot-check][ERROR] COPILOT_API_URL is not set!" - echo "[copilot-check][ERROR] API proxy may not be enabled or configured" - exit 1 -fi -echo "[copilot-check] ✓ COPILOT_API_URL is set: $COPILOT_API_URL" - -# Check if COPILOT_GITHUB_TOKEN is set to placeholder -if [ -z "$COPILOT_GITHUB_TOKEN" ]; then - echo "[copilot-check][ERROR] COPILOT_GITHUB_TOKEN is not set!" - echo "[copilot-check][ERROR] Expected placeholder value for credential isolation" - exit 1 -fi - -if [ "$COPILOT_GITHUB_TOKEN" != "placeholder-token-for-credential-isolation" ]; then - echo "[copilot-check][ERROR] COPILOT_GITHUB_TOKEN is not set to placeholder value!" - echo "[copilot-check][ERROR] Current value starts with: ${COPILOT_GITHUB_TOKEN:0:5}..." - echo "[copilot-check][ERROR] Expected: placeholder-token-for-credential-isolation" - echo "[copilot-check][ERROR] Real token detected - credential isolation failed" - exit 1 -fi -echo "[copilot-check] ✓ COPILOT_GITHUB_TOKEN is placeholder value (correct)" - -# Test connectivity to COPILOT_API_URL -echo "[copilot-check] Testing connectivity to $COPILOT_API_URL..." - -# Extract host and port from API URL (format: http://IP:PORT) -PROXY_HOST=$(echo "$COPILOT_API_URL" | sed -E 's|^https?://([^:]+):.*|\1|') -PROXY_PORT=$(echo "$COPILOT_API_URL" | sed -E 's|^https?://[^:]+:([0-9]+).*|\1|') - -# Test TCP connectivity with timeout -if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$PROXY_HOST/$PROXY_PORT" 2>/dev/null; then - echo "[copilot-check] ✓ GitHub Copilot API proxy is reachable" -else - echo "[copilot-check][ERROR] Cannot connect to GitHub Copilot API proxy at $COPILOT_API_URL" - echo "[copilot-check][ERROR] Proxy may not be running or network is blocked" - exit 1 -fi - -echo "[copilot-check] ==================================================" -echo "[copilot-check] ✓ All pre-execution checks passed" -echo "[copilot-check] ✓ Ready to run GitHub Copilot CLI" -echo "[copilot-check] ==================================================" From f99e45ad20c4ef74a113632feb400486bcd1ac57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:58:25 +0000 Subject: [PATCH 13/14] fix(cli): add api proxy status logging for debugging Add info-level logging to show which API keys are detected when api-proxy is enabled. Update health check message to include COPILOT_API_URL. This helps diagnose configuration issues when API keys aren't being read. Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/agent/api-proxy-health-check.sh | 2 +- src/cli.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/containers/agent/api-proxy-health-check.sh b/containers/agent/api-proxy-health-check.sh index d1c72f65..03d51a58 100755 --- a/containers/agent/api-proxy-health-check.sh +++ b/containers/agent/api-proxy-health-check.sh @@ -151,7 +151,7 @@ if [ "$API_PROXY_CONFIGURED" = "true" ]; then echo "[health-check] ✓ Connectivity established" echo "[health-check] ==========================================" else - echo "[health-check] No API proxy configured (ANTHROPIC_BASE_URL and OPENAI_BASE_URL not set)" + echo "[health-check] No API proxy configured (ANTHROPIC_BASE_URL, OPENAI_BASE_URL, and COPILOT_API_URL not set)" echo "[health-check] Skipping health checks" fi diff --git a/src/cli.ts b/src/cli.ts index 19279b56..a3554f7a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1034,6 +1034,12 @@ program !!config.anthropicApiKey, !!config.copilotApiKey ); + + // Log API proxy status at info level for visibility + if (config.enableApiProxy) { + logger.info(`API proxy enabled: OpenAI=${!!config.openaiApiKey}, Anthropic=${!!config.anthropicApiKey}, Copilot=${!!config.copilotApiKey}`); + } + for (const warning of apiProxyValidation.warnings) { logger.warn(warning); } From 6bf49ed27bb02ad9ef4a77a15a85092cdbcfd6ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Feb 2026 23:15:34 +0000 Subject: [PATCH 14/14] fix: replace copilot_api_key with copilot_github_token Change implementation to use COPILOT_GITHUB_TOKEN directly instead of a separate COPILOT_API_KEY. This aligns with how Copilot authentication actually works - there's only one GitHub token, not two separate credentials. Changes: - Renamed copilotApiKey to copilotGithubToken in types - CLI now reads COPILOT_GITHUB_TOKEN from environment - API proxy reads COPILOT_GITHUB_TOKEN and uses it for Authorization header - Removed COPILOT_API_KEY from excluded env vars (no longer used) - Removed COPILOT_API_KEY from AWF_ONE_SHOT_TOKENS list - Updated tests to use COPILOT_GITHUB_TOKEN - Updated example script - Updated workflow to only require COPILOT_GITHUB_TOKEN secret Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- .github/workflows/smoke-copilot.lock.yml | 11 ++++------- containers/agent/api-proxy-health-check.sh | 9 --------- containers/api-proxy/server.js | 16 ++++++++-------- examples/github-copilot.sh | 2 +- src/cli.ts | 10 +++++----- src/docker-manager.ts | 12 ++++++------ src/types.ts | 15 ++++++++------- tests/integration/api-proxy.test.ts | 16 ++++++++-------- 8 files changed, 40 insertions(+), 51 deletions(-) diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 2ef528c7..7e52d161 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -155,11 +155,10 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN and COPILOT_API_KEY secrets + - name: Validate COPILOT_GITHUB_TOKEN secret id: validate-secret - run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN COPILOT_API_KEY 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: - COPILOT_API_KEY: ${{ secrets.COPILOT_API_KEY }} COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Install GitHub Copilot CLI run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405 @@ -717,7 +716,6 @@ jobs: env: AWF_ONE_SHOT_TOKEN_DEBUG: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: ${{ secrets.COPILOT_API_KEY }} COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} @@ -1034,11 +1032,10 @@ jobs: run: | mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - - name: Validate COPILOT_GITHUB_TOKEN and COPILOT_API_KEY secrets + - name: Validate COPILOT_GITHUB_TOKEN secret id: validate-secret - run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN COPILOT_API_KEY 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: - COPILOT_API_KEY: ${{ secrets.COPILOT_API_KEY }} COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Install GitHub Copilot CLI run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.405 diff --git a/containers/agent/api-proxy-health-check.sh b/containers/agent/api-proxy-health-check.sh index 03d51a58..3f76777f 100755 --- a/containers/agent/api-proxy-health-check.sh +++ b/containers/agent/api-proxy-health-check.sh @@ -97,15 +97,6 @@ if [ -n "$COPILOT_API_URL" ]; then echo "[health-check] Checking GitHub Copilot API proxy configuration..." echo "[health-check] COPILOT_API_URL=$COPILOT_API_URL" - # Verify credentials are NOT in agent environment - if [ -n "$COPILOT_API_KEY" ]; then - echo "[health-check][ERROR] COPILOT_API_KEY found in agent environment!" - echo "[health-check][ERROR] Credential isolation failed - API key should only be in api-proxy container" - echo "[health-check][ERROR] COPILOT_API_KEY=${COPILOT_API_KEY:+}" - exit 1 - fi - echo "[health-check] ✓ COPILOT_API_KEY NOT in agent environment (correct)" - # Verify COPILOT_GITHUB_TOKEN is placeholder (protected by one-shot-token) if [ -n "$COPILOT_GITHUB_TOKEN" ]; then if [ "$COPILOT_GITHUB_TOKEN" != "placeholder-token-for-credential-isolation" ]; then diff --git a/containers/api-proxy/server.js b/containers/api-proxy/server.js index 7b5b41b4..aec9ab00 100644 --- a/containers/api-proxy/server.js +++ b/containers/api-proxy/server.js @@ -45,7 +45,7 @@ function sanitizeForLog(str) { // Read API keys from environment (set by docker-compose) const OPENAI_API_KEY = process.env.OPENAI_API_KEY; const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; -const COPILOT_API_KEY = process.env.COPILOT_API_KEY; +const COPILOT_GITHUB_TOKEN = process.env.COPILOT_GITHUB_TOKEN; // Squid proxy configuration (set via HTTP_PROXY/HTTPS_PROXY in docker-compose) const HTTPS_PROXY = process.env.HTTPS_PROXY || process.env.HTTP_PROXY; @@ -58,8 +58,8 @@ if (OPENAI_API_KEY) { if (ANTHROPIC_API_KEY) { console.log('[API Proxy] Anthropic API key configured'); } -if (COPILOT_API_KEY) { - console.log('[API Proxy] GitHub Copilot API key configured'); +if (COPILOT_GITHUB_TOKEN) { + console.log('[API Proxy] GitHub Copilot token configured'); } // Create proxy agent for routing through Squid @@ -173,7 +173,7 @@ if (OPENAI_API_KEY) { status: 'healthy', service: 'awf-api-proxy', squid_proxy: HTTPS_PROXY || 'not configured', - providers: { openai: true, anthropic: !!ANTHROPIC_API_KEY, copilot: !!COPILOT_API_KEY }, + providers: { openai: true, anthropic: !!ANTHROPIC_API_KEY, copilot: !!COPILOT_GITHUB_TOKEN }, })); return; } @@ -197,7 +197,7 @@ if (OPENAI_API_KEY) { status: 'healthy', service: 'awf-api-proxy', squid_proxy: HTTPS_PROXY || 'not configured', - providers: { openai: false, anthropic: !!ANTHROPIC_API_KEY, copilot: !!COPILOT_API_KEY }, + providers: { openai: false, anthropic: !!ANTHROPIC_API_KEY, copilot: !!COPILOT_GITHUB_TOKEN }, })); return; } @@ -237,7 +237,7 @@ if (ANTHROPIC_API_KEY) { // GitHub Copilot API proxy (port 10002) -if (COPILOT_API_KEY) { +if (COPILOT_GITHUB_TOKEN) { const copilotServer = http.createServer((req, res) => { // Health check endpoint if (req.url === '/health' && req.method === 'GET') { @@ -248,9 +248,9 @@ if (COPILOT_API_KEY) { // Log and proxy the request console.log(`[Copilot Proxy] ${sanitizeForLog(req.method)} ${sanitizeForLog(req.url)}`); - console.log(`[Copilot Proxy] Injecting Authorization header with COPILOT_API_KEY`); + console.log(`[Copilot Proxy] Injecting Authorization header with COPILOT_GITHUB_TOKEN`); proxyRequest(req, res, 'api.githubcopilot.com', { - 'Authorization': `Bearer ${COPILOT_API_KEY}`, + 'Authorization': `Bearer ${COPILOT_GITHUB_TOKEN}`, }); }); diff --git a/examples/github-copilot.sh b/examples/github-copilot.sh index 73c6aca2..157b254c 100644 --- a/examples/github-copilot.sh +++ b/examples/github-copilot.sh @@ -37,7 +37,7 @@ echo "Running GitHub Copilot CLI with API proxy and debug logging enabled..." echo "" # Run Copilot CLI with API proxy enabled -# Use sudo -E to preserve environment variables (COPILOT_API_KEY, GITHUB_TOKEN, AWF_ONE_SHOT_TOKEN_DEBUG) +# Use sudo -E to preserve environment variables (COPILOT_GITHUB_TOKEN, GITHUB_TOKEN, AWF_ONE_SHOT_TOKEN_DEBUG) # Required domains: # - api.githubcopilot.com: Copilot API endpoint (proxied via api-proxy) # - github.com: GitHub API access diff --git a/src/cli.ts b/src/cli.ts index a3554f7a..3805b796 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -280,7 +280,7 @@ export function validateApiProxyConfig( if (!hasOpenaiKey && !hasAnthropicKey && !hasCopilotKey) { warnings.push('⚠️ API proxy enabled but no API keys found in environment'); - warnings.push(' Set OPENAI_API_KEY, ANTHROPIC_API_KEY, or COPILOT_API_KEY to use the proxy'); + warnings.push(' Set OPENAI_API_KEY, ANTHROPIC_API_KEY, or COPILOT_GITHUB_TOKEN to use the proxy'); } if (hasOpenaiKey) { debugMessages.push('OpenAI API key detected - will be held securely in sidecar'); @@ -992,7 +992,7 @@ program enableApiProxy: options.enableApiProxy, openaiApiKey: process.env.OPENAI_API_KEY, anthropicApiKey: process.env.ANTHROPIC_API_KEY, - copilotApiKey: process.env.COPILOT_API_KEY, + copilotGithubToken: process.env.COPILOT_GITHUB_TOKEN, }; // Warn if --env-all is used @@ -1032,12 +1032,12 @@ program config.enableApiProxy || false, !!config.openaiApiKey, !!config.anthropicApiKey, - !!config.copilotApiKey + !!config.copilotGithubToken ); // Log API proxy status at info level for visibility if (config.enableApiProxy) { - logger.info(`API proxy enabled: OpenAI=${!!config.openaiApiKey}, Anthropic=${!!config.anthropicApiKey}, Copilot=${!!config.copilotApiKey}`); + logger.info(`API proxy enabled: OpenAI=${!!config.openaiApiKey}, Anthropic=${!!config.anthropicApiKey}, Copilot=${!!config.copilotGithubToken}`); } for (const warning of apiProxyValidation.warnings) { @@ -1051,7 +1051,7 @@ program // to prevent sensitive data from flowing to logger (CodeQL sensitive data logging) const redactedConfig: Record = {}; for (const [key, value] of Object.entries(config)) { - if (key === 'openaiApiKey' || key === 'anthropicApiKey' || key === 'copilotApiKey') continue; + if (key === 'openaiApiKey' || key === 'anthropicApiKey' || key === 'copilotGithubToken') continue; redactedConfig[key] = key === 'agentCommand' ? redactSecrets(value as string) : value; } logger.debug('Configuration:', JSON.stringify(redactedConfig, null, 2)); diff --git a/src/docker-manager.ts b/src/docker-manager.ts index 0c75bd83..8705e707 100644 --- a/src/docker-manager.ts +++ b/src/docker-manager.ts @@ -334,7 +334,7 @@ export function generateDockerCompose( EXCLUDED_ENV_VARS.add('CODEX_API_KEY'); EXCLUDED_ENV_VARS.add('ANTHROPIC_API_KEY'); EXCLUDED_ENV_VARS.add('CLAUDE_API_KEY'); - EXCLUDED_ENV_VARS.add('COPILOT_API_KEY'); + // COPILOT_GITHUB_TOKEN gets a placeholder (not excluded), protected by one-shot-token } // Start with required/overridden environment variables @@ -349,12 +349,12 @@ export function generateDockerCompose( PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', // Configure one-shot-token library with sensitive tokens to protect // These tokens are cached on first access and unset from /proc/self/environ - AWF_ONE_SHOT_TOKENS: 'COPILOT_GITHUB_TOKEN,GITHUB_TOKEN,GH_TOKEN,GITHUB_API_TOKEN,GITHUB_PAT,GH_ACCESS_TOKEN,OPENAI_API_KEY,OPENAI_KEY,ANTHROPIC_API_KEY,CLAUDE_API_KEY,CODEX_API_KEY,COPILOT_API_KEY', + AWF_ONE_SHOT_TOKENS: 'COPILOT_GITHUB_TOKEN,GITHUB_TOKEN,GH_TOKEN,GITHUB_API_TOKEN,GITHUB_PAT,GH_ACCESS_TOKEN,OPENAI_API_KEY,OPENAI_KEY,ANTHROPIC_API_KEY,CLAUDE_API_KEY,CODEX_API_KEY', }; // When api-proxy is enabled with Copilot, set placeholder tokens early // so --env-all won't override them with real values from host environment - if (config.enableApiProxy && config.copilotApiKey) { + if (config.enableApiProxy && config.copilotGithubToken) { environment.COPILOT_GITHUB_TOKEN = 'placeholder-token-for-credential-isolation'; logger.debug('COPILOT_GITHUB_TOKEN set to placeholder value (early) to prevent --env-all override'); } @@ -432,7 +432,7 @@ export function generateDockerCompose( if (process.env.OPENAI_API_KEY && !config.enableApiProxy) environment.OPENAI_API_KEY = process.env.OPENAI_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.COPILOT_API_KEY && !config.enableApiProxy) environment.COPILOT_API_KEY = process.env.COPILOT_API_KEY; + // COPILOT_GITHUB_TOKEN is handled separately - gets placeholder when api-proxy enabled if (process.env.USER) environment.USER = process.env.USER; if (process.env.TERM) environment.TERM = process.env.TERM; if (process.env.XDG_CONFIG_HOME) environment.XDG_CONFIG_HOME = process.env.XDG_CONFIG_HOME; @@ -962,7 +962,7 @@ export function generateDockerCompose( // Pass API keys securely to sidecar (not visible to agent) ...(config.openaiApiKey && { OPENAI_API_KEY: config.openaiApiKey }), ...(config.anthropicApiKey && { ANTHROPIC_API_KEY: config.anthropicApiKey }), - ...(config.copilotApiKey && { COPILOT_API_KEY: config.copilotApiKey }), + ...(config.copilotGithubToken && { COPILOT_GITHUB_TOKEN: config.copilotGithubToken }), // Route through Squid to respect domain whitelisting HTTP_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`, HTTPS_PROXY: `http://${networkConfig.squidIp}:${SQUID_PORT}`, @@ -1026,7 +1026,7 @@ export function generateDockerCompose( 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'); } - if (config.copilotApiKey) { + if (config.copilotGithubToken) { environment.COPILOT_API_URL = `http://${networkConfig.proxyIp}:10002`; logger.debug(`GitHub Copilot API will be proxied through sidecar at http://${networkConfig.proxyIp}:10002`); diff --git a/src/types.ts b/src/types.ts index 0d4d344f..affd0a42 100644 --- a/src/types.ts +++ b/src/types.ts @@ -399,13 +399,13 @@ export interface WrapperConfig { * variables are set in the agent container: * - OPENAI_BASE_URL=http://api-proxy:10000/v1 (set when OPENAI_API_KEY is provided) * - ANTHROPIC_BASE_URL=http://api-proxy:10001 (set when ANTHROPIC_API_KEY is provided) - * - COPILOT_API_URL=http://api-proxy:10002 (set when COPILOT_API_KEY is provided) + * - COPILOT_API_URL=http://api-proxy:10002 (set when COPILOT_GITHUB_TOKEN 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 * - ANTHROPIC_API_KEY - Optional Anthropic API key for Claude - * - COPILOT_API_KEY - Optional GitHub Copilot API key + * - COPILOT_GITHUB_TOKEN - Optional GitHub token for Copilot * * @default false * @example @@ -413,7 +413,7 @@ export interface WrapperConfig { * # Enable API proxy with keys from environment * export OPENAI_API_KEY="sk-..." * export ANTHROPIC_API_KEY="sk-ant-..." - * export COPILOT_API_KEY="ghu_..." + * export COPILOT_GITHUB_TOKEN="ghp_..." * awf --enable-api-proxy --allow-domains api.openai.com,api.anthropic.com,api.githubcopilot.com -- command * ``` */ @@ -444,16 +444,17 @@ export interface WrapperConfig { anthropicApiKey?: string; /** - * GitHub Copilot API key (used by API proxy sidecar) + * GitHub token for Copilot (used by API proxy sidecar) * - * When enableApiProxy is true, this key is injected into the Node.js sidecar + * When enableApiProxy is true, this token is injected into the Node.js sidecar * container and used to authenticate requests to api.githubcopilot.com. * - * The key is NOT exposed to the agent container - only the proxy URL is provided. + * The token is NOT exposed to the agent container - only the proxy URL is provided. + * The agent receives a placeholder value that is protected by the one-shot-token library. * * @default undefined */ - copilotApiKey?: string; + copilotGithubToken?: string; } /** diff --git a/tests/integration/api-proxy.test.ts b/tests/integration/api-proxy.test.ts index f0c2bcfa..a231a7f5 100644 --- a/tests/integration/api-proxy.test.ts +++ b/tests/integration/api-proxy.test.ts @@ -183,7 +183,7 @@ describe('API Proxy Sidecar', () => { logLevel: 'debug', timeout: 120000, env: { - COPILOT_API_KEY: 'ghu_fake-test-key-12345', + COPILOT_GITHUB_TOKEN: 'ghp_fake-test-token-12345', }, } ); @@ -193,7 +193,7 @@ describe('API Proxy Sidecar', () => { expect(result.stdout).toContain('copilot-proxy'); }, 180000); - test('should set COPILOT_API_URL in agent when Copilot key is provided', async () => { + test('should set COPILOT_API_URL in agent when Copilot token is provided', async () => { const result = await runner.runWithSudo( 'bash -c "echo COPILOT_API_URL=$COPILOT_API_URL"', { @@ -203,7 +203,7 @@ describe('API Proxy Sidecar', () => { logLevel: 'debug', timeout: 120000, env: { - COPILOT_API_KEY: 'ghu_fake-test-key-12345', + COPILOT_GITHUB_TOKEN: 'ghp_fake-test-token-12345', }, } ); @@ -212,7 +212,7 @@ describe('API Proxy Sidecar', () => { expect(result.stdout).toContain(`COPILOT_API_URL=http://${API_PROXY_IP}:10002`); }, 180000); - test('should set COPILOT_TOKEN to placeholder in agent when Copilot key is provided', async () => { + test('should set COPILOT_TOKEN to placeholder in agent when Copilot token is provided', async () => { const result = await runner.runWithSudo( 'bash -c "echo COPILOT_TOKEN=$COPILOT_TOKEN"', { @@ -222,7 +222,7 @@ describe('API Proxy Sidecar', () => { logLevel: 'debug', timeout: 120000, env: { - COPILOT_API_KEY: 'ghu_fake-test-key-12345', + COPILOT_GITHUB_TOKEN: 'ghp_fake-test-token-12345', }, } ); @@ -231,8 +231,8 @@ describe('API Proxy Sidecar', () => { expect(result.stdout).toContain('COPILOT_TOKEN=placeholder-token-for-credential-isolation'); }, 180000); - test('should report copilot in health providers when Copilot key is provided', async () => { - // When Copilot key is provided, the main health endpoint should report copilot: true + test('should report copilot in health providers when Copilot token is provided', async () => { + // When Copilot token is provided, the main health endpoint should report copilot: true const result = await runner.runWithSudo( `curl -s http://${API_PROXY_IP}:10000/health`, { @@ -242,7 +242,7 @@ describe('API Proxy Sidecar', () => { logLevel: 'debug', timeout: 120000, env: { - COPILOT_API_KEY: 'ghu_fake-test-key-12345', + COPILOT_GITHUB_TOKEN: 'ghp_fake-test-token-12345', }, } );