基于源码
src/utils/sandbox/sandbox-adapter.ts深度分析
Sandbox 使用 @anthropic-ai/sandbox-runtime 包提供操作系统级别的隔离执行环境,用于安全运行 Bash 命令和限制文件/网络访问。
┌────────────────────────────────────────────────────────────┐
│ Sandbox 架构 │
├────────────────────────────────────────────────────────────┤
│ │
│ Claude Code CLI │
│ │ │
│ ▼ │
│ SandboxAdapter (sandbox-adapter.ts) │
│ │ │
│ ▼ │
│ @anthropic-ai/sandbox-runtime │
│ │ │
│ ├── macOS: Bubblewrap (Linux) / SandboxKit (macOS) │
│ ├── Linux: Bubblewrap │
│ └── WSL2: Linux namespace │
│ │
└────────────────────────────────────────────────────────────┘
| 平台 | 支持状态 | 技术 |
|---|---|---|
| macOS | ✅ 完全支持 | SandboxKit |
| Linux | ✅ 完全支持 | Bubblewrap |
| WSL2 | ✅ 支持 | Linux namespace |
| WSL1 | ❌ 不支持 | - |
// 检查依赖是否可用
SandboxManager.checkDependencies()
// 返回 { errors: string[], warnings: string[] }
// errors 为空才可运行// settings.json
{
"sandbox": {
"enabled": true
}
}基于 src/utils/sandbox/sandbox-adapter.ts 导出的 ISandboxManager:
interface ISandboxManager {
// 初始化
initialize(sandboxAskCallback?: SandboxAskCallback): Promise<void>
// 状态检查
isSandboxingEnabled(): boolean // 是否启用
isSupportedPlatform(): boolean // 平台是否支持
isPlatformInEnabledList(): boolean // 是否在 enabledPlatforms 中
isSandboxEnabledInSettings(): boolean // 设置中是否启用
getSandboxUnavailableReason(): string | undefined // 不可用原因
isSandboxRequired(): boolean // 是否必须沙箱
areSandboxSettingsLockedByPolicy(): boolean // 是否被策略锁定
// Bash 相关
isAutoAllowBashIfSandboxedEnabled(): boolean
areUnsandboxedCommandsAllowed(): boolean
getExcludedCommands(): string[]
// 配置获取
getFsReadConfig(): FsReadRestrictionConfig
getFsWriteConfig(): FsWriteRestrictionConfig
getNetworkRestrictionConfig(): NetworkRestrictionConfig
getIgnoreViolations(): IgnoreViolationsConfig | undefined
getEnableWeakerNestedSandbox(): boolean | undefined
getAllowUnixSockets(): string[] | undefined
getAllowLocalBinding(): boolean | undefined
getProxyPort(): number | undefined
getSocksProxyPort(): number | undefined
getLinuxGlobPatternWarnings(): string[] // Linux glob 警告
checkDependencies(): { errors: string[], warnings: string[] } // 依赖检查
getLinuxHttpSocketPath(): string | undefined // Linux HTTP socket 路径
getLinuxSocksSocketPath(): string | undefined // Linux SOCKS socket 路径
// 网络
waitForNetworkInitialization(): Promise<boolean>
// 命令执行
wrapWithSandbox(command: string, binShell?: string, customConfig?: Partial<SandboxRuntimeConfig>, abortSignal?: AbortSignal): Promise<string>
cleanupAfterCommand(): void
// 沙箱违规
getSandboxViolationStore(): SandboxViolationStore
annotateStderrWithSandboxFailures(command: string, stderr: string): string
// 设置
setSandboxSettings(options: {
enabled?: boolean
autoAllowBashIfSandboxed?: boolean
allowUnsandboxedCommands?: boolean
}): Promise<void>
refreshConfig(): void
reset(): Promise<void>
}interface SandboxRuntimeConfig {
network: {
allowedDomains: string[] // 允许的域名
deniedDomains: string[] // 禁止的域名
allowUnixSockets?: string[] // 允许的 Unix socket
allowAllUnixSockets?: boolean
allowLocalBinding?: boolean // 允许本地端口绑定
httpProxyPort?: number // HTTP 代理端口
socksProxyPort?: number // SOCKS 代理端口
}
filesystem: {
denyRead: string[] // 禁止读取的路径
allowRead: string[] // 允许读取的路径
allowWrite: string[] // 允许写入的路径
denyWrite: string[] // 禁止写入的路径
}
ignoreViolations?: Record<string, string[]> // 忽略的违规
enableWeakerNestedSandbox?: boolean
enableWeakerNetworkIsolation?: boolean
ripgrep: {
command: string
args: string[] // 可选
}
}注意:ripgrep 是可配置的,允许自定义 ripgrep 命令路径和参数。argv0 是内部行为而非用户可配置字段。
sandbox-adapter.ts 中的 convertToSandboxRuntimeConfig() 函数:
// 自动添加的路径
allowWrite: [
'.', // 当前目录
getClaudeTempDir(), // Claude 临时目录
...additionalDirectories, // --add-dir 添加的目录
worktreeMainRepoPath, // Git worktree 主仓库
]
// 自动禁止的路径
denyWrite: [
...settingsPaths, // 所有 settings.json 文件
getManagedSettingsDropInDir(), // Managed 设置目录
'.claude/skills', // Skills 目录
...bareGitRepoFiles, // Bare git repo 文件
]resolvePathPatternForSandbox(pattern, source):
"//path" → "/path" // 绝对路径 (从根) CC 特殊约定
"/path" → "$SETTINGS_DIR/path" // 相对于 settings 文件目录 (CC 特殊约定)
"~/path" → 展开 home 目录 (标准)
"./path" → 相对路径 (标准)
"path" → 相对路径 (标准)说明:
//path和/path是 Claude Code 权限规则的特殊约定/path不是绝对路径,而是相对于 settings 文件所在目录- 标准路径语义 (
~/,./,path) 由 sandbox-runtime 处理
sandbox.filesystem.* 设置使用标准路径语义(与权限规则不同):
resolveSandboxFilesystemPath(pattern, source):
"/path" → "/path" // 绝对路径 (按字面意思)
"//path" → "/path" // 兼容 legacy 权限规则语法
"~/path" → 展开 home 目录
"./path" → 相对于 settings 目录
"path" → 相对于 settings 目录注意:修复 #30067 后,sandbox.filesystem.allowWrite 中的 /path 被视为绝对路径,而非 settings-相对路径。
// 禁止写入任何 settings.json 文件
denyWrite.push(...settingsPaths)
// 包括 managed 设置
denyWrite.push(getManagedSettingsDropInDir())
// 如果 cwd 与原始终端目录不同,也阻止写入当前目录的 settings
if (cwd !== originalCwd) {
denyWrite.push(resolve(cwd, '.claude', 'settings.json'))
denyWrite.push(resolve(cwd, '.claude', 'settings.local.json'))
}// .claude/skills 目录被保护
// Skills 有与 commands/agents 相同的权限级别
denyWrite.push(resolve(originalCwd, '.claude', 'skills'))// 防止在沙箱中植入 git 文件夹逃脱
// 安全问题:如果 cwd 存在 HEAD + objects/ + refs/ (bare repo 特征),
// 攻击者可植入 config + core.fsmonitor 逃逸沙箱
denyWrite.push(...bareGitRepoFiles)
// 包括: HEAD, objects, refs, hooks, config
// 如果不存在则事后清理
scrubBareGitRepoFiles()
// 在 cleanupAfterCommand() 中调用,删除沙箱命令植入的文件安全机制:
- 沙箱初始化时检测 bare repo 文件是否存在
- 存在的文件:添加
denyWrite(只读绑定) - 不存在的文件:事后清理
scrubBareGitRepoFiles()
// 检测 worktree 并允许写入主仓库
detectWorktreeMainRepoPath(cwd):
// 在 worktree 中,.git 是一个文件,内容为 "gitdir: /path/to/main/repo/.git/worktrees/name"
// 函数解析此文件获取主仓库路径
// 缓存结果供整个会话使用
if (worktreeMainRepoPath) {
allowWrite.push(worktreeMainRepoPath)
// 允许 worktree 中的 git 操作写入主仓库
}Worktree 检测机制:
- 在
initialize()时调用一次并缓存 - 读取
.git文件内容,匹配gitdir:格式 - 检查是否存在
.git/worktrees/标记确认是 worktree - 结果存储在
worktreeMainRepoPath变量中
sandbox-adapter.ts 中的配置转换函数 convertToSandboxRuntimeConfig():
// 自动添加的路径
allowWrite: [
'.', // 当前目录
getClaudeTempDir(), // Claude 临时目录
...additionalDirectories, // --add-dir 添加的目录
worktreeMainRepoPath, // Git worktree 主仓库
]
// 自动禁止的路径
denyWrite: [
...settingsPaths, // 所有 settings.json 文件
getManagedSettingsDropInDir(), // Managed 设置目录
'.claude/skills', // Skills 目录
...bareGitRepoFiles, // Bare git repo 文件
]
// 实验性配置
enableWeakerNestedSandbox?: boolean // 允许嵌套沙箱
enableWeakerNetworkIsolation?: boolean // 允许更宽松的网络隔离shouldAllowManagedSandboxDomainsOnly()
// 当 policySettings.sandbox.network.allowManagedDomainsOnly: true 时
// 只使用策略设置的域名shouldAllowManagedReadPathsOnly()
// 当 policySettings.sandbox.filesystem.allowManagedReadPathsOnly: true 时
// 只使用策略设置的 read 路径areSandboxSettingsLockedByPolicy()
// 检查 flagSettings 或 policySettings 是否设置了沙箱选项
// 如果设置了,localSettings 的沙箱设置将被忽略┌────────────────────────────────────────────────────────────┐
│ 沙箱初始化流程 │
├────────────────────────────────────────────────────────────┤
│ │
│ 1. isSandboxingEnabled() │
│ ├── 检查平台支持 │
│ ├── 检查依赖 │
│ └── 检查 enabledPlatforms │
│ │
│ 2. detectWorktreeMainRepoPath() │
│ └── 检测并缓存主仓库路径 │
│ │
│ 3. convertToSandboxRuntimeConfig() │
│ └── 转换设置为运行时配置 │
│ │
│ 4. BaseSandboxManager.initialize() │
│ └── 调用 sandbox-runtime 初始化 │
│ │
│ 5. settingsChangeDetector.subscribe() │
│ └── 订阅设置变更,动态更新配置 │
│ │
└────────────────────────────────────────────────────────────┘
async function wrapWithSandbox(command: string, binShell?: string): Promise<string> {
// 确保初始化完成
await initializationPromise
// 调用 sandbox-runtime 执行
return BaseSandboxManager.wrapWithSandbox(command, binShell)
}
// 命令后清理
cleanupAfterCommand(): void {
BaseSandboxManager.cleanupAfterCommand()
scrubBareGitRepoFiles() // 清理植入的 git 文件
}// 设置变更时自动更新
settingsChangeDetector.subscribe(() => {
const newConfig = convertToSandboxRuntimeConfig(settings)
BaseSandboxManager.updateConfig(newConfig)
})
// 手动刷新
refreshConfig(): void {
const newConfig = convertToSandboxRuntimeConfig(settings)
BaseSandboxManager.updateConfig(newConfig)
}addToExcludedCommands(command: string, permissionUpdates?: PermissionUpdate[]): string {
// 从 permissionUpdates 中提取 Bash 规则
// 添加到 sandbox.excludedCommands
}沙箱无法启动时的行为控制:
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true // 沙箱无法启动时退出,默认为 false (仅警告)
}
}用途:企业托管设置中强制沙箱化,作为硬门控。当 enabled=true 但沙箱无法启动时,CLI 以错误退出而非仅警告。
限制沙箱在特定平台上启用:
{
"sandbox": {
"enabled": true,
"enabledPlatforms": ["macos"] // 仅在 macOS 上启用沙箱
}
}用途:企业部署时逐步 rollout,例如 NVIDIA 部署先只在 macOS 上启用 autoAllowBashIfSandboxed。
{
"sandbox": {
"excludedCommands": [
"docker",
"kubectl",
"helm"
]
}
}// 从 WebFetch 权限规则中提取域名
if (rule.toolName === "WebFetch" && rule.ruleContent?.startsWith("domain:")) {
allowedDomains.push(rule.ruleContent.substring("domain:".length))
}// HTTP 代理
httpProxyPort: settings.sandbox?.network?.httpProxyPort
// SOCKS 代理
socksProxyPort: settings.sandbox?.network?.socksProxyPortgetSandboxViolationStore(): SandboxViolationStore
// 存储违规事件供查询initialize(sandboxAskCallback?: SandboxAskCallback): Promise<void>
// 回调签名
type SandboxAskCallback = (hostPattern: NetworkHostPattern) => boolean
// 返回 true = 允许, false = 拒绝{
"sandbox": {
"ignoreViolations": {
"network": ["*.internal.company.com"]
}
}
}{
"sandbox": {
"enabled": true,
"filesystem": {
"allowWrite": ["./src"],
"denyWrite": ["/etc", "/root", "/home"]
},
"network": {
"allowedDomains": ["api.github.com"]
}
}
}{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"network": {
"allowManagedDomainsOnly": true
},
"filesystem": {
"allowManagedReadPathsOnly": true
},
"autoAllowBashIfSandboxed": true,
"allowUnsandboxedCommands": false
}
}安全注意:autoAllowBashIfSandboxed 默认为 true。这意味着启用沙箱时,命令默认会自动放行(由沙箱提供保护),ask 规则会被跳过。
{
"sandbox": {
"enabled": true,
"enabledPlatforms": ["macos"],
"network": {
"allowUnixSockets": ["/var/run/docker.sock"],
"allowAllUnixSockets": false
}
}
}{
"sandbox": {
"enabled": true,
"enabledPlatforms": ["macos", "linux"]
}
}{
"sandbox": {
"enabled": true,
"filesystem": {
"allowWrite": ["./src", "./tests", "./scripts"]
}
}
}{
"sandbox": {
"enabled": true,
"network": {
"httpProxyPort": 8080
}
}
}# 检查不可用原因
node -e "console.log(SandboxManager.getSandboxUnavailableReason())"# macOS: 依赖已内置
# Linux: 需要安装
apt install bubblewrap socat// 检查 glob 模式警告 (Linux/WSL)
SandboxManager.getLinuxGlobPatternWarnings()
// Linux 上的 bubblewrap 不完全支持 glob| 维度 | Sandbox | Permissions |
|---|---|---|
| 层级 | OS 级别 | 应用级别 |
| 粒度 | 目录级别 | 工具/命令级别 |
| 技术 | namespace/container | Zod schema |
| 绕过 | 禁用/排除命令 | 权限规则 |
permissions.additionalDirectories 集成:permissions.additionalDirectories 的值会同时影响权限验证的路径范围和 sandbox 的 allowWrite 路径。
dangerouslyDisableSandbox 交互:
- 当
allowUnsandboxedCommands: false时,dangerouslyDisableSandbox参数被完全忽略 - 所有命令必须在沙箱中运行或通过
excludedCommands排除 autoAllowBashIfSandboxed: true时,沙箱命令跳过 ask 规则自动放行,但非沙箱命令仍需遵守 ask 规则
推荐:同时使用 Sandbox 和 Permissions 实现纵深防御。
enabledPlatforms 是 未文档化的企业配置选项(undocumented setting),用于 NVIDIA 企业部署的临时方案:
{
"sandbox": {
"enabled": true,
"enabledPlatforms": ["macos"] // 仅在 macOS 上启用沙箱
}
}沙箱内自动设置以下环境变量:
| 变量 | 说明 |
|---|---|
TMPDIR |
临时目录 |
CLAUDE_CODE_TMPDIR |
Claude Code 专用临时目录 |
TMPPREFIX |
临时文件前缀 |
Ant 用户 (USER_TYPE === 'ant') 有特殊的危险命令列表:
fa run,coo,gh,gh apicurl,wget,git,kubectlaws,gcloud,gsutil
完整的 PowerShell 危险命令模式列表,包括:
iex,invoke-expression,start-processregister-wmievent- .exe 后缀变体匹配
NTFS 特殊路径模式检测:
- ADS (Alternate Data Streams)
- 8.3 短文件名
- 长路径前缀 (
\\?\,\\.\) - 尾随点和空格
- DOS 设备名
- 三点或更多连续点
allowUnixSockets 仅 macOS 支持(seccomp 无法在 Linux 上按路径过滤)。
enableWeakerNetworkIsolation: true 允许访问 com.apple.trustd.agent(降低安全性的配置)。
DENIAL_LIMITS 限制连续和总拒绝次数,超限时回退到提示模式。
/sandbox 命令允许用户切换沙箱状态。
# 检查平台支持
node -e "console.log(SandboxManager.isSupportedPlatform())"
# 检查依赖
node -e "console.log(SandboxManager.checkDependencies())"
# 检查启用状态
node -e "console.log(SandboxManager.isSandboxingEnabled())"