From cc06ac3202d02f107ed4449a52f37cdab478ac85 Mon Sep 17 00:00:00 2001 From: ehedlund Date: Wed, 15 Apr 2026 17:23:13 -0400 Subject: [PATCH] address Gemini feedback --- .../src/sandbox/linux/LinuxSandboxManager.ts | 150 +++++++++--------- 1 file changed, 73 insertions(+), 77 deletions(-) diff --git a/packages/core/src/sandbox/linux/LinuxSandboxManager.ts b/packages/core/src/sandbox/linux/LinuxSandboxManager.ts index 3533f7d504..877ded781f 100644 --- a/packages/core/src/sandbox/linux/LinuxSandboxManager.ts +++ b/packages/core/src/sandbox/linux/LinuxSandboxManager.ts @@ -28,86 +28,85 @@ import { } from '../abstractOsSandboxManager.js'; import { isStrictlyApproved } from '../utils/commandUtils.js'; -let cachedBpfPath: string | undefined; - -function getSeccompBpfPath(): string { - if (cachedBpfPath) return cachedBpfPath; - - const arch = os.arch(); - let AUDIT_ARCH: number; - let SYS_ptrace: number; - - if (arch === 'x64') { - AUDIT_ARCH = 0xc000003e; // AUDIT_ARCH_X86_64 - SYS_ptrace = 101; - } else if (arch === 'arm64') { - AUDIT_ARCH = 0xc00000b7; // AUDIT_ARCH_AARCH64 - SYS_ptrace = 117; - } else if (arch === 'arm') { - AUDIT_ARCH = 0x40000028; // AUDIT_ARCH_ARM - SYS_ptrace = 26; - } else if (arch === 'ia32') { - AUDIT_ARCH = 0x40000003; // AUDIT_ARCH_I386 - SYS_ptrace = 26; - } else { - throw new Error(`Unsupported architecture for seccomp filter: ${arch}`); - } - - const EPERM = 1; - const SECCOMP_RET_KILL_PROCESS = 0x80000000; - const SECCOMP_RET_ERRNO = 0x00050000; - const SECCOMP_RET_ALLOW = 0x7fff0000; - - const instructions = [ - { code: 0x20, jt: 0, jf: 0, k: 4 }, // Load arch - { code: 0x15, jt: 1, jf: 0, k: AUDIT_ARCH }, // Jump to kill if arch != native arch - { code: 0x06, jt: 0, jf: 0, k: SECCOMP_RET_KILL_PROCESS }, // Kill - - { code: 0x20, jt: 0, jf: 0, k: 0 }, // Load nr - { code: 0x15, jt: 0, jf: 1, k: SYS_ptrace }, // If ptrace, jump to ERRNO - { code: 0x06, jt: 0, jf: 0, k: SECCOMP_RET_ERRNO | EPERM }, // ERRNO - - { code: 0x06, jt: 0, jf: 0, k: SECCOMP_RET_ALLOW }, // Allow - ]; - - const buf = Buffer.alloc(8 * instructions.length); - for (let i = 0; i < instructions.length; i++) { - const inst = instructions[i]; - const offset = i * 8; - buf.writeUInt16LE(inst.code, offset); - buf.writeUInt8(inst.jt, offset + 2); - buf.writeUInt8(inst.jf, offset + 3); - buf.writeUInt32LE(inst.k, offset + 4); - } - - const tempDir = fs.mkdtempSync(join(os.tmpdir(), 'gemini-cli-seccomp-')); - const bpfPath = join(tempDir, 'seccomp.bpf'); - fs.writeFileSync(bpfPath, buf); - cachedBpfPath = bpfPath; - - // Cleanup on exit - process.on('exit', () => { - try { - fs.rmSync(tempDir, { recursive: true, force: true }); - } catch { - // Ignore errors - } - }); - - return bpfPath; -} - /** * A SandboxManager implementation for Linux that uses Bubblewrap (bwrap). */ export class LinuxSandboxManager extends AbstractOsSandboxManager { - private static maskFilePath: string | undefined; + private cachedBpfPath: string | undefined; + private maskFilePath: string | undefined; protected override async initialize(): Promise { // Default no-op } + private getSeccompBpfPath(): string { + if (this.cachedBpfPath) return this.cachedBpfPath; + + const arch = os.arch(); + let AUDIT_ARCH: number; + let SYS_ptrace: number; + + if (arch === 'x64') { + AUDIT_ARCH = 0xc000003e; // AUDIT_ARCH_X86_64 + SYS_ptrace = 101; + } else if (arch === 'arm64') { + AUDIT_ARCH = 0xc00000b7; // AUDIT_ARCH_AARCH64 + SYS_ptrace = 117; + } else if (arch === 'arm') { + AUDIT_ARCH = 0x40000028; // AUDIT_ARCH_ARM + SYS_ptrace = 26; + } else if (arch === 'ia32') { + AUDIT_ARCH = 0x40000003; // AUDIT_ARCH_I386 + SYS_ptrace = 26; + } else { + throw new Error(`Unsupported architecture for seccomp filter: ${arch}`); + } + + const EPERM = 1; + const SECCOMP_RET_KILL_PROCESS = 0x80000000; + const SECCOMP_RET_ERRNO = 0x00050000; + const SECCOMP_RET_ALLOW = 0x7fff0000; + + const instructions = [ + { code: 0x20, jt: 0, jf: 0, k: 4 }, // Load arch + { code: 0x15, jt: 1, jf: 0, k: AUDIT_ARCH }, // Jump to kill if arch != native arch + { code: 0x06, jt: 0, jf: 0, k: SECCOMP_RET_KILL_PROCESS }, // Kill + + { code: 0x20, jt: 0, jf: 0, k: 0 }, // Load nr + { code: 0x15, jt: 0, jf: 1, k: SYS_ptrace }, // If ptrace, jump to ERRNO + { code: 0x06, jt: 0, jf: 0, k: SECCOMP_RET_ERRNO | EPERM }, // ERRNO + + { code: 0x06, jt: 0, jf: 0, k: SECCOMP_RET_ALLOW }, // Allow + ]; + + const buf = Buffer.alloc(8 * instructions.length); + for (let i = 0; i < instructions.length; i++) { + const inst = instructions[i]; + const offset = i * 8; + buf.writeUInt16LE(inst.code, offset); + buf.writeUInt8(inst.jt, offset + 2); + buf.writeUInt8(inst.jf, offset + 3); + buf.writeUInt32LE(inst.k, offset + 4); + } + + const tempDir = fs.mkdtempSync(join(os.tmpdir(), 'gemini-cli-seccomp-')); + const bpfPath = join(tempDir, 'seccomp.bpf'); + fs.writeFileSync(bpfPath, buf); + this.cachedBpfPath = bpfPath; + + // Cleanup on exit + process.on('exit', () => { + try { + fs.rmSync(tempDir, { recursive: true, force: true }); + } catch { + // Ignore errors + } + }); + + return bpfPath; + } + protected override ensureGovernanceFilesExist(workspace: string): void { if (this.governanceFilesInitialized) return; ensureGovernanceFilesExist(workspace); @@ -179,7 +178,7 @@ export class LinuxSandboxManager extends AbstractOsSandboxManager { isReadOnlyCommand: req.command === '__read', }); - const bpfPath = getSeccompBpfPath(); + const bpfPath = this.getSeccompBpfPath(); bwrapArgs.push('--seccomp', '9'); const argsPath = this.writeArgsToTempFile(bwrapArgs); @@ -248,17 +247,14 @@ export class LinuxSandboxManager extends AbstractOsSandboxManager { } private getMaskFilePath(): string { - if ( - LinuxSandboxManager.maskFilePath && - fs.existsSync(LinuxSandboxManager.maskFilePath) - ) { - return LinuxSandboxManager.maskFilePath; + if (this.maskFilePath && fs.existsSync(this.maskFilePath)) { + return this.maskFilePath; } const tempDir = fs.mkdtempSync(join(os.tmpdir(), 'gemini-cli-mask-file-')); const maskPath = join(tempDir, 'mask'); fs.writeFileSync(maskPath, ''); fs.chmodSync(maskPath, 0); - LinuxSandboxManager.maskFilePath = maskPath; + this.maskFilePath = maskPath; // Cleanup on exit process.on('exit', () => {