Skip to content

Commit b705505

Browse files
authored
fix(acp/auth): prevent conflicting credentials on enterprise gateways and support optional API keys natively (google-gemini#27021)
1 parent 488d71b commit b705505

3 files changed

Lines changed: 113 additions & 10 deletions

File tree

packages/cli/src/acp/acpSessionManager.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ export class AcpSessionManager {
6969
);
7070

7171
const authType =
72-
loadedSettings.merged.security.auth.selectedType || AuthType.USE_GEMINI;
72+
loadedSettings.merged.security.auth.selectedType ||
73+
(authDetails.baseUrl || process.env['GOOGLE_GEMINI_BASE_URL']
74+
? AuthType.GATEWAY
75+
: AuthType.USE_GEMINI);
7376

7477
let isAuthenticated = false;
7578
let authErrorMessage = '';
@@ -231,7 +234,12 @@ export class AcpSessionManager {
231234
mcpServers: acp.McpServer[],
232235
authDetails: AuthDetails,
233236
): Promise<Config> {
234-
const selectedAuthType = this.settings.merged.security.auth.selectedType;
237+
const selectedAuthType =
238+
this.settings.merged.security.auth.selectedType ||
239+
(authDetails.baseUrl || process.env['GOOGLE_GEMINI_BASE_URL']
240+
? AuthType.GATEWAY
241+
: undefined);
242+
235243
if (!selectedAuthType) {
236244
throw acp.RequestError.authRequired();
237245
}

packages/core/src/core/contentGenerator.test.ts

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
createContentGenerator,
1010
AuthType,
1111
createContentGeneratorConfig,
12+
getAuthTypeFromEnv,
1213
type ContentGenerator,
1314
} from './contentGenerator.js';
1415
import { createCodeAssistContentGenerator } from '../code_assist/codeAssist.js';
@@ -35,6 +36,45 @@ const mockConfig = {
3536
getClientName: vi.fn().mockReturnValue(undefined),
3637
} as unknown as Config;
3738

39+
describe('getAuthTypeFromEnv', () => {
40+
beforeEach(() => {
41+
vi.stubEnv('GEMINI_API_KEY', '');
42+
});
43+
44+
afterEach(() => {
45+
vi.unstubAllEnvs();
46+
});
47+
48+
it('should detect LOGIN_WITH_GOOGLE when GOOGLE_GENAI_USE_GCA is true', () => {
49+
vi.stubEnv('GOOGLE_GENAI_USE_GCA', 'true');
50+
expect(getAuthTypeFromEnv()).toBe(AuthType.LOGIN_WITH_GOOGLE);
51+
});
52+
53+
it('should detect USE_VERTEX_AI when GOOGLE_GENAI_USE_VERTEXAI is true', () => {
54+
vi.stubEnv('GOOGLE_GENAI_USE_VERTEXAI', 'true');
55+
expect(getAuthTypeFromEnv()).toBe(AuthType.USE_VERTEX_AI);
56+
});
57+
58+
it('should detect GATEWAY when GOOGLE_GEMINI_BASE_URL is present', () => {
59+
vi.stubEnv('GOOGLE_GEMINI_BASE_URL', 'https://gateway.example.com');
60+
expect(getAuthTypeFromEnv()).toBe(AuthType.GATEWAY);
61+
});
62+
63+
it('should detect USE_GEMINI when GEMINI_API_KEY is present', () => {
64+
vi.stubEnv('GEMINI_API_KEY', 'fake-key');
65+
expect(getAuthTypeFromEnv()).toBe(AuthType.USE_GEMINI);
66+
});
67+
68+
it('should detect COMPUTE_ADC when CLOUD_SHELL is true', () => {
69+
vi.stubEnv('CLOUD_SHELL', 'true');
70+
expect(getAuthTypeFromEnv()).toBe(AuthType.COMPUTE_ADC);
71+
});
72+
73+
it('should return undefined when no matching env variables are set', () => {
74+
expect(getAuthTypeFromEnv()).toBeUndefined();
75+
});
76+
});
77+
3878
describe('createContentGenerator', () => {
3979
beforeEach(() => {
4080
resetVersionCache();
@@ -851,6 +891,40 @@ describe('createContentGenerator', () => {
851891
),
852892
).rejects.toThrow('Invalid custom base URL: not-a-url');
853893
});
894+
895+
it('should set empty x-goog-api-key header for GATEWAY auth when apiKey is empty string', async () => {
896+
const mockConfig = {
897+
getModel: vi.fn().mockReturnValue('gemini-pro'),
898+
getProxy: vi.fn().mockReturnValue(undefined),
899+
getUsageStatisticsEnabled: () => false,
900+
getClientName: vi.fn().mockReturnValue(undefined),
901+
} as unknown as Config;
902+
903+
const mockGenerator = {
904+
models: {},
905+
} as unknown as GoogleGenAI;
906+
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator as never);
907+
908+
await createContentGenerator(
909+
{
910+
apiKey: '',
911+
authType: AuthType.GATEWAY,
912+
baseUrl: 'https://gateway.test.local',
913+
},
914+
mockConfig,
915+
);
916+
917+
expect(GoogleGenAI).toHaveBeenCalledWith(
918+
expect.objectContaining({
919+
apiKey: '',
920+
httpOptions: expect.objectContaining({
921+
headers: expect.objectContaining({
922+
'x-goog-api-key': '',
923+
}),
924+
}),
925+
}),
926+
);
927+
});
854928
});
855929

856930
describe('createContentGeneratorConfig', () => {
@@ -955,24 +1029,33 @@ describe('createContentGeneratorConfig', () => {
9551029
expect(config.apiKey).toBeUndefined();
9561030
expect(config.vertexai).toBeUndefined();
9571031
});
958-
it('should configure for GATEWAY using dummy placeholder if GEMINI_API_KEY is set', async () => {
959-
vi.stubEnv('GEMINI_API_KEY', 'env-gemini-key');
1032+
it('should configure for GATEWAY using provided apiKey if available', async () => {
1033+
const config = await createContentGeneratorConfig(
1034+
mockConfig,
1035+
AuthType.GATEWAY,
1036+
'custom-gateway-key',
1037+
);
1038+
expect(config.apiKey).toBe('custom-gateway-key');
1039+
expect(config.vertexai).toBe(false);
1040+
});
1041+
1042+
it('should configure for GATEWAY using GEMINI_API_KEY from environment if set', async () => {
1043+
vi.stubEnv('GEMINI_API_KEY', 'env-gateway-key');
9601044
const config = await createContentGeneratorConfig(
9611045
mockConfig,
9621046
AuthType.GATEWAY,
9631047
);
964-
expect(config.apiKey).toBe('gateway-placeholder-key');
1048+
expect(config.apiKey).toBe('env-gateway-key');
9651049
expect(config.vertexai).toBe(false);
9661050
});
9671051

968-
it('should configure for GATEWAY using dummy placeholder if GEMINI_API_KEY is not set', async () => {
1052+
it('should configure for GATEWAY using empty string if no apiKey is provided', async () => {
9691053
vi.stubEnv('GEMINI_API_KEY', '');
970-
vi.mocked(loadApiKey).mockResolvedValue(null);
9711054
const config = await createContentGeneratorConfig(
9721055
mockConfig,
9731056
AuthType.GATEWAY,
9741057
);
975-
expect(config.apiKey).toBe('gateway-placeholder-key');
1058+
expect(config.apiKey).toBe('');
9761059
expect(config.vertexai).toBe(false);
9771060
});
9781061
});

packages/core/src/core/contentGenerator.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ export function getAuthTypeFromEnv(): AuthType | undefined {
8080
if (process.env['GOOGLE_GENAI_USE_VERTEXAI'] === 'true') {
8181
return AuthType.USE_VERTEX_AI;
8282
}
83+
if (process.env['GOOGLE_GEMINI_BASE_URL']) {
84+
return AuthType.GATEWAY;
85+
}
8386
if (process.env['GEMINI_API_KEY']) {
8487
return AuthType.USE_GEMINI;
8588
}
@@ -178,7 +181,8 @@ export async function createContentGeneratorConfig(
178181
}
179182

180183
if (authType === AuthType.GATEWAY) {
181-
contentGeneratorConfig.apiKey = apiKey || 'gateway-placeholder-key';
184+
contentGeneratorConfig.apiKey =
185+
apiKey || process.env['GEMINI_API_KEY'] || '';
182186
contentGeneratorConfig.vertexai = false;
183187

184188
return contentGeneratorConfig;
@@ -313,6 +317,9 @@ export async function createContentGenerator(
313317
'x-gemini-api-privileged-user-id': `${installationId}`,
314318
};
315319
}
320+
if (config.authType === AuthType.GATEWAY && config.apiKey === '') {
321+
headers['x-goog-api-key'] = '';
322+
}
316323
let baseUrl = config.baseUrl;
317324
if (!baseUrl) {
318325
const envBaseUrl =
@@ -337,7 +344,12 @@ export async function createContentGenerator(
337344
}
338345

339346
const googleGenAI = new GoogleGenAI({
340-
apiKey: config.apiKey === '' ? undefined : config.apiKey,
347+
apiKey:
348+
config.authType === AuthType.GATEWAY
349+
? config.apiKey
350+
: config.apiKey === ''
351+
? undefined
352+
: config.apiKey,
341353
vertexai: config.vertexai ?? config.authType === AuthType.USE_VERTEX_AI,
342354
httpOptions,
343355
...(apiVersionEnv && { apiVersion: apiVersionEnv }),

0 commit comments

Comments
 (0)