mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-28 22:14:52 -07:00
feat(core): add forbiddenPaths to GlobalSandboxOptions and refactor createSandboxManager (#23936)
This commit is contained in:
@@ -142,7 +142,7 @@ function ensureSandboxAvailable(): boolean {
|
||||
|
||||
describe('SandboxManager Integration', () => {
|
||||
const workspace = process.cwd();
|
||||
const manager = createSandboxManager({ enabled: true }, workspace);
|
||||
const manager = createSandboxManager({ enabled: true }, { workspace });
|
||||
|
||||
// Skip if we are on an unsupported platform or if it's a NoopSandboxManager
|
||||
const shouldSkip =
|
||||
@@ -235,7 +235,7 @@ describe('SandboxManager Integration', () => {
|
||||
try {
|
||||
const osManager = createSandboxManager(
|
||||
{ enabled: true },
|
||||
tempWorkspace,
|
||||
{ workspace: tempWorkspace, forbiddenPaths: [forbiddenDir] },
|
||||
);
|
||||
const { command, args } = Platform.touch(testFile);
|
||||
|
||||
@@ -244,7 +244,6 @@ describe('SandboxManager Integration', () => {
|
||||
args,
|
||||
cwd: tempWorkspace,
|
||||
env: process.env,
|
||||
policy: { forbiddenPaths: [forbiddenDir] },
|
||||
});
|
||||
|
||||
const result = await runCommand(sandboxed);
|
||||
@@ -268,7 +267,7 @@ describe('SandboxManager Integration', () => {
|
||||
try {
|
||||
const osManager = createSandboxManager(
|
||||
{ enabled: true },
|
||||
tempWorkspace,
|
||||
{ workspace: tempWorkspace, forbiddenPaths: [forbiddenDir] },
|
||||
);
|
||||
const { command, args } = Platform.cat(nestedFile);
|
||||
|
||||
@@ -277,7 +276,6 @@ describe('SandboxManager Integration', () => {
|
||||
args,
|
||||
cwd: tempWorkspace,
|
||||
env: process.env,
|
||||
policy: { forbiddenPaths: [forbiddenDir] },
|
||||
});
|
||||
|
||||
const result = await runCommand(sandboxed);
|
||||
@@ -298,7 +296,7 @@ describe('SandboxManager Integration', () => {
|
||||
try {
|
||||
const osManager = createSandboxManager(
|
||||
{ enabled: true },
|
||||
tempWorkspace,
|
||||
{ workspace: tempWorkspace, forbiddenPaths: [conflictDir] },
|
||||
);
|
||||
const { command, args } = Platform.touch(testFile);
|
||||
|
||||
@@ -309,7 +307,6 @@ describe('SandboxManager Integration', () => {
|
||||
env: process.env,
|
||||
policy: {
|
||||
allowedPaths: [conflictDir],
|
||||
forbiddenPaths: [conflictDir],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -329,7 +326,7 @@ describe('SandboxManager Integration', () => {
|
||||
try {
|
||||
const osManager = createSandboxManager(
|
||||
{ enabled: true },
|
||||
tempWorkspace,
|
||||
{ workspace: tempWorkspace, forbiddenPaths: [nonExistentPath] },
|
||||
);
|
||||
const { command, args } = Platform.echo('survived');
|
||||
const sandboxed = await osManager.prepareCommand({
|
||||
@@ -339,7 +336,6 @@ describe('SandboxManager Integration', () => {
|
||||
env: process.env,
|
||||
policy: {
|
||||
allowedPaths: [nonExistentPath],
|
||||
forbiddenPaths: [nonExistentPath],
|
||||
},
|
||||
});
|
||||
const result = await runCommand(sandboxed);
|
||||
@@ -362,7 +358,7 @@ describe('SandboxManager Integration', () => {
|
||||
try {
|
||||
const osManager = createSandboxManager(
|
||||
{ enabled: true },
|
||||
tempWorkspace,
|
||||
{ workspace: tempWorkspace, forbiddenPaths: [nonExistentFile] },
|
||||
);
|
||||
|
||||
// We use touch to attempt creation of the file
|
||||
@@ -374,7 +370,6 @@ describe('SandboxManager Integration', () => {
|
||||
args: argsTouch,
|
||||
cwd: tempWorkspace,
|
||||
env: process.env,
|
||||
policy: { forbiddenPaths: [nonExistentFile] },
|
||||
});
|
||||
|
||||
// Execute the command, we expect it to fail (permission denied or read-only file system)
|
||||
@@ -402,7 +397,7 @@ describe('SandboxManager Integration', () => {
|
||||
try {
|
||||
const osManager = createSandboxManager(
|
||||
{ enabled: true },
|
||||
tempWorkspace,
|
||||
{ workspace: tempWorkspace, forbiddenPaths: [symlinkFile] },
|
||||
);
|
||||
|
||||
// Attempt to read the target file directly
|
||||
@@ -413,7 +408,6 @@ describe('SandboxManager Integration', () => {
|
||||
args: argsTarget,
|
||||
cwd: tempWorkspace,
|
||||
env: process.env,
|
||||
policy: { forbiddenPaths: [symlinkFile] }, // Forbid the symlink
|
||||
});
|
||||
const resultTarget = await runCommand(commandTarget);
|
||||
expect(resultTarget.status).not.toBe(0);
|
||||
@@ -426,7 +420,6 @@ describe('SandboxManager Integration', () => {
|
||||
args: argsLink,
|
||||
cwd: tempWorkspace,
|
||||
env: process.env,
|
||||
policy: { forbiddenPaths: [symlinkFile] }, // Forbid the symlink
|
||||
});
|
||||
const resultLink = await runCommand(commandLink);
|
||||
expect(resultLink.status).not.toBe(0);
|
||||
|
||||
@@ -364,7 +364,10 @@ describe('SandboxManager', () => {
|
||||
|
||||
describe('createSandboxManager', () => {
|
||||
it('should return NoopSandboxManager if sandboxing is disabled', () => {
|
||||
const manager = createSandboxManager({ enabled: false }, '/workspace');
|
||||
const manager = createSandboxManager(
|
||||
{ enabled: false },
|
||||
{ workspace: '/workspace' },
|
||||
);
|
||||
expect(manager).toBeInstanceOf(NoopSandboxManager);
|
||||
});
|
||||
|
||||
@@ -375,7 +378,10 @@ describe('SandboxManager', () => {
|
||||
'should return $expected.name if sandboxing is enabled and platform is $platform',
|
||||
({ platform, expected }) => {
|
||||
vi.spyOn(os, 'platform').mockReturnValue(platform);
|
||||
const manager = createSandboxManager({ enabled: true }, '/workspace');
|
||||
const manager = createSandboxManager(
|
||||
{ enabled: true },
|
||||
{ workspace: '/workspace' },
|
||||
);
|
||||
expect(manager).toBeInstanceOf(expected);
|
||||
},
|
||||
);
|
||||
@@ -384,7 +390,7 @@ describe('SandboxManager', () => {
|
||||
vi.spyOn(os, 'platform').mockReturnValue('win32');
|
||||
const manager = createSandboxManager(
|
||||
{ enabled: true, command: 'windows-native' },
|
||||
'/workspace',
|
||||
{ workspace: '/workspace' },
|
||||
);
|
||||
expect(manager).toBeInstanceOf(WindowsSandboxManager);
|
||||
});
|
||||
@@ -393,7 +399,7 @@ describe('SandboxManager', () => {
|
||||
vi.spyOn(os, 'platform').mockReturnValue('win32');
|
||||
const manager = createSandboxManager(
|
||||
{ enabled: true, command: 'docker' as unknown as 'windows-native' },
|
||||
'/workspace',
|
||||
{ workspace: '/workspace' },
|
||||
);
|
||||
expect(manager).toBeInstanceOf(LocalSandboxManager);
|
||||
});
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
type EnvironmentSanitizationConfig,
|
||||
} from './environmentSanitization.js';
|
||||
import type { ShellExecutionResult } from './shellExecutionService.js';
|
||||
import type { SandboxPolicyManager } from '../policy/sandboxPolicyManager.js';
|
||||
export interface SandboxPermissions {
|
||||
/** Filesystem permissions. */
|
||||
fileSystem?: {
|
||||
@@ -40,8 +41,6 @@ export interface SandboxPermissions {
|
||||
export interface ExecutionPolicy {
|
||||
/** Additional absolute paths to grant full read/write access to. */
|
||||
allowedPaths?: string[];
|
||||
/** Absolute paths to explicitly deny read/write access to (overrides allowlists). */
|
||||
forbiddenPaths?: string[];
|
||||
/** Whether network access is allowed. */
|
||||
networkAccess?: boolean;
|
||||
/** Rules for scrubbing sensitive environment variables. */
|
||||
@@ -50,6 +49,16 @@ export interface ExecutionPolicy {
|
||||
additionalPermissions?: SandboxPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for the sandbox mode behavior.
|
||||
*/
|
||||
export interface SandboxModeConfig {
|
||||
readonly?: boolean;
|
||||
network?: boolean;
|
||||
approvedTools?: string[];
|
||||
allowOverrides?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global configuration options used to initialize a SandboxManager.
|
||||
*/
|
||||
@@ -59,6 +68,12 @@ export interface GlobalSandboxOptions {
|
||||
* This directory is granted full read and write access.
|
||||
*/
|
||||
workspace: string;
|
||||
/** Absolute paths to explicitly deny read/write access to (overrides allowlists). */
|
||||
forbiddenPaths?: string[];
|
||||
/** The current sandbox mode behavior from config. */
|
||||
modeConfig?: SandboxModeConfig;
|
||||
/** The policy manager for persistent approvals. */
|
||||
policyManager?: SandboxPolicyManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,50 +9,36 @@ import {
|
||||
type SandboxManager,
|
||||
NoopSandboxManager,
|
||||
LocalSandboxManager,
|
||||
type GlobalSandboxOptions,
|
||||
} from './sandboxManager.js';
|
||||
import { LinuxSandboxManager } from '../sandbox/linux/LinuxSandboxManager.js';
|
||||
import { MacOsSandboxManager } from '../sandbox/macos/MacOsSandboxManager.js';
|
||||
import { WindowsSandboxManager } from '../sandbox/windows/WindowsSandboxManager.js';
|
||||
import type { SandboxConfig } from '../config/config.js';
|
||||
import { type SandboxPolicyManager } from '../policy/sandboxPolicyManager.js';
|
||||
|
||||
/**
|
||||
* Creates a sandbox manager based on the provided settings.
|
||||
*/
|
||||
export function createSandboxManager(
|
||||
sandbox: SandboxConfig | undefined,
|
||||
workspace: string,
|
||||
policyManager?: SandboxPolicyManager,
|
||||
options: GlobalSandboxOptions,
|
||||
approvalMode?: string,
|
||||
): SandboxManager {
|
||||
if (approvalMode === 'yolo') {
|
||||
return new NoopSandboxManager();
|
||||
}
|
||||
|
||||
const modeConfig =
|
||||
policyManager && approvalMode
|
||||
? policyManager.getModeConfig(approvalMode)
|
||||
: undefined;
|
||||
if (!options.modeConfig && options.policyManager && approvalMode) {
|
||||
options.modeConfig = options.policyManager.getModeConfig(approvalMode);
|
||||
}
|
||||
|
||||
if (sandbox?.enabled) {
|
||||
if (os.platform() === 'win32' && sandbox?.command === 'windows-native') {
|
||||
return new WindowsSandboxManager({
|
||||
workspace,
|
||||
modeConfig,
|
||||
policyManager,
|
||||
});
|
||||
return new WindowsSandboxManager(options);
|
||||
} else if (os.platform() === 'linux') {
|
||||
return new LinuxSandboxManager({
|
||||
workspace,
|
||||
modeConfig,
|
||||
policyManager,
|
||||
});
|
||||
return new LinuxSandboxManager(options);
|
||||
} else if (os.platform() === 'darwin') {
|
||||
return new MacOsSandboxManager({
|
||||
workspace,
|
||||
modeConfig,
|
||||
policyManager,
|
||||
});
|
||||
return new MacOsSandboxManager(options);
|
||||
}
|
||||
return new LocalSandboxManager();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user