
Claude Code 的沙箱是一个 OS 级别的命令隔离机制,目标是将 AI 执行的 Bash 命令限制在安全的边界内,防止:
[!IMPORTANT]
沙箱不是应用层的权限检查(那是 Permission System),而是底层 OS 沙箱包装器,在 macOS 上使用sandbox-exec,Linux 上使用bubblewrap (bwrap)+seccomp。
graph TB
subgraph "用户层"
A["/sandbox 命令"] --> B["SandboxSettings UI"]
C["Settings JSON<br>sandbox.enabled"] --> D["SandboxManager"]
end
subgraph "核心适配层"
D --> E["sandbox-adapter.ts<br>(Claude CLI 适配器)"]
E --> F["@anthropic-ai/sandbox-runtime<br>(底层运行时)"]
end
subgraph "工具集成层"
G["BashTool"] --> H["shouldUseSandbox()"]
H --> I["wrapWithSandbox()"]
I --> F
end
subgraph "安全控制"
J["文件系统限制"] --> E
K["网络域名白名单"] --> E
L["违规事件监控"] --> E
M["Git bare repo 防护"] --> E
end
subgraph "UI 反馈"
N["SandboxViolationExpandedView"]
O["SandboxPermissionRequest"]
P["SandboxDoctorSection"]
end
F --> N
F --> O
sandboxTypes.ts📝 文件: sandboxTypes.ts
这是沙箱配置的 单一数据源(Single Source of Truth),使用 Zod schema 定义了三个核心配置结构:
| Schema | 用途 |
|---|---|
SandboxNetworkConfigSchema | 网络隔离配置:允许的域名、Unix Socket、本地绑定、代理端口 |
SandboxFilesystemConfigSchema | 文件系统配置:读写白名单/黑名单路径 |
SandboxSettingsSchema | 顶层沙箱配置:启用开关、自动允许、排除命令列表等 |
关键设计点:
// 网络层 — 支持企业级管控
allowManagedDomainsOnly // 企业模式:只允许托管域名
allowUnixSockets // macOS 专有:Unix socket 白名单
httpProxyPort // HTTP 代理端口(MITM 代理支持)
// 文件系统层 — 分层权限
allowWrite / denyWrite // 写入白名单/黑名单
denyRead / allowRead // 读取黑名单/白名单(allowRead 优先于 denyRead)
// 顶层开关
enabled // 主开关
failIfUnavailable // 严格模式(企业部署用)
autoAllowBashIfSandboxed // 沙箱内自动批准 Bash 命令
allowUnsandboxedCommands // 是否允许跳出沙箱
excludedCommands // 排除命令列表(非安全边界)
enableWeakerNetworkIsolation // macOS: 放松 trustd 限制(Go CLI 兼容)
sandbox-adapter.ts📝 文件: sandbox-adapter.ts
这是沙箱系统的 最核心文件(~986行),作为 Claude CLI 与底层 @anthropic-ai/sandbox-runtime 之间的桥接层。主要职责:
convertToSandboxRuntimeConfig()将 Claude Code 的 settings 格式转换为沙箱运行时理解的 SandboxRuntimeConfig:
Claude Code Settings ──> convertToSandboxRuntimeConfig() ──> SandboxRuntimeConfig
网络域名提取:
permissions.allow 中提取 WebFetch(domain:xxx) 规则 → 加入 allowedDomainspermissions.deny 中提取 → 加入 deniedDomainsallowManagedDomainsOnly 企业模式文件系统路径构建:
. + Claude 临时目录.claude/skills 的写入(防止技能注入)Edit(path) 权限规则提取可写路径Read(path) 权限规则提取读取限制--add-dir 的额外目录两套不同的路径语义:
| 路径前缀 | Permission Rules 中的含义 | sandbox.filesystem 中的含义 |
|---|---|---|
//path | 绝对路径 (/path) | 绝对路径 (/path) |
/path | 相对于 settings 文件目录 | 绝对路径(直接使用) |
~/path | 透传给沙箱运行时 | 展开为 home 目录 |
./path | 透传 | 相对于 settings 文件目录 |
[!WARNING]
这是一个易混淆的设计。/Users/foo/.cargo在 permissions 规则中会被错误解析为 settings 相对路径!这也是 #30067 bug fix 的原因。
攻击场景:
攻击者在 cwd 放置 HEAD + objects/ + refs/ + hooks/ + config
→ Git 的 is_git_directory() 误认为 bare repo
→ core.fsmonitor 恶意配置被执行
→ 沙箱逃逸
防护策略(双层):
denyWrite (只读绑定)bareGitRepoScrubPaths,命令执行后立即 rmSync 清理// sandbox-adapter.ts:404
function scrubBareGitRepoFiles(): void {
for (const p of bareGitRepoScrubPaths) {
try { rmSync(p, { recursive: true }) }
catch { /* ENOENT 是正常情况 */ }
}
}
async function initialize(sandboxAskCallback?) {
// 1. 检查 Git worktree 路径(一次性)
// 2. 读取 settings → 转换为 SandboxRuntimeConfig
// 3. 调用 BaseSandboxManager.initialize()
// 4. 订阅 settings 变更 → 自动执行 BaseSandboxManager.updateConfig()
}
[!NOTE]
配置热更新通过settingsChangeDetector.subscribe()实现,当用户修改 settings 时,沙箱规则实时刷新,无需重启。
SandboxManager 接口export const SandboxManager: ISandboxManager = {
// 🟢 自定义实现(Claude CLI 特有逻辑)
initialize,
isSandboxingEnabled, // 综合判断:平台 + 依赖 + 设置
getSandboxUnavailableReason, // 诊断信息(#34044 fix)
isAutoAllowBashIfSandboxedEnabled,
areUnsandboxedCommandsAllowed,
isSandboxRequired, // failIfUnavailable
areSandboxSettingsLockedByPolicy,
setSandboxSettings, // 写入 localSettings
wrapWithSandbox, // 包装命令
refreshConfig, // 立即刷新
// 🔵 直接转发给底层 sandbox-runtime
getFsReadConfig,
getFsWriteConfig,
getNetworkRestrictionConfig,
getSandboxViolationStore,
annotateStderrWithSandboxFailures,
// ...
// 🔴 特殊处理
cleanupAfterCommand: () => {
BaseSandboxManager.cleanupAfterCommand()
scrubBareGitRepoFiles() // 额外清理 bare repo 植入
},
}
shouldUseSandbox.ts📝 文件: shouldUseSandbox.ts
决定一个 Bash 命令是否应该在沙箱内运行的核心逻辑:
flowchart TD
A["shouldUseSandbox(input)"] --> B{"沙箱已启用?"}
B -- No --> Z["return false"]
B -- Yes --> C{"dangerouslyDisableSandbox<br>且允许跳出?"}
C -- Yes --> Z
C -- No --> D{"命令为空?"}
D -- Yes --> Z
D -- No --> E{"命令在排除列表中?"}
E -- Yes --> Z
E -- No --> Y["return true ✅"]
排除命令匹配 (containsExcludedCommand()) 的精妙之处:
docker ps && curl evil.com 拆分后逐一检查FOO=bar bazel run → 先检查完整,再检查 bazel runtimeout 30 bazel run → bazel runtimeout 300 FOO=bar bazel run)[!NOTE]
excludedCommands是 用户便利功能,不是安全边界。注释明确说明:"It is not a security bug to be able to bypass excludedCommands"。真正的安全控制是沙箱权限系统本身。
anthropic-sandbox-runtime.ts📝 文件: anthropic-sandbox-runtime.ts
因为 @anthropic-ai/sandbox-runtime 是 Anthropic 内部包,源码还原版提供了一个 no-op stub:
export class SandboxManager {
static isSupportedPlatform(): boolean { return false }
static isSandboxingEnabled(): boolean { return false }
static async wrapWithSandbox(command: string): Promise<string> {
return command // 直接返回原命令,不做任何包装
}
// ...全部返回空/默认值
}
[!CAUTION]
这意味着在源码还原版中,沙箱功能实际上是完全禁用的。所有命令都会 bypass 沙箱直接执行。要理解真实的沙箱行为,需要参考 adapter 层的逻辑设计。
SandboxSettings.tsx📝 文件: SandboxSettings.tsx
通过 /sandbox 命令触发,提供三种模式选择:
| 模式 | 说明 |
|---|---|
| Auto-allow | 沙箱内自动批准,沙箱外回退到常规权限 |
| Regular | 沙箱内运行,但每次仍需权限确认 |
| Disabled | 完全关闭沙箱 |
包含 4 个 Tab 页面:
SandboxPermissionRequest.tsx📝 文件: SandboxPermissionRequest.tsx
当沙箱拦截到一个不在白名单中的网络请求时,弹出交互式对话框:
┌─ Network request outside of sandbox ─────┐
│ Host: api.example.com │
│ │
│ Do you want to allow this connection? │
│ ○ Yes │
│ ○ Yes, and don't ask again for api.example│
│ ○ No, and tell Claude what to do (esc) │
└───────────────────────────────────────────┘
[!NOTE]
如果启用了allowManagedDomainsOnly企业策略,"don't ask again" 选项会被隐藏,用户无法自行永久放行域名。
SandboxViolationExpandedView.tsx📝 文件: SandboxViolationExpandedView.tsx
实时展示沙箱阻止的操作,订阅 SandboxViolationStore 显示最近 10 条:
⧈ Sandbox blocked 3 total operations
1:30:45pm npm run build: network connect to registry.npmjs.org:443
1:30:46pm curl: network connect to evil.com:80
… showing last 2 of 3
flowchart LR
A["~/.claude/settings.json<br>(localSettings)"] --> G["getSettings_DEPRECATED()"]
B[".claude/settings.json<br>(projectSettings)"] --> G
C["policySettings<br>(企业托管)"] --> G
D["flagSettings<br>(Feature Flags)"] --> G
G --> H["convertToSandboxRuntimeConfig()"]
H --> I["SandboxRuntimeConfig"]
I --> J["BaseSandboxManager.initialize()"]
K["settingsChangeDetector"] -.-> |"热更新"| H
设置优先级(高 → 低):
flagSettings — Feature Flag 控制policySettings — 企业策略文件projectSettings — 项目级 .claude/settings.jsonlocalSettings — 用户级 ~/.claude/settings.local.json| 层级 | 机制 | 具体措施 |
|---|---|---|
| OS 层 | sandbox-exec / bwrap | 进程级隔离,底层沙箱包装命令 |
| 文件系统 | 路径白名单/黑名单 | settings.json + .claude/skills 始终 deny-write |
| 网络 | 域名白名单 + 代理 | HTTP/SOCKS proxy + 未知域名交互式询问 |
| Git 安全 | Bare repo 防护 | 已存在文件 readonly + 新文件后清理 |
| 配置锁定 | 企业策略 | policySettings 覆盖本地 + failIfUnavailable |
| 命令过滤 | excludedCommands | 复合命令拆分 + 环境变量剥离 + 包装器剥离 |
| 诊断 | 违规监控 + Doctor | 实时违规展示 + 依赖检查 |
| 文件 | 作用 |
|---|---|
| sandboxTypes.ts | 沙箱配置 Zod Schema (SSoT) |
| sandbox-adapter.ts | 核心适配器 (986行) |
| shouldUseSandbox.ts | BashTool 沙箱决策 |
| anthropic-sandbox-runtime.ts | 底层运行时 Stub |
| SandboxSettings.tsx | 设置 UI 入口 |
| SandboxPermissionRequest.tsx | 网络权限弹窗 |
| SandboxViolationExpandedView.tsx | 违规事件展示 |
| sandbox-toggle.tsx | /sandbox 命令实现 |