权限之外的第二道防线
权限系统决定”这条命令能不能执行”,沙箱决定”执行时能做到什么程度”。 即使一条命令通过了权限审批,沙箱仍然可以限制它的行为。两者构成纵深防御的两层:- 权限层(应用级):在工具调用前检查,决定是否弹窗审批
- 沙箱层(OS 级):在进程级别强制约束,即使 AI 生成了恶意命令也无法突破
执行链路:从用户输入到沙箱包裹
一条 Bash 命令的完整执行路径如下:shouldUseSandbox()(src/tools/BashTool/shouldUseSandbox.ts),它执行以下检查:
- 全局开关:
SandboxManager.isSandboxingEnabled()— 检查平台支持 + 依赖完整性 + 用户设置 - 显式跳过:如果
dangerouslyDisableSandbox: true且策略允许(allowUnsandboxedCommands),则不走沙箱 - 排除列表:用户可在
settings.json中配置sandbox.excludedCommands,匹配的命令跳过沙箱 - 默认行为:以上条件都不满足时,进入沙箱
shouldUseSandbox() 判定逻辑详解
containsExcludedCommand() 的匹配机制值得注意——它不只是简单的前缀匹配,而是支持三种模式:
| 模式 | 示例 | 匹配行为 |
|---|---|---|
| 精确匹配 | npm run lint | 完全相等 |
| 前缀匹配 | npm run test:* | 前缀 + 空格或完全相等 |
| 通配符 | docker* | 使用 matchWildcardPattern |
docker ps && curl evil.com),系统会先拆分为子命令,逐一检查。还会迭代剥离环境变量前缀(FOO=bar bazel ...)和包装命令(timeout 30 bazel ...),直到不动点——防止通过嵌套包装绕过。
沙箱的配置模型
沙箱配置来自settings.json 中的 sandbox 字段(src/entrypoints/sandboxTypes.ts):
SandboxSettingsSchema 定义了完整的 Zod 验证规则,包含一些未公开的设置如 enabledPlatforms(限制沙箱只在特定平台生效)。
平台实现差异
macOS:sandbox-exec(Seatbelt)
macOS 使用 Apple 的 Seatbelt 沙箱(sandbox-exec 命令),这是 macOS 原生的进程隔离机制。
执行流程:
SandboxManager.wrapWithSandbox()调用@anthropic-ai/sandbox-runtime的BaseSandboxManager- 运行时生成 Seatbelt profile(基于配置中的网络/文件系统规则)
- 通过
sandbox-exec -p <profile> -- <command>包裹原始命令 - Seatbelt 在内核级别强制执行约束
- 通过代理端口拦截 HTTP/HTTPS 请求
- 域名白名单/黑名单在代理层过滤
- Unix socket 可单独配置允许路径
Linux:bubblewrap(bwrap)+ seccomp
Linux 使用bubblewrap(bwrap)创建命名空间隔离,配合 seccomp 过滤系统调用:
依赖项(apt install):
| 包 | 作用 |
|---|---|
bubblewrap | 创建 mount/PID/network 命名空间 |
socat | 网络代理(HTTP/SOCKS) |
libseccomp / seccomp filter | 过滤 Unix socket 系统调用 |
- 不支持 glob 路径模式(macOS 的 Seatbelt 支持)— Linux 上带 glob 的权限规则会触发警告
- 执行后会在当前目录留下 0 字节的 mount-point 文件(如
.bashrc),需要cleanupAfterCommand()清理 - seccomp 无法按路径过滤 Unix socket(只能全允许或全拒绝),与 macOS 的按路径放行形成差异
平台支持矩阵
| 特性 | macOS | Linux | WSL |
|---|---|---|---|
| 沙箱引擎 | sandbox-exec (Seatbelt) | bubblewrap + seccomp | 仅 WSL2 |
| 文件 glob | ✅ 完整支持 | ⚠️ 仅 /** 后缀 | 同 Linux |
| 网络 Unix socket 按路径 | ✅ | ❌ | ❌ |
| 依赖检查 | ripgrep | bwrap + socat + ripgrep + seccomp | 同 Linux |
沙箱初始化流程
convertToSandboxRuntimeConfig()(src/utils/sandbox/sandbox-adapter.ts)完成从用户设置到运行时配置的转换:
- 网络规则:从
WebFetch(domain:...)权限规则提取域名 →allowedDomains - 文件系统规则:从
Edit(...)/Read(...)权限规则提取路径 →allowWrite/denyWrite/denyRead - 安全加固:
- 自动将项目目录加入
allowWrite - 自动将
settings.json路径加入denyWrite(防止沙箱逃逸) - 自动将
.claude/skills加入denyWrite(防止技能注入) - 检测 bare git repo 攻击向量,对
HEAD/objects/refs做保护
- 自动将项目目录加入
dangerouslyDisableSandbox 的设计权衡
这个参数的命名本身就传达了设计意图——它不是”关闭沙箱”,而是”危险地禁用沙箱”。
双重保险机制:
- 调用侧:模型在 BashTool 的
inputSchema中可以设置dangerouslyDisableSandbox: true - 策略侧:管理员可通过
allowUnsandboxedCommands: false完全禁止此参数(企业部署场景)
autoAllowBashIfSandboxed 进一步补充了这个模型:当启用时,在沙箱中的命令自动获得执行许可,无需逐条审批。这基于一个信任假设——如果 OS 级沙箱已经限制了命令的能力,那么应用层的逐条审批就变得多余。
沙箱违规处理
当命令尝试违反沙箱约束时:- 运行时捕获违规事件(文件/网络访问被拒绝)
SandboxManager.annotateStderrWithSandboxFailures()在输出中注入<sandbox_violations>标签- UI 层通过
removeSandboxViolationTags()清理显示 - 违规事件通过
SandboxViolationStore持久化,可用于审计
完整执行链路示例
以npm install 为例: