/** * @license * Copyright 2026 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { createSandboxManager } from './sandboxManagerFactory.js'; import { ShellExecutionService } from './shellExecutionService.js'; import { getSecureSanitizationConfig } from './environmentSanitization.js'; import { type SandboxedCommand, NoopSandboxManager, LocalSandboxManager, } from './sandboxManager.js'; import { execFile, execSync } from 'node:child_process'; import { promisify } from 'node:util'; import os from 'node:os'; import fs from 'node:fs'; import path from 'node:path'; import http from 'node:http'; /** * Abstracts platform-specific shell commands for integration testing. */ const Platform = { isWindows: os.platform() === 'win32', /** Returns a command to create an empty file. */ touch(filePath: string) { return this.isWindows ? { command: 'cmd.exe', args: ['/c', `type nul > "${filePath}"`] } : { command: 'touch', args: [filePath] }; }, /** Returns a command to read a file's content. */ cat(filePath: string) { return this.isWindows ? { command: 'cmd.exe', args: ['/c', `type "${filePath}"`] } : { command: 'cat', args: [filePath] }; }, /** Returns a command to echo a string. */ echo(text: string) { return this.isWindows ? { command: 'cmd.exe', args: ['/c', `echo ${text}`] } : { command: 'echo', args: [text] }; }, /** Returns a command to perform a network request. */ curl(url: string) { return this.isWindows ? { command: 'powershell.exe', args: ['-Command', `Invoke-WebRequest -Uri ${url} -TimeoutSec 1`], } : { command: 'curl', args: ['-s', '--connect-timeout', '1', url] }; }, /** Returns a command that checks if the current terminal is interactive. */ isPty() { return this.isWindows ? 'cmd.exe /c echo True' : 'bash -c "if [ -t 1 ]; then echo True; else echo False; fi"'; }, /** Returns a path that is strictly outside the workspace and likely blocked. */ getExternalBlockedPath() { return this.isWindows ? 'C:\\Windows\\System32\\drivers\\etc\\hosts' : '/Users/Shared/.gemini_test_blocked'; }, }; async function runCommand(command: SandboxedCommand) { try { const { stdout, stderr } = await promisify(execFile)( command.program, command.args, { cwd: command.cwd, env: command.env, encoding: 'utf-8', }, ); return { status: 0, stdout, stderr }; } catch (error: unknown) { const err = error as { code?: number; stdout?: string; stderr?: string }; return { status: err.code ?? 1, stdout: err.stdout ?? '', stderr: err.stderr ?? '', }; } } /** * Determines if the system has the necessary binaries to run the sandbox. * Throws an error if a supported platform is missing its required tools. */ function ensureSandboxAvailable(): boolean { const platform = os.platform(); if (platform === 'win32') { // Windows sandboxing relies on icacls, which is a core system utility and // always available. return true; } if (platform === 'darwin') { if (fs.existsSync('/usr/bin/sandbox-exec')) { try { execSync('sandbox-exec -p "(version 1)(allow default)" echo test', { stdio: 'ignore', }); return true; } catch { // eslint-disable-next-line no-console console.warn( 'sandbox-exec is present but cannot be used (likely running inside a sandbox already). Skipping sandbox tests.', ); return false; } } throw new Error( 'Sandboxing tests on macOS require /usr/bin/sandbox-exec to be present.', ); } if (platform === 'linux') { try { execSync('which bwrap', { stdio: 'ignore' }); return true; } catch { throw new Error( 'Sandboxing tests on Linux require bubblewrap (bwrap) to be installed.', ); } } return false; } describe('SandboxManager Integration', () => { const workspace = process.cwd(); const manager = createSandboxManager({ enabled: true }, { workspace }); // Skip if we are on an unsupported platform or if it's a NoopSandboxManager const shouldSkip = manager instanceof NoopSandboxManager || manager instanceof LocalSandboxManager || !ensureSandboxAvailable(); describe.skipIf(shouldSkip)('Cross-platform Sandbox Behavior', () => { describe('Basic Execution', () => { it('executes commands within the workspace', async () => { const { command, args } = Platform.echo('sandbox test'); const sandboxed = await manager.prepareCommand({ command, args, cwd: workspace, env: process.env, }); const result = await runCommand(sandboxed); expect(result.status).toBe(0); expect(result.stdout.trim()).toBe('sandbox test'); }); it('supports interactive pseudo-terminals (node-pty)', async () => { const handle = await ShellExecutionService.execute( Platform.isPty(), workspace, () => {}, new AbortController().signal, true, { sanitizationConfig: getSecureSanitizationConfig(), sandboxManager: manager, }, ); const result = await handle.result; expect(result.exitCode).toBe(0); expect(result.output).toContain('True'); }); }); describe('File System Access', () => { it('blocks access outside the workspace', async () => { const blockedPath = Platform.getExternalBlockedPath(); const { command, args } = Platform.touch(blockedPath); const sandboxed = await manager.prepareCommand({ command, args, cwd: workspace, env: process.env, }); const result = await runCommand(sandboxed); expect(result.status).not.toBe(0); }); it('grants access to explicitly allowed paths', async () => { const allowedDir = fs.mkdtempSync(path.join(os.tmpdir(), 'allowed-')); const testFile = path.join(allowedDir, 'test.txt'); try { const { command, args } = Platform.touch(testFile); const sandboxed = await manager.prepareCommand({ command, args, cwd: workspace, env: process.env, policy: { allowedPaths: [allowedDir] }, }); const result = await runCommand(sandboxed); expect(result.status).toBe(0); expect(fs.existsSync(testFile)).toBe(true); } finally { if (fs.existsSync(testFile)) fs.unlinkSync(testFile); fs.rmSync(allowedDir, { recursive: true, force: true }); } }); it('blocks access to forbidden paths within the workspace', async () => { const tempWorkspace = fs.mkdtempSync( path.join(os.tmpdir(), 'workspace-'), ); const forbiddenDir = path.join(tempWorkspace, 'forbidden'); const testFile = path.join(forbiddenDir, 'test.txt'); fs.mkdirSync(forbiddenDir); try { const osManager = createSandboxManager( { enabled: true }, { workspace: tempWorkspace, forbiddenPaths: [forbiddenDir] }, ); const { command, args } = Platform.touch(testFile); const sandboxed = await osManager.prepareCommand({ command, args, cwd: tempWorkspace, env: process.env, }); const result = await runCommand(sandboxed); expect(result.status).not.toBe(0); } finally { fs.rmSync(tempWorkspace, { recursive: true, force: true }); } }); it('blocks access to files inside forbidden directories recursively', async () => { const tempWorkspace = fs.mkdtempSync( path.join(os.tmpdir(), 'workspace-'), ); const forbiddenDir = path.join(tempWorkspace, 'forbidden'); const nestedDir = path.join(forbiddenDir, 'nested'); const nestedFile = path.join(nestedDir, 'test.txt'); fs.mkdirSync(nestedDir, { recursive: true }); fs.writeFileSync(nestedFile, 'secret'); try { const osManager = createSandboxManager( { enabled: true }, { workspace: tempWorkspace, forbiddenPaths: [forbiddenDir] }, ); const { command, args } = Platform.cat(nestedFile); const sandboxed = await osManager.prepareCommand({ command, args, cwd: tempWorkspace, env: process.env, }); const result = await runCommand(sandboxed); expect(result.status).not.toBe(0); } finally { fs.rmSync(tempWorkspace, { recursive: true, force: true }); } }); it('prioritizes forbiddenPaths over allowedPaths', async () => { const tempWorkspace = fs.mkdtempSync( path.join(os.tmpdir(), 'workspace-'), ); const conflictDir = path.join(tempWorkspace, 'conflict'); const testFile = path.join(conflictDir, 'test.txt'); fs.mkdirSync(conflictDir); try { const osManager = createSandboxManager( { enabled: true }, { workspace: tempWorkspace, forbiddenPaths: [conflictDir] }, ); const { command, args } = Platform.touch(testFile); const sandboxed = await osManager.prepareCommand({ command, args, cwd: tempWorkspace, env: process.env, policy: { allowedPaths: [conflictDir], }, }); const result = await runCommand(sandboxed); expect(result.status).not.toBe(0); } finally { fs.rmSync(tempWorkspace, { recursive: true, force: true }); } }); it('gracefully ignores non-existent paths in allowedPaths and forbiddenPaths', async () => { const tempWorkspace = fs.mkdtempSync( path.join(os.tmpdir(), 'workspace-'), ); const nonExistentPath = path.join(tempWorkspace, 'does-not-exist'); try { const osManager = createSandboxManager( { enabled: true }, { workspace: tempWorkspace, forbiddenPaths: [nonExistentPath] }, ); const { command, args } = Platform.echo('survived'); const sandboxed = await osManager.prepareCommand({ command, args, cwd: tempWorkspace, env: process.env, policy: { allowedPaths: [nonExistentPath], }, }); const result = await runCommand(sandboxed); expect(result.status).toBe(0); expect(result.stdout.trim()).toBe('survived'); } finally { fs.rmSync(tempWorkspace, { recursive: true, force: true }); } }); it('prevents creation of non-existent forbidden paths', async () => { // Windows icacls cannot explicitly protect paths that have not yet been created. if (Platform.isWindows) return; const tempWorkspace = fs.mkdtempSync( path.join(os.tmpdir(), 'workspace-'), ); const nonExistentFile = path.join(tempWorkspace, 'never-created.txt'); try { const osManager = createSandboxManager( { enabled: true }, { workspace: tempWorkspace, forbiddenPaths: [nonExistentFile] }, ); // We use touch to attempt creation of the file const { command: cmdTouch, args: argsTouch } = Platform.touch(nonExistentFile); const sandboxedCmd = await osManager.prepareCommand({ command: cmdTouch, args: argsTouch, cwd: tempWorkspace, env: process.env, }); // Execute the command, we expect it to fail (permission denied or read-only file system) const result = await runCommand(sandboxedCmd); expect(result.status).not.toBe(0); expect(fs.existsSync(nonExistentFile)).toBe(false); } finally { fs.rmSync(tempWorkspace, { recursive: true, force: true }); } }); it('blocks access to both a symlink and its target when the symlink is forbidden', async () => { if (Platform.isWindows) return; const tempWorkspace = fs.mkdtempSync( path.join(os.tmpdir(), 'workspace-'), ); const targetFile = path.join(tempWorkspace, 'target.txt'); const symlinkFile = path.join(tempWorkspace, 'link.txt'); fs.writeFileSync(targetFile, 'secret data'); fs.symlinkSync(targetFile, symlinkFile); try { const osManager = createSandboxManager( { enabled: true }, { workspace: tempWorkspace, forbiddenPaths: [symlinkFile] }, ); // Attempt to read the target file directly const { command: cmdTarget, args: argsTarget } = Platform.cat(targetFile); const commandTarget = await osManager.prepareCommand({ command: cmdTarget, args: argsTarget, cwd: tempWorkspace, env: process.env, }); const resultTarget = await runCommand(commandTarget); expect(resultTarget.status).not.toBe(0); // Attempt to read via the symlink const { command: cmdLink, args: argsLink } = Platform.cat(symlinkFile); const commandLink = await osManager.prepareCommand({ command: cmdLink, args: argsLink, cwd: tempWorkspace, env: process.env, }); const resultLink = await runCommand(commandLink); expect(resultLink.status).not.toBe(0); } finally { fs.rmSync(tempWorkspace, { recursive: true, force: true }); } }); }); describe('Network Access', () => { let server: http.Server; let url: string; beforeAll(async () => { server = http.createServer((_, res) => { res.setHeader('Connection', 'close'); res.writeHead(200); res.end('ok'); }); await new Promise((resolve, reject) => { server.on('error', reject); server.listen(0, '127.0.0.1', () => { const addr = server.address() as import('net').AddressInfo; url = `http://127.0.0.1:${addr.port}`; resolve(); }); }); }); afterAll(async () => { if (server) await new Promise((res) => server.close(() => res())); }); it('blocks network access by default', async () => { const { command, args } = Platform.curl(url); const sandboxed = await manager.prepareCommand({ command, args, cwd: workspace, env: process.env, }); const result = await runCommand(sandboxed); expect(result.status).not.toBe(0); }); it('grants network access when explicitly allowed', async () => { const { command, args } = Platform.curl(url); const sandboxed = await manager.prepareCommand({ command, args, cwd: workspace, env: process.env, policy: { networkAccess: true }, }); const result = await runCommand(sandboxed); expect(result.status).toBe(0); if (!Platform.isWindows) { expect(result.stdout.trim()).toBe('ok'); } }); }); }); });