mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-17 01:21:10 -07:00
feat(cli): disable folder trust in headless mode (#18407)
This commit is contained in:
@@ -23,11 +23,22 @@ import { FolderTrustChoice } from '../components/FolderTrustDialog.js';
|
||||
import type { LoadedTrustedFolders } from '../../config/trustedFolders.js';
|
||||
import { TrustLevel } from '../../config/trustedFolders.js';
|
||||
import * as trustedFolders from '../../config/trustedFolders.js';
|
||||
import { coreEvents, ExitCodes } from '@google/gemini-cli-core';
|
||||
import { coreEvents, ExitCodes, isHeadlessMode } from '@google/gemini-cli-core';
|
||||
import { MessageType } from '../types.js';
|
||||
|
||||
const mockedCwd = vi.hoisted(() => vi.fn());
|
||||
const mockedExit = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async () => {
|
||||
const actual = await vi.importActual<
|
||||
typeof import('@google/gemini-cli-core')
|
||||
>('@google/gemini-cli-core');
|
||||
return {
|
||||
...actual,
|
||||
isHeadlessMode: vi.fn().mockReturnValue(false),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('node:process', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('node:process')>('node:process');
|
||||
@@ -46,8 +57,24 @@ describe('useFolderTrust', () => {
|
||||
let onTrustChange: (isTrusted: boolean | undefined) => void;
|
||||
let addItem: Mock;
|
||||
|
||||
const originalStdoutIsTTY = process.stdout.isTTY;
|
||||
const originalStdinIsTTY = process.stdin.isTTY;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
// Default to interactive mode for tests
|
||||
Object.defineProperty(process.stdout, 'isTTY', {
|
||||
value: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
Object.defineProperty(process.stdin, 'isTTY', {
|
||||
value: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
mockSettings = {
|
||||
merged: {
|
||||
security: {
|
||||
@@ -75,6 +102,16 @@ describe('useFolderTrust', () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.clearAllMocks();
|
||||
Object.defineProperty(process.stdout, 'isTTY', {
|
||||
value: originalStdoutIsTTY,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
Object.defineProperty(process.stdin, 'isTTY', {
|
||||
value: originalStdinIsTTY,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not open dialog when folder is already trusted', () => {
|
||||
@@ -318,4 +355,28 @@ describe('useFolderTrust', () => {
|
||||
);
|
||||
expect(mockedExit).toHaveBeenCalledWith(ExitCodes.FATAL_CONFIG_ERROR);
|
||||
});
|
||||
|
||||
describe('headless mode', () => {
|
||||
it('should force trust and hide dialog in headless mode', () => {
|
||||
vi.mocked(isHeadlessMode).mockReturnValue(true);
|
||||
isWorkspaceTrustedSpy.mockReturnValue({
|
||||
isTrusted: false,
|
||||
source: 'file',
|
||||
});
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useFolderTrust(mockSettings, onTrustChange, addItem),
|
||||
);
|
||||
|
||||
expect(result.current.isFolderTrustDialogOpen).toBe(false);
|
||||
expect(onTrustChange).toHaveBeenCalledWith(true);
|
||||
expect(addItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageType.INFO,
|
||||
text: expect.stringContaining('This folder is untrusted'),
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from '../../config/trustedFolders.js';
|
||||
import * as process from 'node:process';
|
||||
import { type HistoryItemWithoutId, MessageType } from '../types.js';
|
||||
import { coreEvents, ExitCodes } from '@google/gemini-cli-core';
|
||||
import { coreEvents, ExitCodes, isHeadlessMode } from '@google/gemini-cli-core';
|
||||
import { runExitCleanup } from '../../utils/cleanup.js';
|
||||
|
||||
export const useFolderTrust = (
|
||||
@@ -30,21 +30,39 @@ export const useFolderTrust = (
|
||||
const folderTrust = settings.merged.security.folderTrust.enabled ?? true;
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
const { isTrusted: trusted } = isWorkspaceTrusted(settings.merged);
|
||||
setIsTrusted(trusted);
|
||||
setIsFolderTrustDialogOpen(trusted === undefined);
|
||||
onTrustChange(trusted);
|
||||
|
||||
if (trusted === false && !startupMessageSent.current) {
|
||||
addItem(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
text: 'This folder is untrusted, project settings, hooks, MCPs, and GEMINI.md files will not be applied for this folder.\nUse the `/permissions` command to change the trust level.',
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
startupMessageSent.current = true;
|
||||
const showUntrustedMessage = () => {
|
||||
if (trusted === false && !startupMessageSent.current) {
|
||||
addItem(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
text: 'This folder is untrusted, project settings, hooks, MCPs, and GEMINI.md files will not be applied for this folder.\nUse the `/permissions` command to change the trust level.',
|
||||
},
|
||||
Date.now(),
|
||||
);
|
||||
startupMessageSent.current = true;
|
||||
}
|
||||
};
|
||||
|
||||
if (isHeadlessMode()) {
|
||||
if (isMounted) {
|
||||
setIsTrusted(trusted);
|
||||
setIsFolderTrustDialogOpen(false);
|
||||
onTrustChange(true);
|
||||
showUntrustedMessage();
|
||||
}
|
||||
} else if (isMounted) {
|
||||
setIsTrusted(trusted);
|
||||
setIsFolderTrustDialogOpen(trusted === undefined);
|
||||
onTrustChange(trusted);
|
||||
showUntrustedMessage();
|
||||
}
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [folderTrust, onTrustChange, settings.merged, addItem]);
|
||||
|
||||
const handleFolderTrustSelect = useCallback(
|
||||
|
||||
Reference in New Issue
Block a user