feat(core): integrate SandboxManager to sandbox all process-spawning tools (#22231)

This commit is contained in:
Gal Zahavi
2026-03-13 14:11:51 -07:00
committed by GitHub
parent 24adacdbc2
commit fa024133e6
31 changed files with 558 additions and 94 deletions

View File

@@ -105,6 +105,7 @@ export class AddMemoryCommand implements Command {
await tool.buildAndExecute(result.toolArgs, signal, undefined, {
sanitizationConfig: DEFAULT_SANITIZATION_CONFIG,
sandboxManager: context.config.sandboxManager,
});
await refreshMemory(context.config);
return {

View File

@@ -744,6 +744,7 @@ export async function loadCliConfig(
clientVersion: await getVersion(),
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
sandbox: sandboxConfig,
toolSandboxing: settings.security?.toolSandboxing ?? false,
targetDir: cwd,
includeDirectoryTree,
includeDirectories,

View File

@@ -20,7 +20,12 @@ import {
import { createExtension } from '../test-utils/createExtension.js';
import { ExtensionManager } from './extension-manager.js';
import { themeManager, DEFAULT_THEME } from '../ui/themes/theme-manager.js';
import { GEMINI_DIR, type Config, tmpdir } from '@google/gemini-cli-core';
import {
GEMINI_DIR,
type Config,
tmpdir,
NoopSandboxManager,
} from '@google/gemini-cli-core';
import { createTestMergedSettings, SettingScope } from './settings.js';
describe('ExtensionManager theme loading', () => {
@@ -117,6 +122,7 @@ describe('ExtensionManager theme loading', () => {
terminalHeight: 24,
showColor: false,
pager: 'cat',
sandboxManager: new NoopSandboxManager(),
sanitizationConfig: {
allowedEnvironmentVariables: [],
blockedEnvironmentVariables: [],

View File

@@ -34,7 +34,9 @@ const VALID_SANDBOX_COMMANDS = [
function isSandboxCommand(
value: string,
): value is Exclude<SandboxConfig['command'], undefined> {
return VALID_SANDBOX_COMMANDS.includes(value);
return (VALID_SANDBOX_COMMANDS as ReadonlyArray<string | undefined>).includes(
value,
);
}
function getSandboxCommand(

View File

@@ -1300,7 +1300,7 @@ const SETTINGS_SCHEMA = {
default: undefined as boolean | string | SandboxConfig | undefined,
ref: 'BooleanOrStringOrObject',
description: oneLine`
Sandbox execution environment.
Legacy full-process sandbox execution environment.
Set to a boolean to enable or disable the sandbox, provide a string path to a sandbox profile,
or specify an explicit sandbox command (e.g., "docker", "podman", "lxc").
`,
@@ -1522,6 +1522,16 @@ const SETTINGS_SCHEMA = {
description: 'Security-related settings.',
showInDialog: false,
properties: {
toolSandboxing: {
type: 'boolean',
label: 'Tool Sandboxing',
category: 'Security',
requiresRestart: false,
default: false,
description:
'Experimental tool-level sandboxing (implementation in progress).',
showInDialog: true,
},
disableYoloMode: {
type: 'boolean',
label: 'Disable YOLO Mode',

View File

@@ -13,6 +13,7 @@ import {
ApprovalMode,
getShellConfiguration,
PolicyDecision,
NoopSandboxManager,
} from '@google/gemini-cli-core';
import { quote } from 'shell-quote';
import { createPartFromText } from '@google/genai';
@@ -77,7 +78,14 @@ describe('ShellProcessor', () => {
getTargetDir: vi.fn().mockReturnValue('/test/dir'),
getApprovalMode: vi.fn().mockReturnValue(ApprovalMode.DEFAULT),
getEnableInteractiveShell: vi.fn().mockReturnValue(false),
getShellExecutionConfig: vi.fn().mockReturnValue({}),
getShellExecutionConfig: vi.fn().mockReturnValue({
sandboxManager: new NoopSandboxManager(),
sanitizationConfig: {
allowedEnvironmentVariables: [],
blockedEnvironmentVariables: [],
enableEnvironmentVariableRedaction: false,
},
}),
getPolicyEngine: vi.fn().mockReturnValue({
check: mockPolicyEngineCheck,
}),

View File

@@ -5,6 +5,7 @@
*/
import { vi } from 'vitest';
import { NoopSandboxManager } from '@google/gemini-cli-core';
import type { Config } from '@google/gemini-cli-core';
import {
createTestMergedSettings,
@@ -131,7 +132,14 @@ export const createMockConfig = (overrides: Partial<Config> = {}): Config =>
getRetryFetchErrors: vi.fn().mockReturnValue(true),
getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true),
getShellToolInactivityTimeout: vi.fn().mockReturnValue(300000),
getShellExecutionConfig: vi.fn().mockReturnValue({}),
getShellExecutionConfig: vi.fn().mockReturnValue({
sandboxManager: new NoopSandboxManager(),
sanitizationConfig: {
allowedEnvironmentVariables: [],
blockedEnvironmentVariables: [],
enableEnvironmentVariableRedaction: false,
},
}),
setShellExecutionConfig: vi.fn(),
getEnableToolOutputTruncation: vi.fn().mockReturnValue(true),
getTruncateToolOutputThreshold: vi.fn().mockReturnValue(1000),

View File

@@ -1425,6 +1425,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
pager: settings.merged.tools.shell.pager,
showColor: settings.merged.tools.shell.showColor,
sanitizationConfig: config.sanitizationConfig,
sandboxManager: config.sandboxManager,
});
const { isFocused, hasReceivedFocusEvent } = useFocus();

View File

@@ -16,6 +16,7 @@ import {
afterEach,
type Mock,
} from 'vitest';
import { NoopSandboxManager } from '@google/gemini-cli-core';
const mockIsBinary = vi.hoisted(() => vi.fn());
const mockShellExecutionService = vi.hoisted(() => vi.fn());
@@ -109,8 +110,14 @@ describe('useShellCommandProcessor', () => {
getShellExecutionConfig: () => ({
terminalHeight: 20,
terminalWidth: 80,
sandboxManager: new NoopSandboxManager(),
sanitizationConfig: {
allowedEnvironmentVariables: [],
blockedEnvironmentVariables: [],
enableEnvironmentVariableRedaction: false,
},
}),
} as Config;
} as unknown as Config;
mockGeminiClient = { addHistory: vi.fn() } as unknown as GeminiClient;
vi.mocked(os.platform).mockReturnValue('linux');