Files

308 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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]]