mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-21 10:34:35 -07:00
feat(core): integrate SandboxManager to sandbox all process-spawning tools (#22231)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
@@ -301,15 +301,41 @@ class GrepToolInvocation extends BaseToolInvocation<
|
||||
* @param {string} command The command name (e.g., 'git', 'grep').
|
||||
* @returns {Promise<boolean>} True if the command is available, false otherwise.
|
||||
*/
|
||||
private isCommandAvailable(command: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const checkCommand = process.platform === 'win32' ? 'where' : 'command';
|
||||
const checkArgs =
|
||||
process.platform === 'win32' ? [command] : ['-v', command];
|
||||
try {
|
||||
const child = spawn(checkCommand, checkArgs, {
|
||||
private async isCommandAvailable(command: string): Promise<boolean> {
|
||||
const checkCommand = process.platform === 'win32' ? 'where' : 'command';
|
||||
const checkArgs =
|
||||
process.platform === 'win32' ? [command] : ['-v', command];
|
||||
try {
|
||||
const sandboxManager = this.config.sandboxManager;
|
||||
|
||||
let finalCommand = checkCommand;
|
||||
let finalArgs = checkArgs;
|
||||
let finalEnv = process.env;
|
||||
|
||||
if (sandboxManager) {
|
||||
try {
|
||||
const prepared = await sandboxManager.prepareCommand({
|
||||
command: checkCommand,
|
||||
args: checkArgs,
|
||||
cwd: process.cwd(),
|
||||
env: process.env,
|
||||
});
|
||||
finalCommand = prepared.program;
|
||||
finalArgs = prepared.args;
|
||||
finalEnv = prepared.env;
|
||||
} catch (err) {
|
||||
debugLogger.debug(
|
||||
`[GrepTool] Sandbox preparation failed for '${command}':`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return await new Promise((resolve) => {
|
||||
const child = spawn(finalCommand, finalArgs, {
|
||||
stdio: 'ignore',
|
||||
shell: true,
|
||||
env: finalEnv,
|
||||
});
|
||||
child.on('close', (code) => resolve(code === 0));
|
||||
child.on('error', (err) => {
|
||||
@@ -319,10 +345,10 @@ class GrepToolInvocation extends BaseToolInvocation<
|
||||
);
|
||||
resolve(false);
|
||||
});
|
||||
} catch {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -381,6 +407,7 @@ class GrepToolInvocation extends BaseToolInvocation<
|
||||
cwd: absolutePath,
|
||||
signal: options.signal,
|
||||
allowedExitCodes: [0, 1],
|
||||
sandboxManager: this.config.sandboxManager,
|
||||
});
|
||||
|
||||
const results: GrepMatch[] = [];
|
||||
@@ -452,6 +479,7 @@ class GrepToolInvocation extends BaseToolInvocation<
|
||||
cwd: absolutePath,
|
||||
signal: options.signal,
|
||||
allowedExitCodes: [0, 1],
|
||||
sandboxManager: this.config.sandboxManager,
|
||||
});
|
||||
|
||||
for await (const line of generator) {
|
||||
|
||||
@@ -476,6 +476,7 @@ class GrepToolInvocation extends BaseToolInvocation<
|
||||
const generator = execStreaming(rgPath, rgArgs, {
|
||||
signal: options.signal,
|
||||
allowedExitCodes: [0, 1],
|
||||
sandboxManager: this.config.sandboxManager,
|
||||
});
|
||||
|
||||
let matchesFound = 0;
|
||||
|
||||
@@ -45,6 +45,7 @@ import { initializeShellParsers } from '../utils/shell-utils.js';
|
||||
import { ShellTool, OUTPUT_UPDATE_INTERVAL_MS } from './shell.js';
|
||||
import { debugLogger } from '../index.js';
|
||||
import { type Config } from '../config/config.js';
|
||||
import { NoopSandboxManager } from '../services/sandboxManager.js';
|
||||
import {
|
||||
type ShellExecutionResult,
|
||||
type ShellOutputEvent,
|
||||
@@ -137,6 +138,7 @@ describe('ShellTool', () => {
|
||||
getEnableInteractiveShell: vi.fn().mockReturnValue(false),
|
||||
getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true),
|
||||
sanitizationConfig: {},
|
||||
sandboxManager: new NoopSandboxManager(),
|
||||
} as unknown as Config;
|
||||
|
||||
const bus = createMockMessageBus();
|
||||
@@ -281,7 +283,11 @@ describe('ShellTool', () => {
|
||||
expect.any(Function),
|
||||
expect.any(AbortSignal),
|
||||
false,
|
||||
{ pager: 'cat', sanitizationConfig: {} },
|
||||
expect.objectContaining({
|
||||
pager: 'cat',
|
||||
sanitizationConfig: {},
|
||||
sandboxManager: expect.any(Object),
|
||||
}),
|
||||
);
|
||||
expect(result.llmContent).toContain('Background PIDs: 54322');
|
||||
// The file should be deleted by the tool
|
||||
@@ -306,7 +312,11 @@ describe('ShellTool', () => {
|
||||
expect.any(Function),
|
||||
expect.any(AbortSignal),
|
||||
false,
|
||||
{ pager: 'cat', sanitizationConfig: {} },
|
||||
expect.objectContaining({
|
||||
pager: 'cat',
|
||||
sanitizationConfig: {},
|
||||
sandboxManager: expect.any(Object),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -327,7 +337,11 @@ describe('ShellTool', () => {
|
||||
expect.any(Function),
|
||||
expect.any(AbortSignal),
|
||||
false,
|
||||
{ pager: 'cat', sanitizationConfig: {} },
|
||||
expect.objectContaining({
|
||||
pager: 'cat',
|
||||
sanitizationConfig: {},
|
||||
sandboxManager: expect.any(Object),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -373,7 +387,11 @@ describe('ShellTool', () => {
|
||||
expect.any(Function),
|
||||
expect.any(AbortSignal),
|
||||
false,
|
||||
{ pager: 'cat', sanitizationConfig: {} },
|
||||
{
|
||||
pager: 'cat',
|
||||
sanitizationConfig: {},
|
||||
sandboxManager: new NoopSandboxManager(),
|
||||
},
|
||||
);
|
||||
},
|
||||
20000,
|
||||
|
||||
@@ -278,6 +278,7 @@ export class ShellToolInvocation extends BaseToolInvocation<
|
||||
sanitizationConfig:
|
||||
shellExecutionConfig?.sanitizationConfig ??
|
||||
this.context.config.sanitizationConfig,
|
||||
sandboxManager: this.context.config.sandboxManager,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -57,7 +57,28 @@ class DiscoveredToolInvocation extends BaseToolInvocation<
|
||||
_updateOutput?: (output: string) => void,
|
||||
): Promise<ToolResult> {
|
||||
const callCommand = this.config.getToolCallCommand()!;
|
||||
const child = spawn(callCommand, [this.originalToolName]);
|
||||
const args = [this.originalToolName];
|
||||
|
||||
let finalCommand = callCommand;
|
||||
let finalArgs = args;
|
||||
let finalEnv = process.env;
|
||||
|
||||
const sandboxManager = this.config.sandboxManager;
|
||||
if (sandboxManager) {
|
||||
const prepared = await sandboxManager.prepareCommand({
|
||||
command: callCommand,
|
||||
args,
|
||||
cwd: process.cwd(),
|
||||
env: process.env,
|
||||
});
|
||||
finalCommand = prepared.program;
|
||||
finalArgs = prepared.args;
|
||||
finalEnv = prepared.env;
|
||||
}
|
||||
|
||||
const child = spawn(finalCommand, finalArgs, {
|
||||
env: finalEnv,
|
||||
});
|
||||
child.stdin.write(JSON.stringify(this.params));
|
||||
child.stdin.end();
|
||||
|
||||
@@ -322,8 +343,36 @@ export class ToolRegistry {
|
||||
'Tool discovery command is empty or contains only whitespace.',
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const proc = spawn(cmdParts[0] as string, cmdParts.slice(1) as string[]);
|
||||
|
||||
const firstPart = cmdParts[0];
|
||||
if (typeof firstPart !== 'string') {
|
||||
throw new Error(
|
||||
'Tool discovery command must start with a program name.',
|
||||
);
|
||||
}
|
||||
|
||||
let finalCommand: string = firstPart;
|
||||
let finalArgs: string[] = cmdParts
|
||||
.slice(1)
|
||||
.filter((p): p is string => typeof p === 'string');
|
||||
let finalEnv = process.env;
|
||||
|
||||
const sandboxManager = this.config.sandboxManager;
|
||||
if (sandboxManager) {
|
||||
const prepared = await sandboxManager.prepareCommand({
|
||||
command: finalCommand,
|
||||
args: finalArgs,
|
||||
cwd: process.cwd(),
|
||||
env: process.env,
|
||||
});
|
||||
finalCommand = prepared.program;
|
||||
finalArgs = prepared.args;
|
||||
finalEnv = prepared.env;
|
||||
}
|
||||
|
||||
const proc = spawn(finalCommand, finalArgs, {
|
||||
env: finalEnv,
|
||||
});
|
||||
let stdout = '';
|
||||
const stdoutDecoder = new StringDecoder('utf8');
|
||||
let stderr = '';
|
||||
|
||||
Reference in New Issue
Block a user