diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 44fcc63439..5c10c35f62 120000 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1 +1 @@ -../CONTRIBUTING.md \ No newline at end of file +../CONTRIBUTING.md diff --git a/packages/cli/src/utils/sandbox.ts b/packages/cli/src/utils/sandbox.ts index f6fc8cdf87..113309f548 100644 --- a/packages/cli/src/utils/sandbox.ts +++ b/packages/cli/src/utils/sandbox.ts @@ -216,7 +216,7 @@ export async function start_sandbox( // process.argv is [node, script, ...args] // We want to skip the first element (node) when calling spawn(process.execPath, ...) const finalArgv = cliArgs.slice(1); - + const child = spawn(process.execPath, finalArgv, { stdio: 'inherit', env: { diff --git a/packages/core/scripts/compile-windows-sandbox.js b/packages/core/scripts/compile-windows-sandbox.js index 4389817162..54306b21a7 100644 --- a/packages/core/scripts/compile-windows-sandbox.js +++ b/packages/core/scripts/compile-windows-sandbox.js @@ -22,9 +22,18 @@ function compileWindowsSandbox() { return; } - const srcHelperPath = path.resolve(__dirname, '../src/services/scripts/GeminiSandbox.exe'); - const distHelperPath = path.resolve(__dirname, '../dist/src/services/scripts/GeminiSandbox.exe'); - const sourcePath = path.resolve(__dirname, '../src/services/scripts/GeminiSandbox.cs'); + const srcHelperPath = path.resolve( + __dirname, + '../src/services/scripts/GeminiSandbox.exe', + ); + const distHelperPath = path.resolve( + __dirname, + '../dist/src/services/scripts/GeminiSandbox.exe', + ); + const sourcePath = path.resolve( + __dirname, + '../src/services/scripts/GeminiSandbox.cs', + ); if (!fs.existsSync(sourcePath)) { console.error(`Sandbox source not found at ${sourcePath}`); @@ -32,7 +41,7 @@ function compileWindowsSandbox() { } // Ensure directories exist - [srcHelperPath, distHelperPath].forEach(p => { + [srcHelperPath, distHelperPath].forEach((p) => { const dir = path.dirname(p); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); @@ -42,34 +51,52 @@ function compileWindowsSandbox() { // Find csc.exe (C# Compiler) which is built into Windows .NET Framework const systemRoot = process.env['SystemRoot'] || 'C:\\Windows'; const cscPaths = [ - path.join(systemRoot, 'Microsoft.NET', 'Framework64', 'v4.0.30319', 'csc.exe'), - path.join(systemRoot, 'Microsoft.NET', 'Framework', 'v4.0.30319', 'csc.exe'), + path.join( + systemRoot, + 'Microsoft.NET', + 'Framework64', + 'v4.0.30319', + 'csc.exe', + ), + path.join( + systemRoot, + 'Microsoft.NET', + 'Framework', + 'v4.0.30319', + 'csc.exe', + ), ]; - const csc = cscPaths.find(p => fs.existsSync(p)); + const csc = cscPaths.find((p) => fs.existsSync(p)); if (!csc) { - console.warn('Windows C# compiler (csc.exe) not found. Native sandboxing will attempt to compile on first run.'); + console.warn( + 'Windows C# compiler (csc.exe) not found. Native sandboxing will attempt to compile on first run.', + ); return; } console.log(`Compiling native Windows sandbox helper...`); // Compile to src - let result = spawnSync(csc, [`/out:${srcHelperPath}`, '/optimize', sourcePath], { - stdio: 'inherit', - }); + let result = spawnSync( + csc, + [`/out:${srcHelperPath}`, '/optimize', sourcePath], + { + stdio: 'inherit', + }, + ); if (result.status === 0) { console.log('Successfully compiled GeminiSandbox.exe to src'); // Copy to dist if dist exists const distDir = path.resolve(__dirname, '../dist'); if (fs.existsSync(distDir)) { - const distScriptsDir = path.dirname(distHelperPath); - if (!fs.existsSync(distScriptsDir)) { - fs.mkdirSync(distScriptsDir, { recursive: true }); - } - fs.copyFileSync(srcHelperPath, distHelperPath); - console.log('Successfully copied GeminiSandbox.exe to dist'); + const distScriptsDir = path.dirname(distHelperPath); + if (!fs.existsSync(distScriptsDir)) { + fs.mkdirSync(distScriptsDir, { recursive: true }); + } + fs.copyFileSync(srcHelperPath, distHelperPath); + console.log('Successfully copied GeminiSandbox.exe to dist'); } } else { console.error('Failed to compile Windows sandbox helper.'); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index fd83bcf0ab..28712414f4 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -461,7 +461,13 @@ export interface SandboxConfig { enabled: boolean; allowedPaths: string[]; networkAccess: boolean; - command?: 'docker' | 'podman' | 'sandbox-exec' | 'runsc' | 'lxc' | 'windows-native'; + command?: + | 'docker' + | 'podman' + | 'sandbox-exec' + | 'runsc' + | 'lxc' + | 'windows-native'; image?: string; } @@ -472,7 +478,14 @@ export const ConfigSchema = z.object({ allowedPaths: z.array(z.string()).default([]), networkAccess: z.boolean().default(false), command: z - .enum(['docker', 'podman', 'sandbox-exec', 'runsc', 'lxc', 'windows-native']) + .enum([ + 'docker', + 'podman', + 'sandbox-exec', + 'runsc', + 'lxc', + 'windows-native', + ]) .optional(), image: z.string().optional(), }) diff --git a/packages/core/src/services/sandboxedFileSystemService.ts b/packages/core/src/services/sandboxedFileSystemService.ts index 1e058348dc..5c76976851 100644 --- a/packages/core/src/services/sandboxedFileSystemService.ts +++ b/packages/core/src/services/sandboxedFileSystemService.ts @@ -46,7 +46,11 @@ export class SandboxedFileSystemService implements FileSystemService { if (code === 0) { resolve(output); } else { - reject(new Error(`Sandbox Error: Command failed with exit code ${code}. ${error ? 'Details: ' + error : ''}`)); + reject( + new Error( + `Sandbox Error: Command failed with exit code ${code}. ${error ? 'Details: ' + error : ''}`, + ), + ); } }); }); @@ -78,7 +82,11 @@ export class SandboxedFileSystemService implements FileSystemService { if (code === 0) { resolve(); } else { - reject(new Error(`Sandbox Error: Command failed with exit code ${code}. ${error ? 'Details: ' + error : ''}`)); + reject( + new Error( + `Sandbox Error: Command failed with exit code ${code}. ${error ? 'Details: ' + error : ''}`, + ), + ); } }); }); diff --git a/packages/core/src/services/shellExecutionService.ts b/packages/core/src/services/shellExecutionService.ts index d1abc1a14b..e20e522263 100644 --- a/packages/core/src/services/shellExecutionService.ts +++ b/packages/core/src/services/shellExecutionService.ts @@ -337,14 +337,17 @@ export class ShellExecutionService { const finalShell = isStrictSandbox ? 'cmd' : shell; let finalArgsPrefix: string[] = []; if (finalShell === 'cmd') { - finalArgsPrefix = ['/c']; + finalArgsPrefix = ['/c']; } else { - finalArgsPrefix = argsPrefix; + finalArgsPrefix = argsPrefix; } // We still use the original executable logic (e.g. searching for git) // but the guard command formatting is based on the final shell. - const guardedCommand = ensurePromptvarsDisabled(commandToExecute, finalShell as ShellType); + const guardedCommand = ensurePromptvarsDisabled( + commandToExecute, + finalShell as ShellType, + ); const spawnArgs = [...finalArgsPrefix, guardedCommand]; const env = { ...process.env, @@ -475,8 +478,8 @@ export class ShellExecutionService { const isSandboxError = code === 3221225781 || code === -1073741515; // 0xC0000135 if (isSandboxError && shellExecutionConfig.sandboxConfig?.enabled) { - const sandboxMessage = `\\n[GEMINI_CLI_SANDBOX_ERROR: Command execution was blocked by the native Windows sandbox. This typically means the command attempted an unauthorized network request or file access.]`; - combinedOutput += sandboxMessage; + const sandboxMessage = `\\n[GEMINI_CLI_SANDBOX_ERROR: Command execution was blocked by the native Windows sandbox. This typically means the command attempted an unauthorized network request or file access.]`; + combinedOutput += sandboxMessage; } if (state.truncated) { @@ -628,12 +631,15 @@ export class ShellExecutionService { const finalShell = isStrictSandbox ? 'cmd' : shell; let finalArgsPrefix: string[] = []; if (finalShell === 'cmd') { - finalArgsPrefix = ['/c']; + finalArgsPrefix = ['/c']; } else { - finalArgsPrefix = argsPrefix; + finalArgsPrefix = argsPrefix; } - const guardedCommand = ensurePromptvarsDisabled(commandToExecute, finalShell as ShellType); + const guardedCommand = ensurePromptvarsDisabled( + commandToExecute, + finalShell as ShellType, + ); const args = [...finalArgsPrefix, guardedCommand]; const env = { @@ -887,10 +893,14 @@ export class ShellExecutionService { // Store exit info for late subscribers (e.g. backgrounding race condition) this.exitedPtyInfo.set(ptyProcess.pid, { exitCode, signal }); - const isSandboxError = exitCode === 3221225781 || exitCode === -1073741515; // 0xC0000135 + const isSandboxError = + exitCode === 3221225781 || exitCode === -1073741515; // 0xC0000135 let finalOutput = getFullBufferText(headlessTerminal); - if (isSandboxError && shellExecutionConfig.sandboxConfig?.enabled) { - finalOutput += `\n[GEMINI_CLI_SANDBOX_ERROR: Command execution was blocked by the native Windows sandbox. This typically means the command attempted an unauthorized network request or file access.]`; + if ( + isSandboxError && + shellExecutionConfig.sandboxConfig?.enabled + ) { + finalOutput += `\n[GEMINI_CLI_SANDBOX_ERROR: Command execution was blocked by the native Windows sandbox. This typically means the command attempted an unauthorized network request or file access.]`; } setTimeout( diff --git a/packages/core/src/services/windowsSandboxManager.test.ts b/packages/core/src/services/windowsSandboxManager.test.ts index ebcce77d5c..50cce11ea7 100644 --- a/packages/core/src/services/windowsSandboxManager.test.ts +++ b/packages/core/src/services/windowsSandboxManager.test.ts @@ -12,35 +12,43 @@ import * as os from 'node:os'; describe('WindowsSandboxManager', () => { const manager = new WindowsSandboxManager(); - it.skipIf(os.platform() !== 'win32')('should prepare a GeminiSandbox.exe command', async () => { - const req: SandboxRequest = { - command: 'whoami', - args: ['/groups'], - cwd: process.cwd(), - env: { TEST_VAR: 'test_value' }, - config: { - networkAccess: false - } - }; + it.skipIf(os.platform() !== 'win32')( + 'should prepare a GeminiSandbox.exe command', + async () => { + const req: SandboxRequest = { + command: 'whoami', + args: ['/groups'], + cwd: process.cwd(), + env: { TEST_VAR: 'test_value' }, + config: { + networkAccess: false, + }, + }; - const result = await manager.prepareCommand(req); + const result = await manager.prepareCommand(req); - expect(result.program).toContain('GeminiSandbox.exe'); - expect(result.args).toEqual(expect.arrayContaining(['0', process.cwd(), 'whoami', '/groups'])); - }); + expect(result.program).toContain('GeminiSandbox.exe'); + expect(result.args).toEqual( + expect.arrayContaining(['0', process.cwd(), 'whoami', '/groups']), + ); + }, + ); - it.skipIf(os.platform() !== 'win32')('should handle networkAccess from config', async () => { - const req: SandboxRequest = { - command: 'whoami', - args: [], - cwd: process.cwd(), - env: {}, - config: { - networkAccess: true - } - }; + it.skipIf(os.platform() !== 'win32')( + 'should handle networkAccess from config', + async () => { + const req: SandboxRequest = { + command: 'whoami', + args: [], + cwd: process.cwd(), + env: {}, + config: { + networkAccess: true, + }, + }; - const result = await manager.prepareCommand(req); - expect(result.args[0]).toBe('1'); - }); + const result = await manager.prepareCommand(req); + expect(result.args[0]).toBe('1'); + }, + ); }); diff --git a/packages/core/src/services/windowsSandboxManager.ts b/packages/core/src/services/windowsSandboxManager.ts index 0c3918eda6..b0e89f07df 100644 --- a/packages/core/src/services/windowsSandboxManager.ts +++ b/packages/core/src/services/windowsSandboxManager.ts @@ -31,11 +31,7 @@ export class WindowsSandboxManager implements SandboxManager { private initialized = false; constructor() { - this.helperPath = path.resolve( - __dirname, - 'scripts', - 'GeminiSandbox.exe', - ); + this.helperPath = path.resolve(__dirname, 'scripts', 'GeminiSandbox.exe'); } private ensureInitialized(): void { @@ -53,7 +49,9 @@ export class WindowsSandboxManager implements SandboxManager { 'csc.exe', ); if (fs.existsSync(csc)) { - spawnSync(csc, ['/out:' + this.helperPath, sourcePath], { stdio: 'ignore' }); + spawnSync(csc, ['/out:' + this.helperPath, sourcePath], { + stdio: 'ignore', + }); } } } @@ -107,7 +105,7 @@ export class WindowsSandboxManager implements SandboxManager { // 2. Construct the helper command // GeminiSandbox.exe [args...] const program = this.helperPath; - + // If the command starts with __, it's an internal command for the sandbox helper itself. const args = [ req.config?.networkAccess ? '1' : '0',