mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-13 23:51:16 -07:00
fix(cli): resolve environment loading and auth validation issues in ACP mode (#18025)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { AuthType } from '@google/gemini-cli-core';
|
||||
import { loadEnvironment, loadSettings } from './settings.js';
|
||||
|
||||
export function validateAuthMethod(authMethod: string): string | null {
|
||||
loadEnvironment(loadSettings().merged);
|
||||
loadEnvironment(loadSettings().merged, process.cwd());
|
||||
if (
|
||||
authMethod === AuthType.LOGIN_WITH_GOOGLE ||
|
||||
authMethod === AuthType.COMPUTE_ADC
|
||||
|
||||
@@ -433,7 +433,7 @@ export async function loadCliConfig(
|
||||
const ideMode = settings.ide?.enabled ?? false;
|
||||
|
||||
const folderTrust = settings.security?.folderTrust?.enabled ?? false;
|
||||
const trustedFolder = isWorkspaceTrusted(settings)?.isTrusted ?? false;
|
||||
const trustedFolder = isWorkspaceTrusted(settings, cwd)?.isTrusted ?? false;
|
||||
|
||||
// Set the context filename in the server's memoryTool module BEFORE loading memory
|
||||
// TODO(b/343434939): This is a bit of a hack. The contextFileName should ideally be passed
|
||||
|
||||
@@ -29,10 +29,11 @@ vi.mock('./settings.js', async (importActual) => {
|
||||
});
|
||||
|
||||
// Mock trustedFolders
|
||||
import * as trustedFolders from './trustedFolders.js';
|
||||
vi.mock('./trustedFolders.js', () => ({
|
||||
isWorkspaceTrusted: vi
|
||||
.fn()
|
||||
.mockReturnValue({ isTrusted: true, source: 'file' }),
|
||||
isWorkspaceTrusted: vi.fn(),
|
||||
isFolderTrustEnabled: vi.fn(),
|
||||
loadTrustedFolders: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('./settingsSchema.js', async (importOriginal) => {
|
||||
@@ -151,7 +152,7 @@ describe('Settings Loading and Merging', () => {
|
||||
(mockFsExistsSync as Mock).mockReturnValue(false);
|
||||
(fs.readFileSync as Mock).mockReturnValue('{}'); // Return valid empty JSON
|
||||
(mockFsMkdirSync as Mock).mockImplementation(() => undefined);
|
||||
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
|
||||
isTrusted: true,
|
||||
source: 'file',
|
||||
});
|
||||
@@ -1635,7 +1636,7 @@ describe('Settings Loading and Merging', () => {
|
||||
});
|
||||
|
||||
it('should NOT merge workspace settings when workspace is not trusted', () => {
|
||||
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
|
||||
isTrusted: false,
|
||||
source: 'file',
|
||||
});
|
||||
@@ -1666,23 +1667,60 @@ describe('Settings Loading and Merging', () => {
|
||||
expect(settings.merged.context?.fileName).toBe('USER.md'); // User setting
|
||||
expect(settings.merged.ui?.theme).toBe('dark'); // User setting
|
||||
});
|
||||
|
||||
it('should NOT merge workspace settings when workspace trust is undefined', () => {
|
||||
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
|
||||
isTrusted: undefined,
|
||||
source: undefined,
|
||||
});
|
||||
(mockFsExistsSync as Mock).mockReturnValue(true);
|
||||
const userSettingsContent = {
|
||||
ui: { theme: 'dark' },
|
||||
tools: { sandbox: false },
|
||||
context: { fileName: 'USER.md' },
|
||||
};
|
||||
const workspaceSettingsContent = {
|
||||
tools: { sandbox: true },
|
||||
context: { fileName: 'WORKSPACE.md' },
|
||||
};
|
||||
|
||||
(fs.readFileSync as Mock).mockImplementation(
|
||||
(p: fs.PathOrFileDescriptor) => {
|
||||
if (p === USER_SETTINGS_PATH)
|
||||
return JSON.stringify(userSettingsContent);
|
||||
if (p === MOCK_WORKSPACE_SETTINGS_PATH)
|
||||
return JSON.stringify(workspaceSettingsContent);
|
||||
return '{}';
|
||||
},
|
||||
);
|
||||
|
||||
const settings = loadSettings(MOCK_WORKSPACE_DIR);
|
||||
|
||||
expect(settings.merged.tools?.sandbox).toBe(false); // User setting
|
||||
expect(settings.merged.context?.fileName).toBe('USER.md'); // User setting
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadEnvironment', () => {
|
||||
function setup({
|
||||
isFolderTrustEnabled = true,
|
||||
isWorkspaceTrustedValue = true,
|
||||
isWorkspaceTrustedValue = true as boolean | undefined,
|
||||
}) {
|
||||
delete process.env['TESTTEST']; // reset
|
||||
const geminiEnvPath = path.resolve(path.join(GEMINI_DIR, '.env'));
|
||||
const geminiEnvPath = path.resolve(
|
||||
path.join(MOCK_WORKSPACE_DIR, GEMINI_DIR, '.env'),
|
||||
);
|
||||
|
||||
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
|
||||
isTrusted: isWorkspaceTrustedValue,
|
||||
source: 'file',
|
||||
});
|
||||
(mockFsExistsSync as Mock).mockImplementation((p: fs.PathLike) =>
|
||||
[USER_SETTINGS_PATH, geminiEnvPath].includes(p.toString()),
|
||||
);
|
||||
(mockFsExistsSync as Mock).mockImplementation((p: fs.PathLike) => {
|
||||
const normalizedP = path.resolve(p.toString());
|
||||
return [path.resolve(USER_SETTINGS_PATH), geminiEnvPath].includes(
|
||||
normalizedP,
|
||||
);
|
||||
});
|
||||
const userSettingsContent: Settings = {
|
||||
ui: {
|
||||
theme: 'dark',
|
||||
@@ -1698,9 +1736,10 @@ describe('Settings Loading and Merging', () => {
|
||||
};
|
||||
(fs.readFileSync as Mock).mockImplementation(
|
||||
(p: fs.PathOrFileDescriptor) => {
|
||||
if (p === USER_SETTINGS_PATH)
|
||||
const normalizedP = path.resolve(p.toString());
|
||||
if (normalizedP === path.resolve(USER_SETTINGS_PATH))
|
||||
return JSON.stringify(userSettingsContent);
|
||||
if (p === geminiEnvPath) return 'TESTTEST=1234';
|
||||
if (normalizedP === geminiEnvPath) return 'TESTTEST=1234';
|
||||
return '{}';
|
||||
},
|
||||
);
|
||||
@@ -1708,14 +1747,34 @@ describe('Settings Loading and Merging', () => {
|
||||
|
||||
it('sets environment variables from .env files', () => {
|
||||
setup({ isFolderTrustEnabled: false, isWorkspaceTrustedValue: true });
|
||||
loadEnvironment(loadSettings(MOCK_WORKSPACE_DIR).merged);
|
||||
const settings = {
|
||||
security: { folderTrust: { enabled: false } },
|
||||
} as Settings;
|
||||
loadEnvironment(settings, MOCK_WORKSPACE_DIR, isWorkspaceTrusted);
|
||||
|
||||
expect(process.env['TESTTEST']).toEqual('1234');
|
||||
});
|
||||
|
||||
it('does not load env files from untrusted spaces', () => {
|
||||
setup({ isFolderTrustEnabled: true, isWorkspaceTrustedValue: false });
|
||||
loadEnvironment(loadSettings(MOCK_WORKSPACE_DIR).merged);
|
||||
const settings = {
|
||||
security: { folderTrust: { enabled: true } },
|
||||
} as Settings;
|
||||
loadEnvironment(settings, MOCK_WORKSPACE_DIR, isWorkspaceTrusted);
|
||||
|
||||
expect(process.env['TESTTEST']).not.toEqual('1234');
|
||||
});
|
||||
|
||||
it('does not load env files when trust is undefined', () => {
|
||||
delete process.env['TESTTEST'];
|
||||
// isWorkspaceTrusted returns {isTrusted: undefined} for matched rules with no trust value, or no matching rules.
|
||||
setup({ isFolderTrustEnabled: true, isWorkspaceTrustedValue: undefined });
|
||||
const settings = {
|
||||
security: { folderTrust: { enabled: true } },
|
||||
} as Settings;
|
||||
|
||||
const mockTrustFn = vi.fn().mockReturnValue({ isTrusted: undefined });
|
||||
loadEnvironment(settings, MOCK_WORKSPACE_DIR, mockTrustFn);
|
||||
|
||||
expect(process.env['TESTTEST']).not.toEqual('1234');
|
||||
});
|
||||
@@ -1731,7 +1790,7 @@ describe('Settings Loading and Merging', () => {
|
||||
mockFsExistsSync.mockReturnValue(true);
|
||||
mockFsReadFileSync = vi.mocked(fs.readFileSync);
|
||||
mockFsReadFileSync.mockReturnValue('{}');
|
||||
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
||||
vi.spyOn(trustedFolders, 'isWorkspaceTrusted').mockReturnValue({
|
||||
isTrusted: true,
|
||||
source: undefined,
|
||||
});
|
||||
|
||||
@@ -50,7 +50,9 @@ import {
|
||||
formatValidationError,
|
||||
} from './settings-validation.js';
|
||||
|
||||
function getMergeStrategyForPath(path: string[]): MergeStrategy | undefined {
|
||||
export function getMergeStrategyForPath(
|
||||
path: string[],
|
||||
): MergeStrategy | undefined {
|
||||
let current: SettingDefinition | undefined = undefined;
|
||||
let currentSchema: SettingsSchema | undefined = getSettingsSchema();
|
||||
let parent: SettingDefinition | undefined = undefined;
|
||||
@@ -432,10 +434,15 @@ export function setUpCloudShellEnvironment(envFilePath: string | null): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function loadEnvironment(settings: Settings): void {
|
||||
const envFilePath = findEnvFile(process.cwd());
|
||||
export function loadEnvironment(
|
||||
settings: Settings,
|
||||
workspaceDir: string,
|
||||
isWorkspaceTrustedFn = isWorkspaceTrusted,
|
||||
): void {
|
||||
const envFilePath = findEnvFile(workspaceDir);
|
||||
const trustResult = isWorkspaceTrustedFn(settings, workspaceDir);
|
||||
|
||||
if (!isWorkspaceTrusted(settings).isTrusted) {
|
||||
if (trustResult.isTrusted !== true) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -600,7 +607,8 @@ export function loadSettings(
|
||||
userSettings,
|
||||
);
|
||||
const isTrusted =
|
||||
isWorkspaceTrusted(initialTrustCheckSettings as Settings).isTrusted ?? true;
|
||||
isWorkspaceTrusted(initialTrustCheckSettings as Settings, workspaceDir)
|
||||
.isTrusted ?? false;
|
||||
|
||||
// Create a temporary merged settings object to pass to loadEnvironment.
|
||||
const tempMergedSettings = mergeSettings(
|
||||
@@ -613,7 +621,7 @@ export function loadSettings(
|
||||
|
||||
// loadEnvironment depends on settings so we have to create a temp version of
|
||||
// the settings to avoid a cycle
|
||||
loadEnvironment(tempMergedSettings);
|
||||
loadEnvironment(tempMergedSettings, workspaceDir);
|
||||
|
||||
// Check for any fatal errors before proceeding
|
||||
const fatalErrors = settingsErrors.filter((e) => e.severity === 'error');
|
||||
|
||||
@@ -326,6 +326,19 @@ describe('isWorkspaceTrusted', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should use workspaceDir instead of process.cwd() when provided', () => {
|
||||
mockCwd = '/home/user/untrusted';
|
||||
const workspaceDir = '/home/user/projectA';
|
||||
mockRules['/home/user/projectA'] = TrustLevel.TRUST_FOLDER;
|
||||
mockRules['/home/user/untrusted'] = TrustLevel.DO_NOT_TRUST;
|
||||
|
||||
// process.cwd() is untrusted, but workspaceDir is trusted
|
||||
expect(isWorkspaceTrusted(mockSettings, workspaceDir)).toEqual({
|
||||
isTrusted: true,
|
||||
source: 'file',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle path normalization', () => {
|
||||
mockCwd = '/home/user/projectA';
|
||||
mockRules[`/home/user/../user/${path.basename('/home/user/projectA')}`] =
|
||||
|
||||
@@ -227,6 +227,7 @@ export function isFolderTrustEnabled(settings: Settings): boolean {
|
||||
}
|
||||
|
||||
function getWorkspaceTrustFromLocalConfig(
|
||||
workspaceDir: string,
|
||||
trustConfig?: Record<string, TrustLevel>,
|
||||
): TrustResult {
|
||||
const folders = loadTrustedFolders();
|
||||
@@ -241,7 +242,7 @@ function getWorkspaceTrustFromLocalConfig(
|
||||
);
|
||||
}
|
||||
|
||||
const isTrusted = folders.isPathTrusted(process.cwd(), configToUse);
|
||||
const isTrusted = folders.isPathTrusted(workspaceDir, configToUse);
|
||||
return {
|
||||
isTrusted,
|
||||
source: isTrusted !== undefined ? 'file' : undefined,
|
||||
@@ -250,6 +251,7 @@ function getWorkspaceTrustFromLocalConfig(
|
||||
|
||||
export function isWorkspaceTrusted(
|
||||
settings: Settings,
|
||||
workspaceDir: string = process.cwd(),
|
||||
trustConfig?: Record<string, TrustLevel>,
|
||||
): TrustResult {
|
||||
if (!isFolderTrustEnabled(settings)) {
|
||||
@@ -262,5 +264,5 @@ export function isWorkspaceTrusted(
|
||||
}
|
||||
|
||||
// Fall back to the local user configuration
|
||||
return getWorkspaceTrustFromLocalConfig(trustConfig);
|
||||
return getWorkspaceTrustFromLocalConfig(workspaceDir, trustConfig);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user