跳到主要内容

06 · 沙箱与安全边界

这章回答一个尖锐的问题:Chatbox 凭什么敢执行模型生成的 shell 命令? 答案是它没自己造轮子,而是把命令关进 Anthropic 的沙箱运行时,并叠了几层防护。

6.1 它要解决的小问题

第4章的 sandbox_bash 工具能让模型跑任意 shell。这显然危险:模型(或被注入的恶意 prompt)可能 cat ~/.ssh/id_rsarm -rf、外传密钥。所以执行前必须有真正的操作系统级隔离,而不只是"提示模型别乱来"。

6.2 思路:复用 Anthropic sandbox-runtime

沙箱跑在 main 进程(渲染进程没有 Node 的 spawn)。核心依赖是 @anthropic-ai/sandbox-runtime(src/main/sandbox/manager.ts:6)。它提供两个关键能力:

  • SandboxManager.initialize(config)——按配置(允许读写哪些路径、网络策略)初始化一个沙箱 profile。
  • SandboxManager.wrapWithSandbox(command)——把一条命令包裹成"在沙箱约束下执行"的形式,然后才交给 spawn 跑(manager.ts:141)。

执行链路(execCommand, manager.ts:136):

模型给的 command


wrapWithSandbox(command) ← Anthropic runtime 加上 OS 级约束


spawn(wrapped, { shell, cwd, detached }) ← 在工作目录里跑


stdout/stderr 收集 → tailTruncate 截断 → 返回 {stdout, stderr, exitCode}

6.3 精华:默认拒绝清单

安全的关键在 buildConfig(manager.ts:52)和共享常量(src/shared/task-sandbox.ts)。默认就把高危目标拉黑:

// src/shared/task-sandbox.ts —— 默认拒绝清单
export const TASK_SANDBOX_DENY_READ_PATHS = ['~/.ssh', '~/.gnupg', '~/.aws', '~/.config/gh']
export const TASK_SANDBOX_DENY_WRITE_PATHS = ['.env', '.env.local', '.env.production']
export const TASK_SANDBOX_EXTRA_WRITE_PATHS = ['/tmp']

配置组装时:可写 = 用户选的工作目录 + /tmp,拒读 = SSH/GPG/AWS/gh 凭据目录,拒写 = 各种 .env(manager.ts:58-72)。工具说明里也对模型明说了这条边界:"Write access is limited to the selected working directory and /tmp"(toolsets/sandbox.ts 描述)。

这里还藏了一个容易踩的坑注释(manager.ts:60-62):

// WARN: `allowedDomains: ['*']` is NOT a wildcard — it's a literal match.
// Omit `allowedDomains` so wrapWithSandbox generates `(allow network*)`.

即:想"允许所有网络"不能写 ['*'](那会被当成字面域名),而要省略该字段。这种"运行时行为反直觉"的点被留成注释,是高质量边界代码的标志。

6.4 进程管控:超时、进程组 kill

模型跑的命令可能挂死或 fork 子进程,所以:

  • detached spawn + 进程组 kill:spawn(..., { detached: true }) 让命令自成进程组,kill 时用 process.kill(-pid, ...)整组(SIGTERM,3 秒后 SIGKILL)(manager.ts:149-183)。这能干掉命令 fork 出来的子孙进程,避免僵尸。
  • 超时:默认 execCommand 30 秒(manager.ts:143),但工具层 sandbox_bash 默认给到 120 秒(toolsets/sandbox.ts DEFAULT_BASH_TIMEOUT),超时 exitCode 记 124。
  • 可中断:工具层用 abortableExecAbortSignal 接到 platform.sandboxKill(),会话一取消就杀沙箱进程(toolsets/sandbox.ts:48-77)。

6.5 跨平台:Windows 走 WSL2

沙箱在 macOS/Linux 原生可用;Windows 需要 WSL2checkAvailability(manager.ts:346)会 wsl --status 探测,不可用就返回 wsl2_required。路径也要从 C:\foo 转成 /mnt/c/foo(toWSLPath, manager.ts:43)。

6.6 输出截断:防上下文爆炸

命令输出可能是几 MB 的日志,直接喂回模型会爆上下文。truncate.ts 提供 tailTruncate(留尾部,看最新输出)和 headTruncate(留头部,如 cat/ls)。execCommand 对 stdout/stderr 用 tailTruncate(manager.ts:197-198),文件读取类用 headTruncate(manager.ts:233)。

6.7 高层文件操作 = 包一层 shell

有意思的是 sandbox_write/sandbox_edit 等并非用 Node fs,而是把操作翻译成 shell 命令再过沙箱:写文件用 printf '%s' '…' > file(manager.ts:242),编辑用 sed -i(manager.ts:261),都仔细做了转义(shellEscape/escapeSedBRE)。

设计含义:所有文件操作都强制走同一个沙箱约束,不存在"绕过沙箱直接 fs 写盘"的旁路。统一收口于 execCommand

6.8 边界与风险

  • 隔离强度取决于 @anthropic-ai/sandbox-runtime 的底层实现(macOS Seatbelt / Linux 命名空间等);Chatbox 信任这个上游。
  • 拒绝清单是黑名单思路,新出现的敏感路径(如其他云厂商凭据)需要手动补。
  • sandbox_editsed 做替换,对含特殊字符或多行的内容有转义边界(escapeSedBRE 只处理了部分元字符)。

6.9 代码地图

主题文件符号
沙箱管理(初始化/执行)src/main/sandbox/manager.tsinitSandbox / execCommand
命令包裹src/main/sandbox/manager.ts(SandboxManager.wrapWithSandbox)
默认拒绝清单src/shared/task-sandbox.tsTASK_SANDBOX_DENY_READ_PATHS
进程组 killsrc/main/sandbox/manager.tskillTree / killRunningCommand
跨平台可用性src/main/sandbox/manager.tscheckAvailability / toWSLPath
输出截断src/main/sandbox/truncate.tstailTruncate / headTruncate
沙箱工具(模型侧)src/renderer/packages/model-calls/toolsets/sandbox.tssandbox_bash / abortableExec
IPC 暴露src/main/sandbox/ipc-handlers.ts(sandbox:* handlers)