From da7409e666db33fbcda5e3f87dce10524ba22a61 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:33:06 +0000 Subject: [PATCH 1/2] Initial plan From 3a4d0ad4829978b4e7b8a226aac77a00f0bf6f2d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:50:26 +0000 Subject: [PATCH 2/2] fix: catalog endpoints always use 'token', not overridable by platform --- .../copilot-adapter-enterprise.test.js | 34 ++++++++++++++++++- containers/api-proxy/copilot-auth.test.js | 28 +++++++++++++-- .../api-proxy/providers/copilot-auth.js | 34 ++++++++++++------- 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/containers/api-proxy/copilot-adapter-enterprise.test.js b/containers/api-proxy/copilot-adapter-enterprise.test.js index 6945484df..d31dbf537 100644 --- a/containers/api-proxy/copilot-adapter-enterprise.test.js +++ b/containers/api-proxy/copilot-adapter-enterprise.test.js @@ -115,13 +115,30 @@ describe('createCopilotAdapter — GHE enterprise auth format', () => { expect(headers['Authorization']).toBe('token ghu_enterprise_token_123'); }); - it('uses "Bearer" when AWF_PLATFORM_TYPE=github.com overrides GHES-looking GITHUB_SERVER_URL', () => { + it('uses "token" when auto-derived target is api.enterprise even with AWF_PLATFORM_TYPE=github.com', () => { + // With GITHUB_SERVER_URL pointing to a non-ghe.com host, deriveCopilotApiTarget + // auto-derives to api.enterprise.githubcopilot.com — a catalog endpoint that + // always requires the 'token' prefix. AWF_PLATFORM_TYPE cannot override catalog + // endpoints (it only overrides the GHES heuristic for custom/unknown targets). const adapter = createCopilotAdapter({ COPILOT_GITHUB_TOKEN: 'ghu_token_123', AWF_PLATFORM_TYPE: 'github.com', GITHUB_SERVER_URL: 'https://ghes.mycompany.com', }); const headers = adapter.getAuthHeaders(fakeReq); + expect(headers['Authorization']).toBe('token ghu_token_123'); + }); + + it('uses "Bearer" when AWF_PLATFORM_TYPE=github.com overrides GHES heuristic for a custom proxy', () => { + // AWF_PLATFORM_TYPE=github.com does suppress the 'token' prefix for + // custom/unknown targets (not catalog endpoints), e.g. a custom proxy host. + const adapter = createCopilotAdapter({ + COPILOT_GITHUB_TOKEN: 'ghu_token_123', + COPILOT_API_TARGET: 'custom-proxy.internal', + AWF_PLATFORM_TYPE: 'github.com', + GITHUB_SERVER_URL: 'https://ghes.mycompany.com', + }); + const headers = adapter.getAuthHeaders(fakeReq); expect(headers['Authorization']).toBe(bearerGithubComOverrideToken); }); }); @@ -182,6 +199,21 @@ describe('createCopilotAdapter — Copilot Business auth format', () => { expect(headers['Authorization']).toBe('token ghu_business_token_123'); expect(headers['Authorization']).not.toContain('token token'); }); + + it('uses "token" prefix for Business target when AWF_PLATFORM_TYPE=ghec is set (regression #5871)', () => { + // Regression: gh-aw automatically sets AWF_PLATFORM_TYPE=ghec on *.ghe.com + // runners. Previously this caused the 'token' prefix to be suppressed for + // COPILOT_API_TARGET=api.business.githubcopilot.com, yielding + // "400 bad request: Authorization header is badly formatted". + const adapter = createCopilotAdapter({ + COPILOT_GITHUB_TOKEN: 'ghu_business_token_123', + 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 8f2c4c790..0eda4898f 100644 --- a/containers/api-proxy/copilot-auth.test.js +++ b/containers/api-proxy/copilot-auth.test.js @@ -235,11 +235,33 @@ describe('copilotTargetRequiresGitHubTokenPrefix', () => { })).toBe(true); }); - it('returns false when an explicit non-GHES AWF_PLATFORM_TYPE overrides the Business host', () => { - // The explicit platform override is the documented escape hatch and wins - // even for the Business host, matching isGhesInstance semantics. + it('returns true for the Business host even when AWF_PLATFORM_TYPE=ghec is set', () => { + // Regression: gh-aw automatically sets AWF_PLATFORM_TYPE=ghec on *.ghe.com + // (GHEC) runners. The catalog endpoint check must take priority over the + // platform-type guard so that Copilot Business customers who set + // COPILOT_API_TARGET=api.business.githubcopilot.com still receive the + // required 'token' prefix and not 'Bearer'. See github/gh-aw-firewall#5871. expect(copilotTargetRequiresGitHubTokenPrefix('api.business.githubcopilot.com', { AWF_PLATFORM_TYPE: 'ghec', + })).toBe(true); + }); + + it('returns true for Business host with AWF_PLATFORM_TYPE=ghec and *.ghe.com server (full production scenario)', () => { + // Full production scenario from issue #5871: Copilot Business on GHEC. + // COPILOT_API_TARGET=api.business.githubcopilot.com, GITHUB_SERVER_URL=*.ghe.com, + // AWF_PLATFORM_TYPE=ghec (auto-injected by gh-aw on GHEC runners). + expect(copilotTargetRequiresGitHubTokenPrefix('api.business.githubcopilot.com', { + AWF_PLATFORM_TYPE: 'ghec', + GITHUB_SERVER_URL: 'https://myorg.ghe.com', + })).toBe(true); + }); + + it('returns false when AWF_PLATFORM_TYPE=github.com for a non-catalog custom target', () => { + // The platform override should still suppress the GHES heuristic for + // custom/unknown proxy targets (only catalog endpoints are unaffected). + expect(copilotTargetRequiresGitHubTokenPrefix('custom-proxy.internal', { + AWF_PLATFORM_TYPE: 'github.com', + GITHUB_SERVER_URL: 'https://ghes.mycompany.com', })).toBe(false); }); diff --git a/containers/api-proxy/providers/copilot-auth.js b/containers/api-proxy/providers/copilot-auth.js index eb8aaf688..c13e3429b 100644 --- a/containers/api-proxy/providers/copilot-auth.js +++ b/containers/api-proxy/providers/copilot-auth.js @@ -252,15 +252,17 @@ const GITHUB_TOKEN_PREFIX_COPILOT_TARGETS = new Set([ * `token ` Authorization prefix instead of `Bearer `. * * This is true when either: - * 1. The environment is a GHES instance (see {@link isGhesInstance}), or - * 2. The resolved target is a GitHub-hosted Copilot endpoint that accepts the - * GitHub token directly — i.e. the Enterprise or Business host. This case - * covers Copilot Business customers who set - * COPILOT_API_TARGET=api.business.githubcopilot.com while running against a - * *.ghe.com (GHEC) server, which the GHES heuristics would otherwise miss. + * 1. The resolved target is a known GitHub-hosted Copilot endpoint that + * authenticates the GitHub token directly — i.e. the Enterprise or Business + * host. This check takes highest priority and is NOT overridable by + * AWF_PLATFORM_TYPE. Without this ordering, gh-aw's automatic + * AWF_PLATFORM_TYPE=ghec injection on *.ghe.com runners would suppress the + * `token` prefix for Copilot Business customers who set + * COPILOT_API_TARGET=api.business.githubcopilot.com. + * 2. The environment is a GHES instance (see {@link isGhesInstance}). * - * An explicit non-GHES AWF_PLATFORM_TYPE remains an override that forces - * 'Bearer', consistent with {@link isGhesInstance}. + * An explicit non-GHES AWF_PLATFORM_TYPE overrides the GHES heuristics (case 2) + * but never overrides known catalog endpoints (case 1). * * BYOK API keys always use `Bearer`; this predicate only governs the GitHub * token case (callers gate on the absence of a real BYOK key). @@ -270,14 +272,20 @@ const GITHUB_TOKEN_PREFIX_COPILOT_TARGETS = new Set([ * @returns {boolean} */ function copilotTargetRequiresGitHubTokenPrefix(resolvedTarget, env = process.env) { - // An explicit non-GHES platform type is an intentional override that forces - // the standard 'Bearer' prefix, even when the resolved host looks like an - // Enterprise/Business endpoint. (isGhesInstance honors the same override.) + // Known GitHub-hosted Copilot endpoints always require the 'token' prefix for + // GitHub OAuth/PAT credentials, regardless of platform type. This check must + // come before the AWF_PLATFORM_TYPE guard so that an explicit platform type + // (e.g. AWF_PLATFORM_TYPE=ghec set by gh-aw on *.ghe.com runners) does not + // suppress the required 'token' prefix for Business/Enterprise endpoints. + const target = normalizeApiTarget(resolvedTarget); + if (target && GITHUB_TOKEN_PREFIX_COPILOT_TARGETS.has(target)) return true; + + // An explicit non-GHES platform type overrides the GHES heuristics below + // for custom/unknown targets but never overrides catalog endpoints (above). if (env.AWF_PLATFORM_TYPE && env.AWF_PLATFORM_TYPE !== 'ghes') return false; if (isGhesInstance(resolvedTarget, env)) return true; - const target = normalizeApiTarget(resolvedTarget); - return target ? GITHUB_TOKEN_PREFIX_COPILOT_TARGETS.has(target) : false; + return false; } module.exports = {