
从
cli.tsx入口到Shell.ts执行的完整沙箱调用链
flowchart TD
A["cli.tsx:310<br>await cliMain()"] --> B["main.tsx:585<br>main()"]
B --> C["main.tsx:201<br>import SandboxManager"]
B --> D["main.tsx:307-316<br>logStartupTelemetry()<br>记录沙箱状态"]
B --> E["REPL.tsx 渲染"]
E --> F["REPL.tsx:2337-2343<br>SandboxManager.initialize()"]
E --> G["REPL.tsx:2312-2336<br>沙箱不可用检测"]
F --> H["命令执行流程"]
H --> I["BashTool.tsx:896<br>shouldUseSandbox(input)"]
I --> J["Shell.ts:259-265<br>SandboxManager.wrapWithSandbox()"]
J --> K["Shell.ts:316<br>spawn(包装后命令)"]
K --> L["Shell.ts:391-393<br>SandboxManager.cleanupAfterCommand()"]
cli.tsx → main.tsxconst { main: cliMain } = await import('../main.js');
await cliMain();
入口文件不直接涉及沙箱。所有沙箱逻辑从 main.tsx 开始。
main.tsx — 导入和遥测📝 main.tsx:201 — SandboxManager 导入
import { SandboxManager } from './utils/sandbox/sandbox-adapter.js';
📝 main.tsx:314-316 — 启动遥测中记录沙箱状态
sandbox_enabled: SandboxManager.isSandboxingEnabled(),
are_unsandboxed_commands_allowed: SandboxManager.areUnsandboxedCommandsAllowed(),
is_auto_bash_allowed_if_sandbox_enabled: SandboxManager.isAutoAllowBashIfSandboxedEnabled(),
📝 main.tsx:3012-3017 — AppState 中的沙箱状态
workerSandboxPermissions: { ... },
pendingSandboxRequest: null,
📝 文件: REPL.tsx
#34044 修复)useEffect(() => {
const reason = SandboxManager.getSandboxUnavailableReason();
if (!reason) return;
// 如果配置要求沙箱但不可用 → 强制退出
if (SandboxManager.isSandboxRequired()) {
process.stderr.write(`\nError: sandbox required but unavailable: ${reason}\n`);
gracefulShutdownSync(1, 'other');
return;
}
// 否则:记录日志 + 显示通知条
logForDebugging(`sandbox disabled: ${reason}`, { level: 'warn' });
addNotification({
key: 'sandbox-unavailable',
jsx: <><Text color="warning">sandbox disabled</Text><Text dimColor> · /sandbox</Text></>
});
}, [addNotification]);
if (SandboxManager.isSandboxingEnabled()) {
SandboxManager.initialize(sandboxAskCallback).catch(err => {
process.stderr.write(`\n❌ Sandbox Error: ${errorMessage(err)}\n`);
gracefulShutdownSync(1, 'other');
});
}
当沙箱拦截未授权网络请求时的处理流程:
flowchart TD
A["沙箱拦截网络请求"] --> B{"是 Swarm Worker?"}
B -- Yes --> C["发送到 Leader 的 mailbox"]
C --> D["注册回调等待 Leader 回复"]
B -- No --> E["队列化本地权限弹窗"]
E --> F{"连接了 Bridge?"}
F -- Yes --> G["同时转发到远程 Bridge<br>race 模式:哪边先回复算哪边"]
F -- No --> H["仅显示本地 TUI 弹窗"]
沙箱权限弹窗在所有对话框中的优先级:
| 优先级 | 对话框类型 |
|---|---|
| 1 | message-selector |
| 2 | sandbox-permission ← 沙箱请求(高于工具权限!) |
| 3 | tool-permission |
| 4 | prompt |
| 5 | worker-sandbox-permission |
| 6 | elicitation |
| ... | 其他(cost, idle-return 等) |
[!IMPORTANT]
沙箱权限弹窗优先级高于普通工具权限弹窗,即使用户正在输入也会立即显示。
📝 文件: BashTool.tsx
dangerouslyDisableSandbox 参数dangerouslyDisableSandbox: semanticBoolean(z.boolean().optional())
.describe('Set this to true to dangerously override sandbox mode...')
[!WARNING]
BashTool.tsx:249-252 —_simulatedSedEdit被故意从 schema 中 omit,因为暴露它会让模型绕过权限检查和沙箱。
userFacingName// 环境变量控制是否在 UI 上显示 "SandboxedBash" 标签
return isEnvTruthy(process.env.CLAUDE_CODE_BASH_SANDBOX_SHOW_INDICATOR)
&& shouldUseSandbox(input) ? 'SandboxedBash' : 'Bash';
shouldUseSandbox 传递const shellCommand = await exec(command, abortController.signal, 'bash', {
// ...
shouldUseSandbox: shouldUseSandbox(input), // ← 关键决策点
shouldAutoBackground
});
// 在输出中注入沙箱违规信息(model 可以看到,用于解释错误)
const outputWithSbFailures = SandboxManager.annotateStderrWithSandboxFailures(
input.command, result.stdout || ''
);
shouldUseSandbox.ts📝 文件: shouldUseSandbox.ts
flowchart TD
A["shouldUseSandbox(input)"] --> B{"SandboxManager<br>.isSandboxingEnabled()?"}
B -- No --> Z["return false"]
B -- Yes --> C{"input.dangerouslyDisableSandbox<br>&& areUnsandboxedCommandsAllowed()?"}
C -- Yes --> Z
C -- No --> D{"command 为空?"}
D -- Yes --> Z
D -- No --> E{"containsExcludedCommand()?"}
E -- Yes --> Z
E -- No --> Y["return true ✅ 使用沙箱"]
containsExcludedCommand() 的处理步骤:
&&, ||, ;)FOO=bar cmd → cmd)timeout 30 cmd → cmd)📝 文件: Shell.ts
exec() 函数接收 sandbox 标志export type ExecOptions = {
shouldUseSandbox?: boolean // ← 从 BashTool 传入
// ...
}
const { commandString, cwdFilePath } = await provider.buildExecCommand(command, {
id,
sandboxTmpDir: shouldUseSandbox ? sandboxTmpDir : undefined,
useSandbox: shouldUseSandbox ?? false,
});
wrapWithSandbox() 包装命令if (shouldUseSandbox) {
commandString = await SandboxManager.wrapWithSandbox(
commandString,
sandboxBinShell, // macOS: "bash/zsh", PowerShell: "/bin/sh"
undefined,
abortSignal,
);
// 创建沙箱的临时目录(安全权限 0o700)
try {
await fs.mkdir(sandboxTmpDir, { mode: 0o700 });
} catch (error) { /* ... */ }
}
// 沙箱化的 PowerShell:先由 powershellProvider.buildExecCommand 把 pwsh 命令
// 封装为 base64 编码,再用 /bin/sh 作为沙箱内 shell
const isSandboxedPowerShell = shouldUseSandbox && shellType === 'powershell'
const sandboxBinShell = isSandboxedPowerShell ? '/bin/sh' : binShell
void shellCommand.result.then(async result => {
// Linux bwrap 在宿主上创建 0-byte 占位文件,命令结束后同步清理
if (shouldUseSandbox) {
SandboxManager.cleanupAfterCommand()
}
// ...更新 CWD 等
});
sandbox-adapter.ts📝 文件: sandbox-adapter.ts (~986 行)
SandboxManager 导出接口总览| 方法 | 来源 | 作用 |
|---|---|---|
initialize(askCallback) | 自定义 | 检测 git worktree → 转换 config → 初始化运行时 → 订阅设置变更 |
isSandboxingEnabled() | 自定义 | 综合判断:平台 + 依赖 + 设置 |
getSandboxUnavailableReason() | 自定义 | 返回沙箱不可用原因(#34044修复) |
wrapWithSandbox(cmd, shell) | 转发 | 调用底层运行时包装命令 |
cleanupAfterCommand() | 增强 | 底层清理 + scrubBareGitRepoFiles() |
setSandboxSettings(settings) | 自定义 | 写入 localSettings |
refreshConfig() | 自定义 | 立即刷新沙箱配置 |
annotateStdErr... | 转发 | 在 stderr 中注入违规标签 |
getSandboxViolationStore() | 转发 | 获取违规事件存储 |
getFsReadConfig() | 转发 | 获取文件系统读限制 |
getFsWriteConfig() | 转发 | 获取文件系统写限制 |
getNetworkRestrictionConfig() | 转发 | 获取网络限制配置 |
convertToSandboxRuntimeConfig() — 配置转换核心将 Claude Code 的多层配置合并为底层运行时理解的 SandboxRuntimeConfig:
permissions.allow[WebFetch(domain:xxx)] ──┐
permissions.deny[WebFetch(domain:xxx)] ──┤
sandbox.network.allowedDomains ──┤──→ SandboxRuntimeConfig.network
sandbox.network.allowManagedDomainsOnly ──┘
permissions.allow[Edit(path)] ──┐
sandbox.filesystem.allowWrite ──┤──→ SandboxRuntimeConfig.filesystem
sandbox.filesystem.denyWrite ──┤
settings.json 路径 (硬编码 deny) ──┤
.claude/skills 路径 (硬编码 deny) ──┘
始终 deny-write(无法覆盖):
settings.json / settings.local.json.claude/skills 目录anthropic-sandbox-runtime.ts📝 文件: anthropic-sandbox-runtime.ts
export class SandboxManager {
static isSupportedPlatform(): boolean { return false }
static async wrapWithSandbox(command: string): Promise<string> {
return command // No-op: 原样返回,不做任何包装
}
// ... 全部空实现
}
[!CAUTION]
源码还原版中沙箱实际完全禁用。所有命令 bypass 直接执行。
/sandbox 命令入口flowchart TD
A["/sandbox"] --> B{"参数?"}
B -- 无 --> C["SandboxSettings UI<br>交互式菜单"]
B -- "exclude pattern" --> D["addToExcludedCommands()"]
B -- 其他 --> E["Unknown subcommand 错误"]
C --> F["4 个 Tab 页"]
F --> G["Mode: auto-allow / regular / disabled"]
F --> H["Dependencies: 依赖检查"]
F --> I["Overrides: 排除命令管理"]
F --> J["Config: 当前生效配置"]
SandboxSettings.tsx三种沙箱模式:
| 模式 | 描述 | 对应设置 |
|---|---|---|
| Auto-allow | 沙箱内命令自动批准 | enabled: true, autoAllowBashIfSandboxed: true |
| Regular | 沙箱内仍需权限确认 | enabled: true, autoAllowBashIfSandboxed: false |
| Disabled | 关闭沙箱 | enabled: false |
SandboxConfigTab.tsx展示当前生效的沙箱配置:
SandboxOverridesTab.tsx两种覆盖模式:
dangerouslyDisableSandbox 重试excludedCommands 中SandboxDoctorSection.tsx在 /doctor 命令中显示沙箱依赖状态(sandbox-exec / bwrap / seccomp)。
SandboxPermissionRequest.tsx📝 SandboxPermissionRequest.tsx
交互选项:
allowManagedDomainsOnly 时隐藏)SandboxViolationExpandedView.tsx📝 SandboxViolationExpandedView.tsx
订阅 SandboxViolationStore 展示被拦截的操作记录。
SandboxPromptFooterHint.tsx在输入框底部实时显示违规通知(5秒后自动消失):
⧈ Sandbox blocked 3 operations · ctrl+o for details · /sandbox to disable
sandbox-ui-utils.ts & BashToolResultMessage.tsx// 从显示文本中移除 <sandbox_violations>...</sandbox_violations> 标签
export function removeSandboxViolationTags(text: string): string {
return text.replace(/<sandbox_violations>[\s\S]*?<\/sandbox_violations>/g, '')
}
📝 BashToolResultMessage.tsx:24-38
UI 渲染时清理违规标签(确保 model 能看到违规信息解释错误,但 UI 上不显示原始 XML)。
sandboxTypes.ts📝 文件: sandboxTypes.ts
三个核心 Zod Schema:
SandboxSettingsSchema
├── enabled: boolean // 主开关
├── autoAllowBashIfSandboxed // 沙箱内自动批准
├── failIfUnavailable // 严格模式
├── allowUnsandboxedCommands // 允许跳出沙箱
├── enabledPlatforms // 平台白名单
├── enableWeakerNetworkIsolation // macOS trustd 放松
├── excludedCommands // 排除命令列表
├── network: SandboxNetworkConfigSchema
│ ├── allowedDomains
│ ├── allowManagedDomainsOnly
│ ├── allowUnixSockets
│ ├── httpProxyPort
│ └── localBindPorts
└── filesystem: SandboxFilesystemConfigSchema
├── allowWrite / denyWrite
├── denyRead / allowRead
└── (allowRead 优先于 denyRead)
| 文件 | 行数 | 沙箱相关内容 |
|---|---|---|
| sandbox-adapter.ts | ~986 | 核心适配器:配置转换、安全防护、生命周期管理 |
| shouldUseSandbox.ts | ~154 | 沙箱决策引擎 |
| sandboxTypes.ts | ~157 | 配置类型定义 (Zod Schema) |
| anthropic-sandbox-runtime.ts | ~107 | 底层运行时 Stub |
| Shell.ts | ~475 | 命令执行:wrapWithSandbox + cleanupAfterCommand |
| sandbox-ui-utils.ts | 13 | UI 工具函数 |
| 文件 | 关键行 | 沙箱相关内容 |
|---|---|---|
| BashTool.tsx | L242, L502, L710, L896 | Schema定义、UI标签、违规标注、决策传递 |
| BashToolResultMessage.tsx | L24-38 | 输出中违规标签清理 |
| 文件 | 作用 |
|---|---|
| SandboxSettings.tsx | 设置面板主框架(4 Tab) |
| SandboxConfigTab.tsx | 当前生效配置查看 |
| SandboxOverridesTab.tsx | 覆盖规则管理 |
| SandboxDependenciesTab.tsx | 依赖检查页 |
| SandboxDoctorSection.tsx | /doctor 沙箱段 |
| SandboxPermissionRequest.tsx | 网络权限弹窗 |
| SandboxViolationExpandedView.tsx | 违规事件历史列表 |
| SandboxPromptFooterHint.tsx | 输入框底部即时违规提示 |
| 文件 | 作用 |
|---|---|
| sandbox-toggle.tsx | /sandbox 命令处理 |
| 文件 | 关键行 | 沙箱相关内容 |
|---|---|---|
| main.tsx | L201, L314-316, L3012 | 导入、遥测、AppState |
| REPL.tsx | L2216-2343, L4432 | 初始化、权限回调、违规UI渲染 |
| setup.ts | L401-437 | --dangerously-skip-permissions 的沙箱检测 |
用户输入 "npm run build"
│
▼
┌─[ BashTool.call(input) ]──────────────────────┐
│ input.dangerouslyDisableSandbox = false │
│ input.command = "npm run build" │
└────────┬──────────────────────────────────────-┘
│
▼
┌─[ shouldUseSandbox(input) ]───────────────────┐
│ 1. isSandboxingEnabled() → true │
│ 2. dangerouslyDisableSandbox? → false │
│ 3. containsExcludedCommand("npm run build")? │
│ → false │
│ return true ✅ │
└────────┬──────────────────────────────────────-┘
│
▼
┌─[ Shell.exec(command, { shouldUseSandbox: true }) ]─┐
│ 1. provider.buildExecCommand(cmd, {useSandbox}) │
│ 2. SandboxManager.wrapWithSandbox(cmdString,shell) │
│ 3. spawn(wrappedCommand) │
│ 4. .result.then → cleanupAfterCommand() │
└────────┬────────────────────────────────────────────┘
│
▼
┌─[ 命令执行结果 ]──────────────────────────────┐
│ SandboxManager.annotateStderrWithSandbox... │
│ (如果有违规:注入 <sandbox_violations> 标签) │
│ UI 层清理标签显示 → ViolationStore 记录 │
└───────────────────────────────────────────────┘