308 lines
14 KiB
Markdown
308 lines
14 KiB
Markdown
---
|
||
tags: [沙箱, sandbox, 权限, Bash, 纵深防御, Claude-Code]
|
||
create time: 2026-06-09 22:30
|
||
---
|
||
|
||
# 沙箱机制 - 权限系统之外的第二道防线
|
||
|
||
## 概述
|
||
|
||
Claude Code 的沙箱不是用来替代权限系统,而是为 shell 命令再套一层 OS 级能力边界。权限系统决定"这次工具调用要不要执行",沙箱决定"就算执行了,这个子进程最多能碰到哪些文件、哪些网络目标"。两者组合构成真正的 Defense-in-Depth。
|
||
|
||
## 正文
|
||
|
||
### 实现分层:仓库里的适配器 + 底层运行时
|
||
|
||
沙箱实现分成两层:
|
||
|
||
- **仓库自身负责**:策略、配置转换、启停判断、命令包裹、清理和权限联动
|
||
- **底层隔离**:由外部运行时 `@anthropic-ai/sandbox-runtime` 执行
|
||
|
||
在 `src/utils/sandbox/sandbox-adapter.ts` 里可以清楚看到这条边界:项目导入 `SandboxManager as BaseSandboxManager`、`SandboxViolationStore` 等运行时对象,然后在外面再包一层符合 Claude Code 自身权限模型的适配器。
|
||
|
||
底层隔离在不同平台上的落地:
|
||
|
||
| 平台 | 实现方式 |
|
||
|------|---------|
|
||
| macOS | `sandbox-exec`(Seatbelt profile) |
|
||
| Linux / WSL2 | `bubblewrap + seccomp` |
|
||
| Windows 原生 | 不支持 shell 沙箱 |
|
||
|
||
### 它到底解决什么问题
|
||
|
||
如果只有应用层权限系统,Claude Code 需要在命令执行前尽量判断:
|
||
|
||
- 这条命令是不是只读
|
||
- 会不会写危险路径
|
||
- 会不会连到外网
|
||
- 会不会通过复合命令、重定向、子进程、解释器脚本绕过检查
|
||
|
||
这些检查都很有价值,但本质上仍然是"执行前推断"。而 shell 命令的真实副作用经常取决于运行时行为:
|
||
|
||
- `bash script.sh`
|
||
- `python -c "..."`
|
||
- `make`
|
||
- `npm install`
|
||
- 某个命令再启动另一个子进程
|
||
|
||
> [!info] 沙箱的核心价值
|
||
> 沙箱把这些运行时行为的能力范围压缩到一个明确边界内。即使应用层检查漏了,命令也不能随意写系统目录或访问不允许的网络目标。
|
||
|
||
### 四个核心价值
|
||
|
||
#### 1. 给 shell 一个 OS 级兜底
|
||
|
||
`src/utils/bash/ast.ts` 开头就写得很明确:Bash AST 分析不是沙箱,它只是在判断我们能不能可靠地理解命令结构,不能阻止危险命令真的运行。
|
||
|
||
像 `bash script.sh`、`python -c "..."`、`make`、`npm install` 这类命令,真实副作用都要到运行时才完全展开。沙箱即使前面的分析漏了,进程到了 OS 层以后仍然只能写允许目录、访问允许域名。
|
||
|
||
#### 2. 让"安全边界内"的命令可以少弹窗甚至自动放行
|
||
|
||
默认沙箱白名单里包含当前工作目录和 Claude 临时目录,工作区内的大多数开发命令都能顺畅运行:`npm test`、`rg`、`git status`、工作区内的构建和测试。
|
||
|
||
项目专门提供了 `autoAllowBashIfSandboxed`。核心思路不是"更大胆地信任模型",而是"既然命令已经被 OS 级边界收紧,就没必要再让用户为大量低风险 Bash 命令反复点确认"。
|
||
|
||
#### 3. 把"出错"的后果从系统级破坏降成一次受限失败
|
||
|
||
模型偶尔会出错,应用层规则也可能有漏判。例如 `sudo tee /etc/hosts`、`mv ... ~/.ssh/...`、`curl 外网 | bash`——如果没有运行时约束,可能直接修改系统配置或把未知脚本落到机器上。放进沙箱后,更常见的结果是:因为写权限或网络权限不满足而失败。
|
||
|
||
#### 4. 拦截运行时绕过和逃逸路径
|
||
|
||
`src/utils/sandbox/sandbox-adapter.ts` 专门把一些高风险路径额外加入 `denyWrite`,例如 `settings.json`、`.claude/skills`、一些 bare git repo 相关路径。这样做的目的是:即使命令已经执行,也别让它顺手把护栏本身拆掉。
|
||
|
||
### 设计边界:它保护什么,不保护什么
|
||
|
||
#### 保护对象
|
||
|
||
- Bash / shell 命令执行
|
||
- 在支持平台上的 PowerShell 执行
|
||
- shell 子进程的文件系统写入范围
|
||
- shell 子进程的网络访问范围
|
||
- 一些已知的高风险路径和沙箱逃逸向量
|
||
|
||
#### 不直接保护的对象
|
||
|
||
- `FileEditTool` / `FileWriteTool` 这类直接文件工具
|
||
- 纯应用层的权限弹窗和规则匹配
|
||
- Bash AST 解析本身
|
||
|
||
> [!warning] 重要区分
|
||
> Bash AST 分析不是沙箱。源码自己写得很明确,它只回答"我们能不能可信地提取 argv 结构",并不负责阻止危险命令真正运行。
|
||
|
||
### 哪些场景会走沙箱
|
||
|
||
#### 启动阶段先判断"沙箱能不能用"
|
||
|
||
REPL / CLI 启动时就会先检查当前环境是否具备沙箱条件:
|
||
|
||
1. 当前平台是否受底层 runtime 支持
|
||
2. 依赖是否齐全
|
||
3. `sandbox.enabled` 是否打开
|
||
4. 当前平台是否落在 `enabledPlatforms` 范围内
|
||
|
||
如果用户显式开启了沙箱但当前环境不满足条件,启动期会先给出 warning;如果同时配置了 `sandbox.failIfUnavailable`,则会直接拒绝启动。
|
||
|
||
启动时真的会调用初始化流程,把当前设置转换成 runtime 配置并交给底层 `BaseSandboxManager.initialize(...)`。后续如果设置变化,还会通过 `updateConfig(...)` 热更新。
|
||
|
||
#### BashTool 默认会走
|
||
|
||
只要满足以下条件,Bash 命令默认会进入沙箱:
|
||
|
||
1. 当前平台支持沙箱
|
||
2. 沙箱依赖齐全
|
||
3. `sandbox.enabled` 打开
|
||
4. 当前平台在 `enabledPlatforms` 范围内
|
||
5. 这条命令没有被显式排除
|
||
6. 这次调用没有被允许以 `dangerouslyDisableSandbox` 绕过
|
||
|
||
#### PowerShell 只在支持平台上走
|
||
|
||
| 平台 | 行为 |
|
||
|------|------|
|
||
| Linux / macOS / WSL2 | 可以走沙箱 |
|
||
| Windows 原生 | 不支持沙箱,直接返回 `shouldUseSandbox: false` |
|
||
|
||
#### Hook 命令会复用"网络专用沙箱"
|
||
|
||
Hook 不是完整复用 Bash 那套文件系统限制,而是额外套了一层 network-only sandbox——重点拦网络访问,文件系统不额外收紧。
|
||
|
||
### 哪些场景不会走沙箱
|
||
|
||
#### FileEditTool / FileWriteTool
|
||
|
||
这类工具不是靠 shell 修改文件,而是直接在应用层做文件 I/O,所以它们不通过 `Shell.exec()`,自然也不会被 `wrapWithSandbox()` 包裹。
|
||
|
||
> [!tip] 理解两种拦截路径
|
||
> - "shell 改 `/etc/hosts`"通常是沙箱在 OS 层拦
|
||
> - "FileEdit 改 `/etc/hosts`"通常是权限系统在应用层拦
|
||
|
||
#### 明确排除的命令
|
||
|
||
如果命中 `sandbox.excludedCommands`,这条命令会直接跳过沙箱。支持精确匹配、前缀匹配和通配符匹配三种模式。
|
||
|
||
#### 允许 unsandboxed fallback 的命令
|
||
|
||
如果这次调用显式设置了 `dangerouslyDisableSandbox: true` 并且策略允许 `allowUnsandboxedCommands`,那它也可以不进沙箱。命名故意写得很重:`dangerouslyDisableSandbox`,提醒这是例外路径。
|
||
|
||
### 完整执行链路
|
||
|
||
#### 启动期链路
|
||
|
||
```text
|
||
REPL / CLI 启动
|
||
-> isSandboxingEnabled()
|
||
-> convertToSandboxRuntimeConfig(settings)
|
||
-> BaseSandboxManager.initialize(runtimeConfig, callback)
|
||
-> 设置变化时 BaseSandboxManager.updateConfig(newConfig)
|
||
```
|
||
|
||
#### 命令期链路
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
U["用户请求"] --> CP["BashTool.checkPermissions()"]
|
||
CP --> SUS["shouldUseSandbox(input)"]
|
||
SUS --> SE["Shell.exec(command)"]
|
||
SE --> WS["SandboxManager.wrapWithSandbox(...)"]
|
||
WS --> SP["spawn(wrapped command)"]
|
||
SP --> CL["cleanupAfterCommand()"]
|
||
```
|
||
|
||
这里真正把命令"包进沙箱"的关键点是 `Shell.exec()`。它会在真正 `spawn(...)` 之前调用 `SandboxManager.wrapWithSandbox(...)`,把原始命令改写成底层 runtime 可执行的沙箱命令串。
|
||
|
||
#### 两个容易混淆的判定点
|
||
|
||
- **判定点 A:要不要进沙箱** — `shouldUseSandbox()` 的职责,回答"这条命令要不要被 OS 级沙箱包起来执行?"
|
||
- **判定点 B:这条命令要不要弹权限确认** — 权限系统和 Bash 权限检查的职责,回答"这条命令在应用层看来是 allow、ask 还是 deny?"
|
||
|
||
这两个判定点是并列协作的,不是互相替代的。
|
||
|
||
### 默认沙箱到底限制了什么
|
||
|
||
沙箱运行时配置最终由 `convertToSandboxRuntimeConfig()` 生成。它把项目自己的设置、权限规则和安全加固逻辑转换成底层运行时需要的配置。
|
||
|
||
#### 限制怎么从权限系统推导出来
|
||
|
||
- `WebFetch(domain:...)` 和 `sandbox.network.allowedDomains` 被合并成网络白名单
|
||
- `Edit(...)` / `Read(...)` 这类权限规则被翻译成文件系统读写限制
|
||
- `sandbox.filesystem.allowWrite` / `allowRead` / `denyWrite` / `denyRead` 会继续叠加到最终 runtime 配置上
|
||
|
||
#### 文件系统默认写入范围
|
||
|
||
默认 `allowWrite` 只有两类:
|
||
|
||
- 当前工作目录 `.`
|
||
- Claude 的临时目录
|
||
|
||
这意味着工作区内的构建、测试、生成临时文件通常能正常运行,而根路径如 `/etc/...`、`/usr/...`、`/var/...` 默认不在写白名单里。
|
||
|
||
#### 强制 deny 的路径
|
||
|
||
即使有别的配置,项目还会额外加固一些高风险路径:
|
||
|
||
- settings 文件
|
||
- `.claude/skills`
|
||
- 一些 bare git repo 相关路径
|
||
|
||
这样做的原因是:这些路径一旦可写,攻击者可能反过来修改 Claude Code 自己的配置、技能或 git 行为,从而扩大权限。
|
||
|
||
#### 网络限制
|
||
|
||
网络白名单来自两部分:
|
||
|
||
- `sandbox.network.allowedDomains`
|
||
- `WebFetch(domain:...)` 这类权限规则
|
||
|
||
被允许的域名会进入沙箱网络配置;不在白名单里的访问在运行时会被拦截或触发额外的网络授权流程。
|
||
|
||
### autoAllowBashIfSandboxed 的真实意义
|
||
|
||
这是沙箱设计里最值得注意的开关之一。它表达的信任假设是:
|
||
|
||
> 如果命令已经被 OS 级沙箱约束在安全边界内,那么应用层就没有必要再对大量低风险 Bash 命令逐条弹确认框。
|
||
|
||
当这个开关开启时:
|
||
|
||
1. 命令先检查显式 `deny` / `ask` 规则
|
||
2. 如果没有命中这些硬规则
|
||
3. 且命令确实会在沙箱里执行
|
||
4. 那么 BashTool 可以直接自动允许它运行
|
||
|
||
> [!warning] 边界条件
|
||
> 它只对"真正会进沙箱的命令"生效。命中了 `excludedCommands`、显式使用了 `dangerouslyDisableSandbox: true`、当前平台不支持沙箱——这些命令依然要遵守正常的 `ask` 规则。
|
||
|
||
这也是沙箱存在的一个核心产品价值:不是让更多危险操作通过,而是让更多**受限范围内的常规命令**可以无感运行。
|
||
|
||
### 平台差异
|
||
|
||
| 平台 | 实现 | 特点 |
|
||
|------|------|------|
|
||
| macOS | `sandbox-exec` | 路径和网络规则通过 Seatbelt profile 落地,原生 OS 级进程隔离 |
|
||
| Linux | `bubblewrap + seccomp` | 建立 mount / PID / network 等隔离,glob 路径支持比 macOS 弱 |
|
||
| WSL | 仅 WSL2 | WSL1 视为不支持平台 |
|
||
| Windows 原生 | 不支持 | 只能依赖权限系统和工具级检查 |
|
||
|
||
### 工作区内外:应用层与沙箱层如何配合
|
||
|
||
#### 工作区内路径
|
||
|
||
工作区内路径通常有两层保护:应用层权限检查 + 沙箱默认允许写当前工作目录。这使得"工作区内构建/测试/格式化/生成文件"成为最顺滑的一条路径。
|
||
|
||
#### 工作区外路径
|
||
|
||
工作区外路径则更严格:应用层通常会视为高风险要求确认或阻止,即使应用层允许,如果不在沙箱白名单里运行时也会失败。
|
||
|
||
### 用户真的会看到什么
|
||
|
||
被拦截至少有三类体验:
|
||
|
||
| 类型 | 时机 | 用户看到 |
|
||
|------|------|---------|
|
||
| 执行前的权限确认 | 命令还没运行 | 标准权限对话框(Bash/FileEdit/FileWrite) |
|
||
| 执行中的沙箱违规 | 命令已进入沙箱 | 命令失败 + stderr 附加 `<sandbox_violations>` 标签 |
|
||
| 网络越界请求 | 运行时 | 专门的网络授权对话框("Network request outside of sandbox") |
|
||
|
||
### 常见误区
|
||
|
||
> [!warning] 误区 1:沙箱会保护所有文件修改
|
||
> 不是。它主要保护 **shell 子进程**。直接文件编辑工具走的是应用层权限系统,不是 shell 沙箱。
|
||
|
||
> [!warning] 误区 2:只要启用了沙箱,就不会再需要权限系统
|
||
> 不是。沙箱只限制进程能力,不负责解释用户意图、路径安全语义、工具模式、审批体验。
|
||
|
||
> [!warning] 误区 3:危险操作被沙箱拦住说明应用层检查没价值
|
||
> 不是。应用层检查的价值在于更早提示、更好的用户体验、更细的语义判断、对不走 shell 的工具同样生效。沙箱负责的是最终兜底。
|
||
|
||
### 推荐的阅读路径
|
||
|
||
如果你想继续顺着源码深入,推荐按下面顺序看:
|
||
|
||
1. `packages/builtin-tools/src/tools/BashTool/shouldUseSandbox.ts`
|
||
2. `src/utils/Shell.ts`
|
||
3. `src/utils/sandbox/sandbox-adapter.ts`
|
||
4. `src/utils/permissions/permissions.ts`
|
||
5. `packages/builtin-tools/src/tools/BashTool/bashPermissions.ts`
|
||
6. `src/utils/permissions/pathValidation.ts`
|
||
7. `src/utils/permissions/filesystem.ts`
|
||
|
||
### FAQ
|
||
|
||
> [!question] Linux 下 `echo hi > /etc/hosts` 会怎样?
|
||
> 如果是 BashTool:通常会进沙箱,默认沙箱不允许写 `/etc`,所以命令会在运行时失败。如果是 FileEditTool:不进沙箱,通常会在应用层文件权限检查里先被拦下。
|
||
|
||
> [!question] Windows 下改 `C:\Windows\System32\drivers\etc\hosts` 会怎样?
|
||
> 在 Windows 原生环境里,通常没有这套 shell 沙箱兜底,所以主要依赖应用层权限系统和工具自己的检查逻辑。
|
||
|
||
> [!question] 既然沙箱这么强,为什么还保留 `dangerouslyDisableSandbox`?
|
||
> 因为有些真实开发任务确实需要越过默认边界,例如访问未加入白名单的工具链目录、调试系统级环境、做管理员明确允许的例外操作。但项目把这个入口做得非常显眼,也允许管理员通过策略直接禁掉。
|
||
|
||
> [!question] 什么时候最能感受到沙箱的价值?
|
||
> 当你开启 `autoAllowBashIfSandboxed` 时最明显。这时大量工作区内命令可以少弹窗甚至不弹窗,但即使模型偶尔给出过界命令,系统级写入和网络能力仍然被边界限制住。
|
||
|
||
## 关联笔记
|
||
|
||
- [[why-safety-matters|AI 安全至关重要]]
|
||
- [[permission-model|权限模型]]
|
||
- [[plan-mode|计划模式]]
|
||
- [[auto-mode|Auto Mode]]
|