fix(acp/auth): prevent conflicting credentials on enterprise gateways and support optional API keys natively (#27021)

This commit is contained in:
Sri Pasumarthi
2026-05-14 08:38:01 -07:00
committed by GitHub
parent 488d71b8c9
commit b705505dae
3 changed files with 114 additions and 11 deletions
+10 -2
View File
@@ -69,7 +69,10 @@ export class AcpSessionManager {
);
const authType =
loadedSettings.merged.security.auth.selectedType || AuthType.USE_GEMINI;
loadedSettings.merged.security.auth.selectedType ||
(authDetails.baseUrl || process.env['GOOGLE_GEMINI_BASE_URL']
? AuthType.GATEWAY
: AuthType.USE_GEMINI);
let isAuthenticated = false;
let authErrorMessage = '';
@@ -231,7 +234,12 @@ export class AcpSessionManager {
mcpServers: acp.McpServer[],
authDetails: AuthDetails,
): Promise<Config> {
const selectedAuthType = this.settings.merged.security.auth.selectedType;
const selectedAuthType =
this.settings.merged.security.auth.selectedType ||
(authDetails.baseUrl || process.env['GOOGLE_GEMINI_BASE_URL']
? AuthType.GATEWAY
: undefined);
if (!selectedAuthType) {
throw acp.RequestError.authRequired();
}
@@ -9,6 +9,7 @@ import {
createContentGenerator,
AuthType,
createContentGeneratorConfig,
getAuthTypeFromEnv,
type ContentGenerator,
} from './contentGenerator.js';
import { createCodeAssistContentGenerator } from '../code_assist/codeAssist.js';
@@ -35,6 +36,45 @@ const mockConfig = {
getClientName: vi.fn().mockReturnValue(undefined),
} as unknown as Config;
describe('getAuthTypeFromEnv', () => {
beforeEach(() => {
vi.stubEnv('GEMINI_API_KEY', '');
});
afterEach(() => {
vi.unstubAllEnvs();
});
it('should detect LOGIN_WITH_GOOGLE when GOOGLE_GENAI_USE_GCA is true', () => {
vi.stubEnv('GOOGLE_GENAI_USE_GCA', 'true');
expect(getAuthTypeFromEnv()).toBe(AuthType.LOGIN_WITH_GOOGLE);
});
it('should detect USE_VERTEX_AI when GOOGLE_GENAI_USE_VERTEXAI is true', () => {
vi.stubEnv('GOOGLE_GENAI_USE_VERTEXAI', 'true');
expect(getAuthTypeFromEnv()).toBe(AuthType.USE_VERTEX_AI);
});
it('should detect GATEWAY when GOOGLE_GEMINI_BASE_URL is present', () => {
vi.stubEnv('GOOGLE_GEMINI_BASE_URL', 'https://gateway.example.com');
expect(getAuthTypeFromEnv()).toBe(AuthType.GATEWAY);
});
it('should detect USE_GEMINI when GEMINI_API_KEY is present', () => {
vi.stubEnv('GEMINI_API_KEY', 'fake-key');
expect(getAuthTypeFromEnv()).toBe(AuthType.USE_GEMINI);
});
it('should detect COMPUTE_ADC when CLOUD_SHELL is true', () => {
vi.stubEnv('CLOUD_SHELL', 'true');
expect(getAuthTypeFromEnv()).toBe(AuthType.COMPUTE_ADC);
});
it('should return undefined when no matching env variables are set', () => {
expect(getAuthTypeFromEnv()).toBeUndefined();
});
});
describe('createContentGenerator', () => {
beforeEach(() => {
resetVersionCache();
@@ -851,6 +891,40 @@ describe('createContentGenerator', () => {
),
).rejects.toThrow('Invalid custom base URL: not-a-url');
});
it('should set empty x-goog-api-key header for GATEWAY auth when apiKey is empty string', async () => {
const mockConfig = {
getModel: vi.fn().mockReturnValue('gemini-pro'),
getProxy: vi.fn().mockReturnValue(undefined),
getUsageStatisticsEnabled: () => false,
getClientName: vi.fn().mockReturnValue(undefined),
} as unknown as Config;
const mockGenerator = {
models: {},
} as unknown as GoogleGenAI;
vi.mocked(GoogleGenAI).mockImplementation(() => mockGenerator as never);
await createContentGenerator(
{
apiKey: '',
authType: AuthType.GATEWAY,
baseUrl: 'https://gateway.test.local',
},
mockConfig,
);
expect(GoogleGenAI).toHaveBeenCalledWith(
expect.objectContaining({
apiKey: '',
httpOptions: expect.objectContaining({
headers: expect.objectContaining({
'x-goog-api-key': '',
}),
}),
}),
);
});
});
describe('createContentGeneratorConfig', () => {
@@ -955,24 +1029,33 @@ describe('createContentGeneratorConfig', () => {
expect(config.apiKey).toBeUndefined();
expect(config.vertexai).toBeUndefined();
});
it('should configure for GATEWAY using dummy placeholder if GEMINI_API_KEY is set', async () => {
vi.stubEnv('GEMINI_API_KEY', 'env-gemini-key');
it('should configure for GATEWAY using provided apiKey if available', async () => {
const config = await createContentGeneratorConfig(
mockConfig,
AuthType.GATEWAY,
'custom-gateway-key',
);
expect(config.apiKey).toBe('gateway-placeholder-key');
expect(config.apiKey).toBe('custom-gateway-key');
expect(config.vertexai).toBe(false);
});
it('should configure for GATEWAY using dummy placeholder if GEMINI_API_KEY is not set', async () => {
vi.stubEnv('GEMINI_API_KEY', '');
vi.mocked(loadApiKey).mockResolvedValue(null);
it('should configure for GATEWAY using GEMINI_API_KEY from environment if set', async () => {
vi.stubEnv('GEMINI_API_KEY', 'env-gateway-key');
const config = await createContentGeneratorConfig(
mockConfig,
AuthType.GATEWAY,
);
expect(config.apiKey).toBe('gateway-placeholder-key');
expect(config.apiKey).toBe('env-gateway-key');
expect(config.vertexai).toBe(false);
});
it('should configure for GATEWAY using empty string if no apiKey is provided', async () => {
vi.stubEnv('GEMINI_API_KEY', '');
const config = await createContentGeneratorConfig(
mockConfig,
AuthType.GATEWAY,
);
expect(config.apiKey).toBe('');
expect(config.vertexai).toBe(false);
});
});
+14 -2
View File
@@ -80,6 +80,9 @@ export function getAuthTypeFromEnv(): AuthType | undefined {
if (process.env['GOOGLE_GENAI_USE_VERTEXAI'] === 'true') {
return AuthType.USE_VERTEX_AI;
}
if (process.env['GOOGLE_GEMINI_BASE_URL']) {
return AuthType.GATEWAY;
}
if (process.env['GEMINI_API_KEY']) {
return AuthType.USE_GEMINI;
}
@@ -178,7 +181,8 @@ export async function createContentGeneratorConfig(
}
if (authType === AuthType.GATEWAY) {
contentGeneratorConfig.apiKey = apiKey || 'gateway-placeholder-key';
contentGeneratorConfig.apiKey =
apiKey || process.env['GEMINI_API_KEY'] || '';
contentGeneratorConfig.vertexai = false;
return contentGeneratorConfig;
@@ -313,6 +317,9 @@ export async function createContentGenerator(
'x-gemini-api-privileged-user-id': `${installationId}`,
};
}
if (config.authType === AuthType.GATEWAY && config.apiKey === '') {
headers['x-goog-api-key'] = '';
}
let baseUrl = config.baseUrl;
if (!baseUrl) {
const envBaseUrl =
@@ -337,7 +344,12 @@ export async function createContentGenerator(
}
const googleGenAI = new GoogleGenAI({
apiKey: config.apiKey === '' ? undefined : config.apiKey,
apiKey:
config.authType === AuthType.GATEWAY
? config.apiKey
: config.apiKey === ''
? undefined
: config.apiKey,
vertexai: config.vertexai ?? config.authType === AuthType.USE_VERTEX_AI,
httpOptions,
...(apiVersionEnv && { apiVersion: apiVersionEnv }),