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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions containers/api-proxy/copilot-adapter-enterprise.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
11 changes: 11 additions & 0 deletions containers/api-proxy/copilot-auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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();
});
Expand Down
10 changes: 8 additions & 2 deletions containers/api-proxy/providers/copilot-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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-
Expand All @@ -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;
}

/**
Expand Down
12 changes: 6 additions & 6 deletions containers/api-proxy/server.auth-matrix.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});

Expand Down
Loading