From 54dfd80e301fdb57ef3b71ccfd5863191c865110 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:36:09 +0000 Subject: [PATCH 1/2] Initial plan From b59653ba7ab77e9d381f4ecb66a51be00e3d480b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:43:41 +0000 Subject: [PATCH 2/2] fix: ignore gh-aw offline dummy BYOK key for Copilot auth selection --- .../api-proxy/copilot-adapter-enterprise.test.js | 12 ++++++++++++ containers/api-proxy/copilot-auth.test.js | 11 +++++++++++ containers/api-proxy/providers/copilot-auth.js | 10 ++++++++-- containers/api-proxy/server.auth-matrix.test.js | 12 ++++++------ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/containers/api-proxy/copilot-adapter-enterprise.test.js b/containers/api-proxy/copilot-adapter-enterprise.test.js index d31dbf537..f29d553c4 100644 --- a/containers/api-proxy/copilot-adapter-enterprise.test.js +++ b/containers/api-proxy/copilot-adapter-enterprise.test.js @@ -214,6 +214,18 @@ describe('createCopilotAdapter — Copilot Business auth format', () => { const headers = adapter.getAuthHeaders(fakeReq); expect(headers['Authorization']).toBe('token ghu_business_token_123'); }); + + it('ignores offline-mode dummy BYOK sentinel and still uses GitHub token format on Business target', () => { + const adapter = createCopilotAdapter({ + COPILOT_GITHUB_TOKEN: 'ghu_business_token_123', + COPILOT_PROVIDER_API_KEY: 'dummy-byok-key-for-offline-mode', + COPILOT_API_TARGET: 'api.business.githubcopilot.com', + AWF_PLATFORM_TYPE: 'ghec', + GITHUB_SERVER_URL: 'https://myorg.ghe.com', + }); + const headers = adapter.getAuthHeaders(fakeReq); + expect(headers['Authorization']).toBe('token ghu_business_token_123'); + }); }); describe('createCopilotAdapter — Azure OIDC (Entra) getAuthHeaders', () => { diff --git a/containers/api-proxy/copilot-auth.test.js b/containers/api-proxy/copilot-auth.test.js index 0eda4898f..388084c0a 100644 --- a/containers/api-proxy/copilot-auth.test.js +++ b/containers/api-proxy/copilot-auth.test.js @@ -137,6 +137,13 @@ describe('resolveCopilotAuthToken', () => { COPILOT_PROVIDER_API_KEY: COPILOT_PLACEHOLDER_TOKEN, })).toBe('gho_real_token'); }); + + it('uses COPILOT_GITHUB_TOKEN when COPILOT_PROVIDER_API_KEY is the offline-mode dummy sentinel', () => { + expect(resolveCopilotAuthToken({ + COPILOT_GITHUB_TOKEN: 'gho_real_token', + COPILOT_PROVIDER_API_KEY: 'dummy-byok-key-for-offline-mode', + })).toBe('gho_real_token'); + }); }); describe('resolveApiKey', () => { @@ -148,6 +155,10 @@ describe('resolveApiKey', () => { expect(resolveApiKey({ COPILOT_PROVIDER_API_KEY: COPILOT_PLACEHOLDER_TOKEN })).toBeUndefined(); }); + it('returns undefined when COPILOT_PROVIDER_API_KEY is the offline-mode dummy sentinel', () => { + expect(resolveApiKey({ COPILOT_PROVIDER_API_KEY: 'dummy-byok-key-for-offline-mode' })).toBeUndefined(); + }); + it('returns undefined when COPILOT_PROVIDER_API_KEY is not set', () => { expect(resolveApiKey({})).toBeUndefined(); }); diff --git a/containers/api-proxy/providers/copilot-auth.js b/containers/api-proxy/providers/copilot-auth.js index c13e3429b..7e32d51fc 100644 --- a/containers/api-proxy/providers/copilot-auth.js +++ b/containers/api-proxy/providers/copilot-auth.js @@ -4,6 +4,8 @@ const { normalizeApiTarget } = require('../proxy-utils'); const { COPILOT_PLACEHOLDER_TOKEN } = require('./copilot-byok'); const { URL } = require('url'); +const COPILOT_DUMMY_BYOK_OFFLINE_TOKEN = 'dummy-byok-key-for-offline-mode'; + /** * Strip any accidental "Bearer " or "token " prefix from a raw credential * value and trim @@ -22,10 +24,12 @@ function stripBearerPrefix(value) { /** * Returns the COPILOT_PROVIDER_API_KEY value from env if it is a real BYOK credential, - * or undefined in two cases: + * or undefined in three cases: * 1. COPILOT_PROVIDER_API_KEY is not set (or is empty/whitespace-only). * 2. COPILOT_PROVIDER_API_KEY equals the known AWF placeholder sentinel — it was injected * by AWF for credential isolation and is not a usable BYOK credential. + * 3. COPILOT_PROVIDER_API_KEY equals gh-aw's offline-mode dummy BYOK sentinel + * (`dummy-byok-key-for-offline-mode`) and should not suppress COPILOT_GITHUB_TOKEN. * * The case-(2) placeholder check is defense-in-depth: in AWF's normal flow the placeholder * is never written into the sidecar's own COPILOT_PROVIDER_API_KEY (src/services/api-proxy- @@ -40,7 +44,9 @@ function stripBearerPrefix(value) { */ function resolveApiKey(env) { const key = stripBearerPrefix(env.COPILOT_PROVIDER_API_KEY); - return key === COPILOT_PLACEHOLDER_TOKEN ? undefined : key; + return (key === COPILOT_PLACEHOLDER_TOKEN || key === COPILOT_DUMMY_BYOK_OFFLINE_TOKEN) + ? undefined + : key; } /** diff --git a/containers/api-proxy/server.auth-matrix.test.js b/containers/api-proxy/server.auth-matrix.test.js index 7efdfdd80..0a20c12a5 100644 --- a/containers/api-proxy/server.auth-matrix.test.js +++ b/containers/api-proxy/server.auth-matrix.test.js @@ -573,17 +573,17 @@ describe('Auth Matrix — Credential Isolation', () => { expect(headers.Authorization).toBe('Bearer sk-real-byok-key'); }); - it('Copilot dummy BYOK key is used as-is at the adapter level', () => { - // The 'dummy-byok-key-for-offline-mode' string has no special handling - // in the adapter — it's treated as a regular BYOK key. The Copilot CLI - // auth layer (copilot-auth.js) is where placeholder detection occurs. + it('Copilot offline-mode dummy BYOK key is treated as absent', () => { + // gh-aw injects this sentinel for Copilot CLI offline mode. The proxy + // should ignore it and continue using COPILOT_GITHUB_TOKEN. const adapter = createCopilotAdapter({ COPILOT_GITHUB_TOKEN: 'ghu_real_token', COPILOT_PROVIDER_API_KEY: 'dummy-byok-key-for-offline-mode', }); const headers = adapter.getAuthHeaders(fakeReq()); - // BYOK key is present so it's used for inference - expect(headers.Authorization).toBe('Bearer dummy-byok-key-for-offline-mode'); + expect(headers.Authorization).toMatch(/^Bearer\s+/); + expect(headers.Authorization).toContain('ghu_real_token'); + expect(headers.Authorization).not.toContain('dummy-byok-key-for-offline-mode'); }); });