From 69c55827239b5c937c177eef4b4fbcc2758ef23e Mon Sep 17 00:00:00 2001 From: shrutip90 Date: Thu, 14 Aug 2025 11:15:48 -0700 Subject: [PATCH] feat: Show untrusted status in the Footer (#6210) Co-authored-by: Jacob Richman --- packages/cli/src/config/config.test.ts | 9 +- packages/cli/src/config/config.ts | 2 +- .../cli/src/config/trustedFolders.test.ts | 21 +++-- packages/cli/src/config/trustedFolders.ts | 11 ++- packages/cli/src/ui/App.test.tsx | 41 +++++++++ packages/cli/src/ui/App.tsx | 6 +- .../cli/src/ui/components/Footer.test.tsx | 53 ++++++++++++ packages/cli/src/ui/components/Footer.tsx | 8 +- .../cli/src/ui/hooks/useFolderTrust.test.ts | 44 ++++++---- packages/cli/src/ui/hooks/useFolderTrust.ts | 86 ++++++++++++------- 10 files changed, 221 insertions(+), 60 deletions(-) diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 69985867..e4535fca 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -1809,7 +1809,14 @@ describe('loadCliConfig trustedFolder', () => { description, } of testCases) { it(`should be correct for: ${description}`, async () => { - (isWorkspaceTrusted as vi.Mock).mockReturnValue(mockTrustValue); + (isWorkspaceTrusted as vi.Mock).mockImplementation( + (settings: Settings) => { + const featureIsEnabled = + (settings.folderTrustFeature ?? false) && + (settings.folderTrust ?? true); + return featureIsEnabled ? mockTrustValue : true; + }, + ); const argv = await parseArguments(); const settings: Settings = { folderTrustFeature, folderTrust }; const config = await loadCliConfig(settings, [], 'test-session', argv); diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 296d140d..f50cafd4 100644 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -321,7 +321,7 @@ export async function loadCliConfig( const folderTrustFeature = settings.folderTrustFeature ?? false; const folderTrustSetting = settings.folderTrust ?? true; const folderTrust = folderTrustFeature && folderTrustSetting; - const trustedFolder = folderTrust ? isWorkspaceTrusted() : true; + const trustedFolder = isWorkspaceTrusted(settings); const allExtensions = annotateActiveExtensions( extensions, diff --git a/packages/cli/src/config/trustedFolders.test.ts b/packages/cli/src/config/trustedFolders.test.ts index 67bf9cfc..5f613e63 100644 --- a/packages/cli/src/config/trustedFolders.test.ts +++ b/packages/cli/src/config/trustedFolders.test.ts @@ -35,6 +35,7 @@ import { TrustLevel, isWorkspaceTrusted, } from './trustedFolders.js'; +import { Settings } from './settings.js'; vi.mock('fs', async (importOriginal) => { const actualFs = await importOriginal(); @@ -130,6 +131,10 @@ describe('Trusted Folders Loading', () => { describe('isWorkspaceTrusted', () => { let mockCwd: string; const mockRules: Record = {}; + const mockSettings: Settings = { + folderTrustFeature: true, + folderTrust: true, + }; beforeEach(() => { vi.spyOn(process, 'cwd').mockImplementation(() => mockCwd); @@ -153,51 +158,51 @@ describe('isWorkspaceTrusted', () => { it('should return true for a directly trusted folder', () => { mockCwd = '/home/user/projectA'; mockRules['/home/user/projectA'] = TrustLevel.TRUST_FOLDER; - expect(isWorkspaceTrusted()).toBe(true); + expect(isWorkspaceTrusted(mockSettings)).toBe(true); }); it('should return true for a child of a trusted folder', () => { mockCwd = '/home/user/projectA/src'; mockRules['/home/user/projectA'] = TrustLevel.TRUST_FOLDER; - expect(isWorkspaceTrusted()).toBe(true); + expect(isWorkspaceTrusted(mockSettings)).toBe(true); }); it('should return true for a child of a trusted parent folder', () => { mockCwd = '/home/user/projectB'; mockRules['/home/user/projectB/somefile.txt'] = TrustLevel.TRUST_PARENT; - expect(isWorkspaceTrusted()).toBe(true); + expect(isWorkspaceTrusted(mockSettings)).toBe(true); }); it('should return false for a directly untrusted folder', () => { mockCwd = '/home/user/untrusted'; mockRules['/home/user/untrusted'] = TrustLevel.DO_NOT_TRUST; - expect(isWorkspaceTrusted()).toBe(false); + expect(isWorkspaceTrusted(mockSettings)).toBe(false); }); it('should return undefined for a child of an untrusted folder', () => { mockCwd = '/home/user/untrusted/src'; mockRules['/home/user/untrusted'] = TrustLevel.DO_NOT_TRUST; - expect(isWorkspaceTrusted()).toBeUndefined(); + expect(isWorkspaceTrusted(mockSettings)).toBeUndefined(); }); it('should return undefined when no rules match', () => { mockCwd = '/home/user/other'; mockRules['/home/user/projectA'] = TrustLevel.TRUST_FOLDER; mockRules['/home/user/untrusted'] = TrustLevel.DO_NOT_TRUST; - expect(isWorkspaceTrusted()).toBeUndefined(); + expect(isWorkspaceTrusted(mockSettings)).toBeUndefined(); }); it('should prioritize trust over distrust', () => { mockCwd = '/home/user/projectA/untrusted'; mockRules['/home/user/projectA'] = TrustLevel.TRUST_FOLDER; mockRules['/home/user/projectA/untrusted'] = TrustLevel.DO_NOT_TRUST; - expect(isWorkspaceTrusted()).toBe(true); + expect(isWorkspaceTrusted(mockSettings)).toBe(true); }); it('should handle path normalization', () => { mockCwd = '/home/user/projectA'; mockRules[`/home/user/../user/${path.basename('/home/user/projectA')}`] = TrustLevel.TRUST_FOLDER; - expect(isWorkspaceTrusted()).toBe(true); + expect(isWorkspaceTrusted(mockSettings)).toBe(true); }); }); diff --git a/packages/cli/src/config/trustedFolders.ts b/packages/cli/src/config/trustedFolders.ts index 9da27c80..876c2eb3 100644 --- a/packages/cli/src/config/trustedFolders.ts +++ b/packages/cli/src/config/trustedFolders.ts @@ -8,6 +8,7 @@ import * as fs from 'fs'; import * as path from 'path'; import { homedir } from 'os'; import { getErrorMessage, isWithinRoot } from '@google/gemini-cli-core'; +import { Settings } from './settings.js'; import stripJsonComments from 'strip-json-comments'; export const TRUSTED_FOLDERS_FILENAME = 'trustedFolders.json'; @@ -109,7 +110,15 @@ export function saveTrustedFolders( } } -export function isWorkspaceTrusted(): boolean | undefined { +export function isWorkspaceTrusted(settings: Settings): boolean | undefined { + const folderTrustFeature = settings.folderTrustFeature ?? false; + const folderTrustSetting = settings.folderTrust ?? true; + const folderTrustEnabled = folderTrustFeature && folderTrustSetting; + + if (!folderTrustEnabled) { + return true; + } + const { rules, errors } = loadTrustedFolders(); if (errors.length > 0) { diff --git a/packages/cli/src/ui/App.test.tsx b/packages/cli/src/ui/App.test.tsx index c797b778..64cd5842 100644 --- a/packages/cli/src/ui/App.test.tsx +++ b/packages/cli/src/ui/App.test.tsx @@ -163,6 +163,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => { getCurrentIde: vi.fn(() => 'vscode'), getDetectedIdeDisplayName: vi.fn(() => 'VSCode'), })), + isTrustedFolder: vi.fn(() => true), }; }); @@ -1118,5 +1119,45 @@ describe('App UI', () => { await Promise.resolve(); expect(lastFrame()).toContain('Do you trust this folder?'); }); + + it('should display the folder trust dialog when the feature is enabled but the folder is not trusted', async () => { + const { useFolderTrust } = await import('./hooks/useFolderTrust.js'); + vi.mocked(useFolderTrust).mockReturnValue({ + isFolderTrustDialogOpen: true, + handleFolderTrustSelect: vi.fn(), + }); + mockConfig.isTrustedFolder.mockReturnValue(false); + + const { lastFrame, unmount } = render( + , + ); + currentUnmount = unmount; + await Promise.resolve(); + expect(lastFrame()).toContain('Do you trust this folder?'); + }); + + it('should not display the folder trust dialog when the feature is disabled', async () => { + const { useFolderTrust } = await import('./hooks/useFolderTrust.js'); + vi.mocked(useFolderTrust).mockReturnValue({ + isFolderTrustDialogOpen: false, + handleFolderTrustSelect: vi.fn(), + }); + mockConfig.isTrustedFolder.mockReturnValue(false); + + const { lastFrame, unmount } = render( + , + ); + currentUnmount = unmount; + await Promise.resolve(); + expect(lastFrame()).not.toContain('Do you trust this folder?'); + }); }); }); diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 5d4643e5..2c17315f 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -171,6 +171,9 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { const [editorError, setEditorError] = useState(null); const [footerHeight, setFooterHeight] = useState(0); const [corgiMode, setCorgiMode] = useState(false); + const [isTrustedFolderState, setIsTrustedFolder] = useState( + config.isTrustedFolder(), + ); const [currentModel, setCurrentModel] = useState(config.getModel()); const [shellModeActive, setShellModeActive] = useState(false); const [showErrorDetails, setShowErrorDetails] = useState(false); @@ -254,7 +257,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { const { isFolderTrustDialogOpen, handleFolderTrustSelect } = useFolderTrust( settings, - config, + setIsTrustedFolder, ); const { @@ -1198,6 +1201,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => { promptTokenCount={sessionStats.lastPromptTokenCount} nightly={nightly} vimMode={vimModeEnabled ? vimMode : undefined} + isTrustedFolder={isTrustedFolderState} /> diff --git a/packages/cli/src/ui/components/Footer.test.tsx b/packages/cli/src/ui/components/Footer.test.tsx index 5e79eea4..e3673dfe 100644 --- a/packages/cli/src/ui/components/Footer.test.tsx +++ b/packages/cli/src/ui/components/Footer.test.tsx @@ -103,4 +103,57 @@ describe('