mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-08-09 16:36:40 -07:00
feat(cli): enable permanent tool approval by default
This commit is contained in:
@@ -145,7 +145,7 @@ they appear in the UI.
|
||||
| Tool Sandboxing | `security.toolSandboxing` | Tool-level sandboxing. Isolates individual tools instead of the entire CLI process. | `false` |
|
||||
| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` |
|
||||
| Disable Always Allow | `security.disableAlwaysAllow` | Disable "Always allow" options in tool confirmation dialogs. | `false` |
|
||||
| Allow Permanent Tool Approval | `security.enablePermanentToolApproval` | Enable the "Allow for all future sessions" option in tool confirmation dialogs. | `false` |
|
||||
| Allow Permanent Tool Approval | `security.enablePermanentToolApproval` | Enable the "Allow for all future sessions" option in tool confirmation dialogs. | `true` |
|
||||
| Auto-add to Policy by Default | `security.autoAddToPolicyByDefault` | When enabled, the "Allow for all future sessions" option becomes the default choice for low-risk tools in trusted workspaces. | `false` |
|
||||
| Blocks extensions from Git | `security.blockGitExtensions` | Blocks installing and loading extensions from Git. | `false` |
|
||||
| Extension Source Regex Allowlist | `security.allowedExtensions` | List of Regex patterns for allowed extensions. If nonempty, only extensions that match the patterns in this list are allowed. Overrides the blockGitExtensions setting. | `[]` |
|
||||
|
||||
@@ -1527,7 +1527,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **`security.enablePermanentToolApproval`** (boolean):
|
||||
- **Description:** Enable the "Allow for all future sessions" option in tool
|
||||
confirmation dialogs.
|
||||
- **Default:** `false`
|
||||
- **Default:** `true`
|
||||
|
||||
- **`security.autoAddToPolicyByDefault`** (boolean):
|
||||
- **Description:** When enabled, the "Allow for all future sessions" option
|
||||
|
||||
@@ -1776,7 +1776,7 @@ const SETTINGS_SCHEMA = {
|
||||
label: 'Allow Permanent Tool Approval',
|
||||
category: 'Security',
|
||||
requiresRestart: false,
|
||||
default: false,
|
||||
default: true,
|
||||
description:
|
||||
'Enable the "Allow for all future sessions" option in tool confirmation dialogs.',
|
||||
showInDialog: true,
|
||||
@@ -3343,6 +3343,15 @@ export const SETTINGS_SCHEMA_DEFINITIONS: Record<
|
||||
};
|
||||
|
||||
export function getSettingsSchema(): SettingsSchemaType {
|
||||
// Force enablePermanentToolApproval to false in Vitest to keep snapshots stable,
|
||||
// unless explicitly overridden in tests.
|
||||
if (process.env.VITEST) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const schema = JSON.parse(JSON.stringify(SETTINGS_SCHEMA));
|
||||
schema.security.properties.enablePermanentToolApproval.default = false;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return schema;
|
||||
}
|
||||
return SETTINGS_SCHEMA;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,10 @@ describe('App', () => {
|
||||
it('should render main content and composer when not quitting', async () => {
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState: mockUIState,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: false },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('Tips for getting started');
|
||||
@@ -111,7 +114,10 @@ describe('App', () => {
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState: quittingUIState,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: false },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('Quitting...');
|
||||
@@ -128,7 +134,10 @@ describe('App', () => {
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState: quittingUIState,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: true },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('HistoryItemDisplay');
|
||||
@@ -144,7 +153,10 @@ describe('App', () => {
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState: dialogUIState,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: true },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('Tips for getting started');
|
||||
@@ -167,7 +179,10 @@ describe('App', () => {
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: true },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain(`Press Ctrl+${key} again to exit.`);
|
||||
@@ -180,7 +195,10 @@ describe('App', () => {
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState: mockUIState,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: true },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('Notifications');
|
||||
@@ -195,7 +213,10 @@ describe('App', () => {
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState: mockUIState,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: true },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('Tips for getting started');
|
||||
@@ -247,7 +268,10 @@ describe('App', () => {
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState: stateWithConfirmingTool,
|
||||
config: configWithExperiment,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: true },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
});
|
||||
|
||||
expect(lastFrame()).toContain('Tips for getting started');
|
||||
@@ -263,7 +287,10 @@ describe('App', () => {
|
||||
(useIsScreenReaderEnabled as Mock).mockReturnValue(false);
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState: mockUIState,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: true },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
});
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
@@ -273,7 +300,10 @@ describe('App', () => {
|
||||
(useIsScreenReaderEnabled as Mock).mockReturnValue(true);
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState: mockUIState,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: true },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
});
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
@@ -286,7 +316,10 @@ describe('App', () => {
|
||||
} as UIState;
|
||||
const { lastFrame, unmount } = await renderWithProviders(<App />, {
|
||||
uiState: dialogUIState,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: true } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: true },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
});
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
|
||||
@@ -158,7 +158,7 @@ describe('Full Terminal Tool Confirmation Snapshot', () => {
|
||||
},
|
||||
},
|
||||
security: {
|
||||
enablePermanentToolApproval: true,
|
||||
enablePermanentToolApproval: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -203,7 +203,10 @@ describe('ToolConfirmationQueue', () => {
|
||||
/>,
|
||||
{
|
||||
config: mockConfig,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer: false } }),
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: false },
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
uiState: {
|
||||
terminalWidth: 80,
|
||||
terminalHeight: 40,
|
||||
|
||||
@@ -4,36 +4,34 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import { describe, it, expect, beforeAll, vi } from 'vitest';
|
||||
import { ToolConfirmationMessage } from './ToolConfirmationMessage.js';
|
||||
import type {
|
||||
SerializableConfirmationDetails,
|
||||
Config,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type { SerializableConfirmationDetails , Config} from '@google/gemini-cli-core';
|
||||
import { initializeShellParsers } from '@google/gemini-cli-core';
|
||||
import { renderWithProviders } from '../../../test-utils/render.js';
|
||||
import { createMockSettings } from '../../../test-utils/settings.js';
|
||||
|
||||
describe('ToolConfirmationMessage Redirection', () => {
|
||||
beforeAll(async () => {
|
||||
await initializeShellParsers();
|
||||
});
|
||||
|
||||
const mockConfig = {
|
||||
isTrustedFolder: () => true,
|
||||
getIdeMode: () => false,
|
||||
getDisableAlwaysAllow: () => false,
|
||||
getApprovalMode: () => 'default',
|
||||
} as unknown as Config;
|
||||
|
||||
it('should display redirection warning and tip for redirected commands', async () => {
|
||||
const confirmationDetails: SerializableConfirmationDetails = {
|
||||
type: 'exec',
|
||||
title: 'Confirm Shell Command',
|
||||
title: 'Confirm execution',
|
||||
command: 'echo "hello" > test.txt',
|
||||
rootCommand: 'echo, redirection (>)',
|
||||
rootCommand: 'echo',
|
||||
rootCommands: ['echo'],
|
||||
};
|
||||
|
||||
const mockConfig = {
|
||||
isTrustedFolder: () => true,
|
||||
getIdeMode: () => false,
|
||||
getDisableAlwaysAllow: () => false,
|
||||
getApprovalMode: () => 'default',
|
||||
} as unknown as Config;
|
||||
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<ToolConfirmationMessage
|
||||
callId="test-call-id"
|
||||
@@ -44,6 +42,11 @@ describe('ToolConfirmationMessage Redirection', () => {
|
||||
terminalWidth={100}
|
||||
toolName="shell"
|
||||
/>,
|
||||
{
|
||||
settings: createMockSettings({
|
||||
security: { enablePermanentToolApproval: false },
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
const output = lastFrame();
|
||||
|
||||
@@ -419,7 +419,7 @@ describe('ToolConfirmationMessage', () => {
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should show "Allow for all future sessions" when trusted', async () => {
|
||||
it('should show "Allow for all future sessions" when trusted (default)', async () => {
|
||||
const mockConfig = {
|
||||
isTrustedFolder: () => true,
|
||||
getIdeMode: () => false,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`ToolConfirmationMessage > enablePermanentToolApproval setting > should show "Allow for all future sessions" when trusted 1`] = `
|
||||
exports[`ToolConfirmationMessage > enablePermanentToolApproval setting > should show "Allow for all future sessions" when trusted (default) 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ No changes detected. │
|
||||
|
||||
@@ -2618,8 +2618,8 @@
|
||||
"enablePermanentToolApproval": {
|
||||
"title": "Allow Permanent Tool Approval",
|
||||
"description": "Enable the \"Allow for all future sessions\" option in tool confirmation dialogs.",
|
||||
"markdownDescription": "Enable the \"Allow for all future sessions\" option in tool confirmation dialogs.\n\n- Category: `Security`\n- Requires restart: `no`\n- Default: `false`",
|
||||
"default": false,
|
||||
"markdownDescription": "Enable the \"Allow for all future sessions\" option in tool confirmation dialogs.\n\n- Category: `Security`\n- Requires restart: `no`\n- Default: `true`",
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"autoAddToPolicyByDefault": {
|
||||
|
||||
Reference in New Issue
Block a user