为什么需要沙箱:第三条路

没有沙箱时你只有两个选项:逐条审批 Bash 命令(安全但烦),或大范围放行(顺畅但失控)。沙箱给出第三条路:把边界从「哪条命令」改成「什么资源」。你不再关心 Claude 跑的是 npm test 还是 make build,只关心它写不出工作目录、连不上未放行的域名——这个约束由操作系统对命令及其全部子进程强制执行。

平台支持:macOS 零安装(内置 Seatbelt 框架);Linux 与 WSL2 需要 bubblewrap(文件系统隔离)和 socat(网络代理转发)两个包;原生 Windows 不支持。可选的 seccomp 过滤器 (npm i -g @anthropic-ai/sandbox-runtime)额外提供 Unix 域套接字拦截。

上手:/sandbox 面板

/sandbox

面板三个标签页:Mode(审批方式)、Overrides(沙箱外回退开关,即 allowUnsandboxedCommands)、Config(当前生效配置)。Linux 缺依赖时会出现Dependencies 标签页列出缺什么——装完重启 Claude Code 再看。选好的模式存进项目的 .claude/settings.local.json(自动加入全局 gitignore);想全部项目默认开启,在 ~/.claude/settings.json 里设 sandbox.enabled 为 true。

Ubuntu 24.04 及以后注意:默认 AppArmor 策略会拦 bubblewrap 创建用户命名空间。先查:

sysctl kernel.apparmor_restrict_unprivileged_userns
# 返回 1 才需要按官方文档给 bwrap 加 AppArmor profile 并 reload

两种模式与 auto-allow 的精确边界

两种模式下沙箱的文件/网络限制完全相同,区别只在审批:auto-allow 对可沙箱化命令直接放行,regular permissions 保留全部常规弹窗。auto-allow 也不是无条件放行,四条例外记牢:

  • 显式 deny 规则永远生效。
  • rm / rmdir 碰关键路径仍走常规审批。
  • 内容级 ask 规则(如 Bash(git push *))对沙箱内命令照样强制弹窗
  • 裸的 Bash ask 规则对沙箱内命令跳过、对回落命令仍生效;plan 模式下不跳过。

还有一个容易意外的行为:auto-allow 独立于权限模式生效——即使在 Manual 模式,沙箱边界内的 Bash 命令也会免审批执行(文件编辑工具仍会弹窗)。理解成「沙箱内的 Bash 有自己的快速通道」即可。

逃生舱与 Strict 模式

有些命令在沙箱里注定跑不了。Claude Code 会把沙箱拦截的具体路径或主机写进命令结果里,Claude 读到后可能带 dangerouslyDisableSandbox 参数在沙箱外重试——重试走常规权限流程,Manual 模式下你会看到标题为「Bash command (unsandboxed)」的确认框,一眼可辨。

// 三种收紧姿势(settings.json)
{
  "sandbox": {
    "enabled": true,
    // 1. 禁用沙箱外重试:Strict sandbox mode
    "allowUnsandboxedCommands": false,
    // 2. 平台不支持时硬失败而不是静默降级(安全门禁场景)
    "failIfUnavailable": true
  }
}
// 3. 想对每次沙箱外重试强制弹窗(含 auto 模式):
//    加 ask 规则 Bash(dangerouslyDisableSandbox:true)

文件系统规则:allow / deny 的精确语义

默认可写范围:工作目录、--add-dir 添加的目录、会话临时目录($TMPDIR 已被指向它)。工具要写别处, 用 allowWrite 精确放行而不是把命令排除出沙箱:

{
  "sandbox": {
    "enabled": true,
    "filesystem": {
      "allowWrite": ["~/.kube", "/tmp/build"],
      "denyRead": ["~/"],
      "allowRead": ["."]
    }
  }
}

重叠规则按「更具体者胜」解析,三个官方例子值得背下来:

规则组合效果
denyRead ~/ + allowRead ~/projects整个家目录禁读,仅 ~/projects 放行
allowRead ~/ + denyRead ~/.env家目录可读,但 ~/.env 始终被挡——宽 allow 不会静默重曝密钥
allowRead ~/ + denyRead ~/**/.env家目录下所有 .env 通配挡住,其余可读

两个坑:路径前缀规则与 permissions 的 Read/Edit 规则不是一套语法(这里 /tmp/build 就是绝对路径,没有 // 约定);相对路径在项目设置里相对项目根、在用户设置里相对 ~/.claude——同一段配置放错文件语义就变了。

网络隔离与凭证保护

网络层默认按域名放行:命令第一次要连新域名时弹批准(auto 模式交给分类器),也可预配 allowedDomains 白名单(支持 *.npmjs.org 通配)。真正该重点配置的是凭证——沙箱没有内置凭证黑名单

{
  "sandbox": {
    "enabled": true,
    "credentials": {
      "files": [
        { "path": "~/.aws/credentials", "mode": "deny" },
        { "path": "~/.ssh", "mode": "deny" }
      ],
      "envVars": [
        { "name": "GITHUB_TOKEN", "mode": "mask", "injectHosts": ["api.github.com"] },
        { "name": "NPM_TOKEN", "mode": "deny" }
      ]
    }
  }
}
  • deny:文件禁读、环境变量在沙箱命令前移除。简单彻底,但依赖该变量的工具(gh、npm)会跟着失效。
  • mask:命令只见每会话随机的占位符,真值由沙箱代理在发往 injectHosts 的出站请求上替换注入——工具照常认证,而命令本身和它的日志从头到尾拿不到真凭证。需要配 network.tlsTerminate 让代理终止 TLS。

deny 条目跨设置层级只增不减:任何 scope 都能加一条,没有 scope 能删掉别人加的。团队管控上, 管理设置可强制 sandbox.enabled、锁定 filesystem 配置、用 allowManagedDomainsOnly 禁止开发者自行扩大域名白名单。

落地清单

  1. 个人:~/.claude/settings.json 开 sandbox.enabled + auto-allow,体验一周再按摩擦点微调 allowWrite。
  2. 凭证:至少把 ~/.ssh、~/.aws/credentials、云厂商 token 文件列进 credentials deny;高频用的 token 改 mask。
  3. 审批底线:git push、部署、删库类命令保留内容级 ask 规则——沙箱不替你守这道门。
  4. 团队:管理设置强制开启 + failIfUnavailable,把「没沙箱就不跑」变成硬门禁。
  5. 排错:看结果里沙箱报告的被拦路径/主机,优先加精确的 allow 规则,最后才考虑 excludedCommands。

沙箱的正确用法不是「更严」,而是把审批留给真正不可逆的动作:边界内的探索、构建、测试全速自动跑, 写圈外、连新域名、推远端才过人。配好之后你会发现权限弹窗少了一个量级,而实际安全边界反而更清晰了。