mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
fix(cli): do not override GOOGLE_CLOUD_PROJECT in Cloud Shell when using Vertex AI (#24455)
Co-authored-by: David Pierce <davidapierce@google.com>
This commit is contained in:
@@ -82,6 +82,7 @@ import {
|
||||
FatalConfigError,
|
||||
GEMINI_DIR,
|
||||
Storage,
|
||||
AuthType,
|
||||
type MCPServerConfig,
|
||||
} from '@google/gemini-cli-core';
|
||||
import { updateSettingsFilePreservingFormat } from '../utils/commentJson.js';
|
||||
@@ -202,6 +203,7 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
describe('loadSettings', () => {
|
||||
@@ -3036,6 +3038,7 @@ describe('Settings Loading and Merging', () => {
|
||||
delete process.env['CLOUD_SHELL'];
|
||||
delete process.env['MALICIOUS_VAR'];
|
||||
delete process.env['FOO'];
|
||||
delete process.env['_GEMINI_USER_GCP_PROJECT'];
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
});
|
||||
@@ -3268,6 +3271,108 @@ MALICIOUS_VAR=allowed-because-trusted
|
||||
expect(process.env['GOOGLE_CLOUD_PROJECT']).toBe('cloudshell-gca');
|
||||
});
|
||||
|
||||
it('should not override GOOGLE_CLOUD_PROJECT in Cloud Shell when auth type is vertex-ai', () => {
|
||||
vi.stubEnv('CLOUD_SHELL', 'true');
|
||||
vi.stubEnv('GOOGLE_CLOUD_PROJECT', 'my-vertex-project');
|
||||
process.argv = ['node', 'gemini', '-s', 'prompt'];
|
||||
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||
isTrusted: false,
|
||||
source: 'file',
|
||||
});
|
||||
|
||||
// No .env file
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
loadEnvironment(
|
||||
createMockSettings({
|
||||
tools: { sandbox: false },
|
||||
security: { auth: { selectedType: AuthType.USE_VERTEX_AI } },
|
||||
}).merged,
|
||||
MOCK_WORKSPACE_DIR,
|
||||
);
|
||||
|
||||
expect(process.env['GOOGLE_CLOUD_PROJECT']).toBe('my-vertex-project');
|
||||
});
|
||||
|
||||
it('should clear cloudshell-gca when switching to Vertex AI without an original project', () => {
|
||||
process.env['CLOUD_SHELL'] = 'true';
|
||||
process.argv = ['node', 'gemini', '-s', 'prompt'];
|
||||
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||
isTrusted: false,
|
||||
source: 'file',
|
||||
});
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
// First call: normal Cloud Shell auth sets cloudshell-gca
|
||||
loadEnvironment(
|
||||
createMockSettings({ tools: { sandbox: false } }).merged,
|
||||
MOCK_WORKSPACE_DIR,
|
||||
);
|
||||
expect(process.env['GOOGLE_CLOUD_PROJECT']).toBe('cloudshell-gca');
|
||||
|
||||
// Second call: user switched to Vertex AI, should remove cloudshell-gca
|
||||
loadEnvironment(
|
||||
createMockSettings({
|
||||
tools: { sandbox: false },
|
||||
security: { auth: { selectedType: AuthType.USE_VERTEX_AI } },
|
||||
}).merged,
|
||||
MOCK_WORKSPACE_DIR,
|
||||
);
|
||||
expect(process.env['GOOGLE_CLOUD_PROJECT']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should restore original project when switching to Vertex AI after Cloud Shell override', () => {
|
||||
process.env['CLOUD_SHELL'] = 'true';
|
||||
process.env['GOOGLE_CLOUD_PROJECT'] = 'my-real-project';
|
||||
process.argv = ['node', 'gemini', '-s', 'prompt'];
|
||||
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||
isTrusted: false,
|
||||
source: 'file',
|
||||
});
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
// First call: saves original to _GEMINI_USER_GCP_PROJECT, sets cloudshell-gca
|
||||
loadEnvironment(
|
||||
createMockSettings({ tools: { sandbox: false } }).merged,
|
||||
MOCK_WORKSPACE_DIR,
|
||||
);
|
||||
expect(process.env['GOOGLE_CLOUD_PROJECT']).toBe('cloudshell-gca');
|
||||
expect(process.env['_GEMINI_USER_GCP_PROJECT']).toBe('my-real-project');
|
||||
|
||||
// Second call: switching to Vertex AI should restore the saved value
|
||||
loadEnvironment(
|
||||
createMockSettings({
|
||||
tools: { sandbox: false },
|
||||
security: { auth: { selectedType: AuthType.USE_VERTEX_AI } },
|
||||
}).merged,
|
||||
MOCK_WORKSPACE_DIR,
|
||||
);
|
||||
expect(process.env['GOOGLE_CLOUD_PROJECT']).toBe('my-real-project');
|
||||
});
|
||||
|
||||
it('should restore project after restart when child inherits cloudshell-gca', () => {
|
||||
// Simulate child process after restart: inherits cloudshell-gca and
|
||||
// the saved original from the parent process.
|
||||
process.env['CLOUD_SHELL'] = 'true';
|
||||
process.env['GOOGLE_CLOUD_PROJECT'] = 'cloudshell-gca';
|
||||
process.env['_GEMINI_USER_GCP_PROJECT'] = 'my-real-project';
|
||||
process.argv = ['node', 'gemini', '-s', 'prompt'];
|
||||
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||
isTrusted: false,
|
||||
source: 'file',
|
||||
});
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
|
||||
loadEnvironment(
|
||||
createMockSettings({
|
||||
tools: { sandbox: false },
|
||||
security: { auth: { selectedType: AuthType.USE_VERTEX_AI } },
|
||||
}).merged,
|
||||
MOCK_WORKSPACE_DIR,
|
||||
);
|
||||
expect(process.env['GOOGLE_CLOUD_PROJECT']).toBe('my-real-project');
|
||||
});
|
||||
|
||||
it('should sanitize GOOGLE_CLOUD_PROJECT in Cloud Shell when loaded from .env in untrusted mode', () => {
|
||||
process.env['CLOUD_SHELL'] = 'true';
|
||||
process.argv = ['node', 'gemini', '-s', 'prompt'];
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
Storage,
|
||||
coreEvents,
|
||||
homedir,
|
||||
AuthType,
|
||||
type AdminControlsSettings,
|
||||
createCache,
|
||||
} from '@google/gemini-cli-core';
|
||||
@@ -532,16 +533,45 @@ function findEnvFile(startDir: string, isTrusted: boolean): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
// Internal env var used to preserve the user's original GOOGLE_CLOUD_PROJECT
|
||||
// across process restarts in Cloud Shell. This survives relaunch because child
|
||||
// processes inherit the parent's environment.
|
||||
const USER_GCP_PROJECT = '_GEMINI_USER_GCP_PROJECT';
|
||||
|
||||
export function setUpCloudShellEnvironment(
|
||||
envFilePath: string | null,
|
||||
isTrusted: boolean,
|
||||
isSandboxed: boolean,
|
||||
selectedAuthType?: string,
|
||||
): void {
|
||||
// Special handling for GOOGLE_CLOUD_PROJECT in Cloud Shell:
|
||||
// Because GOOGLE_CLOUD_PROJECT in Cloud Shell tracks the project
|
||||
// set by the user using "gcloud config set project" we do not want to
|
||||
// use its value. So, unless the user overrides GOOGLE_CLOUD_PROJECT in
|
||||
// one of the .env files, we set the Cloud Shell-specific default here.
|
||||
//
|
||||
// However, if the user has explicitly selected Vertex AI auth, they intend
|
||||
// to use their own GCP project, so we restore the original value and skip
|
||||
// the Cloud Shell override to respect their .env settings.
|
||||
if (selectedAuthType === AuthType.USE_VERTEX_AI) {
|
||||
const saved = process.env[USER_GCP_PROJECT];
|
||||
if (saved !== undefined) {
|
||||
process.env['GOOGLE_CLOUD_PROJECT'] = saved;
|
||||
} else if (process.env['GOOGLE_CLOUD_PROJECT'] === 'cloudshell-gca') {
|
||||
delete process.env['GOOGLE_CLOUD_PROJECT'];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the user's original value before overwriting, so it can be restored
|
||||
// if the user later switches to Vertex AI (even after a process restart).
|
||||
if (!process.env[USER_GCP_PROJECT]) {
|
||||
const current = process.env['GOOGLE_CLOUD_PROJECT'];
|
||||
if (current && current !== 'cloudshell-gca') {
|
||||
process.env[USER_GCP_PROJECT] = current;
|
||||
}
|
||||
}
|
||||
|
||||
let value = 'cloudshell-gca';
|
||||
|
||||
if (envFilePath && fs.existsSync(envFilePath)) {
|
||||
@@ -584,7 +614,13 @@ export function loadEnvironment(
|
||||
|
||||
// Cloud Shell environment variable handling
|
||||
if (process.env['CLOUD_SHELL'] === 'true') {
|
||||
setUpCloudShellEnvironment(envFilePath, isTrusted, isSandboxed);
|
||||
const selectedAuthType = settings.security?.auth?.selectedType;
|
||||
setUpCloudShellEnvironment(
|
||||
envFilePath,
|
||||
isTrusted,
|
||||
isSandboxed,
|
||||
selectedAuthType,
|
||||
);
|
||||
}
|
||||
|
||||
if (envFilePath) {
|
||||
|
||||
Reference in New Issue
Block a user