Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
24 changes: 21 additions & 3 deletions containers/api-proxy/copilot-adapter-enterprise.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const { createCopilotAdapter } = require('./providers/copilot');

const bearerByokKey = ['Bearer', 'sk-byok-key'].join(' ');
const bearerStandardToken = ['Bearer', 'ghu_standard_token_123'].join(' ');
const bearerGhecToken = ['Bearer', 'ghu_ghec_token_123'].join(' ');
const bearerCustomToken = ['Bearer', 'ghu_standard_token_123'].join(' ');
const bearerGithubComOverrideToken = ['Bearer', 'ghu_token_123'].join(' ');

Expand Down Expand Up @@ -56,13 +55,32 @@ describe('createCopilotAdapter — GHE enterprise auth format', () => {
expect(headers['Authorization']).toBe(bearerStandardToken);
});

it('uses "Bearer" prefix for GHEC tenant (*.ghe.com)', () => {
it('uses "token" prefix for a derived GHEC data-residency target', () => {
const adapter = createCopilotAdapter({
COPILOT_GITHUB_TOKEN: 'ghu_ghec_token_123',
GITHUB_SERVER_URL: 'https://mycompany.ghe.com',
});
const headers = adapter.getAuthHeaders(fakeReq);
expect(headers['Authorization']).toBe(bearerGhecToken);
expect(headers['Authorization']).toBe('token ghu_ghec_token_123');
});
Comment on lines +59 to +66

it('uses "token" prefix for /models on a derived GHEC data-residency target', () => {
const adapter = createCopilotAdapter({
COPILOT_GITHUB_TOKEN: 'ghu_ghec_token_123',
GITHUB_SERVER_URL: 'https://mycompany.ghe.com',
});
const headers = adapter.getAuthHeaders(fakeModelsReq);
expect(headers['Authorization']).toBe('token ghu_ghec_token_123');
});

it('uses "token" prefix for a GHEC target when AWF_PLATFORM_TYPE=ghec', () => {
const adapter = createCopilotAdapter({
COPILOT_GITHUB_TOKEN: 'ghu_ghec_token_123',
AWF_PLATFORM_TYPE: 'ghec',
GITHUB_SERVER_URL: 'https://mycompany.ghe.com',
});
const headers = adapter.getAuthHeaders(fakeReq);
expect(headers['Authorization']).toBe('token ghu_ghec_token_123');
});

it('strips "token " prefix from COPILOT_GITHUB_TOKEN before re-prefixing for GHES', () => {
Expand Down
11 changes: 9 additions & 2 deletions containers/api-proxy/copilot-auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,17 @@ describe('copilotTargetRequiresGitHubTokenPrefix', () => {
expect(copilotTargetRequiresGitHubTokenPrefix('api.githubcopilot.com', {})).toBe(false);
});

it('returns false for a *.ghe.com (GHEC) Copilot target', () => {
it('returns true for a GHEC data-residency Copilot target', () => {
expect(copilotTargetRequiresGitHubTokenPrefix('copilot-api.myorg.ghe.com', {
GITHUB_SERVER_URL: 'https://myorg.ghe.com',
})).toBe(false);
})).toBe(true);
});

it('returns true for a GHEC data-residency Copilot target with AWF_PLATFORM_TYPE=ghec', () => {
expect(copilotTargetRequiresGitHubTokenPrefix('copilot-api.myorg.ghe.com', {
AWF_PLATFORM_TYPE: 'ghec',
GITHUB_SERVER_URL: 'https://myorg.ghe.com',
})).toBe(true);
});
Comment on lines +312 to 323

it('returns false when no token-prefix indicators are present', () => {
Expand Down
30 changes: 18 additions & 12 deletions containers/api-proxy/providers/copilot-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,27 +235,31 @@ function isGhesInstance(resolvedTarget, env = process.env) {
* directly and therefore require the `token <value>` Authorization prefix
* (rather than `Bearer <value>`) for GitHub credentials.
*
* Both the Enterprise and Business endpoints behave this way; the standard
* `api.githubcopilot.com` endpoint instead expects a Copilot token with the
* `Bearer` prefix.
* Enterprise, Business, and GHEC data-residency endpoints behave this way; the
* standard `api.githubcopilot.com` endpoint instead expects a Copilot token
* with the `Bearer` prefix.
*/
const GITHUB_TOKEN_PREFIX_COPILOT_TARGETS = new Set([
'api.enterprise.githubcopilot.com',
'api.business.githubcopilot.com',
]);

function isGhecCopilotApiTarget(target) {
return target.startsWith('copilot-api.')
&& target.endsWith('.ghe.com')
&& target.length > 'copilot-api..ghe.com'.length;
}
Comment on lines +247 to +249

/**
* Decide whether a GitHub OAuth/PAT token sent to the Copilot API must use the
* `token <value>` Authorization prefix instead of `Bearer <value>`.
*
* This is true when either:
* 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.
* authenticates the GitHub token directly — i.e. the Enterprise, Business,
* or GHEC data-residency host. This check takes highest priority and is NOT
* overridable by AWF_PLATFORM_TYPE. Without this ordering, an explicit
* AWF_PLATFORM_TYPE=ghec would suppress the required `token` prefix.
* 2. The environment is a GHES instance (see {@link isGhesInstance}).
*
* An explicit non-GHES AWF_PLATFORM_TYPE overrides the GHES heuristics (case 2)
Expand All @@ -272,10 +276,12 @@ function copilotTargetRequiresGitHubTokenPrefix(resolvedTarget, env = process.en
// 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.
// does not suppress the required 'token' prefix.
const target = normalizeApiTarget(resolvedTarget);
if (target && GITHUB_TOKEN_PREFIX_COPILOT_TARGETS.has(target)) return true;
if (target && (
GITHUB_TOKEN_PREFIX_COPILOT_TARGETS.has(target)
|| isGhecCopilotApiTarget(target)
)) return true;

// An explicit non-GHES platform type overrides the GHES heuristics below
// for custom/unknown targets but never overrides catalog endpoints (above).
Expand Down
4 changes: 2 additions & 2 deletions containers/api-proxy/server.auth-matrix.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,13 @@ describe('Auth Matrix — Copilot', () => {
});

describe('GitHub OAuth token — GHEC (*.ghe.com)', () => {
it('sends Authorization: Bearer (not token) for GHEC', () => {
it('sends Authorization: token for GHEC data-residency targets', () => {
const adapter = createCopilotAdapter({
COPILOT_GITHUB_TOKEN: 'ghu_ghec_token',
GITHUB_SERVER_URL: 'https://mycompany.ghe.com',
});
const headers = adapter.getAuthHeaders(fakeReq());
expect(headers.Authorization).toBe('Bearer ghu_ghec_token');
expect(headers.Authorization).toBe('token ghu_ghec_token');
});

it('derives correct copilot-api target for GHEC', () => {
Expand Down