在 AI Agent 深度参与软件开发的今天,安全问题已经从"可选配置"变成了"核心基础设施"。Claude Code 作为 Anthropic 推出的命令行智能助手,其安全体系的设计尤为值得研究。本文将深入解析 Claude Code 的沙箱(Sandbox)架构、权限验证机制、Undercover Mode 以及安全审计系统,帮助开发者理解一个生产级 AI Agent 如何在开放环境中保护用户系统和敏感信息。
一、沙箱架构全景
Claude Code 的沙箱并非从零构建,而是基于 @anthropic-ai/sandbox-runtime 这一底层包,通过适配层将其与 CLI 的设置系统、工具调用和 UI 渲染深度集成。这种分层架构既保证了安全策略的底层可靠性,又提供了面向用户的灵活配置能力。
1.1 目录结构与职责
沙箱相关的代码主要分布在两个目录:
src/utils/sandbox/
├── sandbox-adapter.ts # 35KB,核心适配层
└── sandbox-ui-utils.ts # 389字节,UI 辅助函数
src/components/sandbox/
├── SandboxConfigTab.tsx # 16KB,配置展示页签
├── SandboxDependenciesTab.tsx # 17KB,依赖检查页签
├── SandboxDoctorSection.tsx # 6KB,诊断面板片段
├── SandboxOverridesTab.tsx # 20KB,覆盖规则页签
└── SandboxSettings.tsx # 29KB,设置主面板
src/components/
└── SandboxViolationExpandedView.tsx # 11KB,违规事件展示sandbox-adapter.ts 是整个沙箱系统的枢纽。它在源码第 1–5 行的注释中明确阐述了设计目标:
"Adapter layer that wraps
@anthropic-ai/sandbox-runtimewith Claude CLI-specific integrations. This file provides the bridge between the external sandbox-runtime package and Claude CLI’s settings system, tool integration, and additional features."
换言之,sandbox-adapter.ts 的职责是将外部沙箱运行时与 Claude Code 的权限系统、设置持久化、以及工具调用链无缝衔接。
1.2 架构分层图
flowchart TB
subgraph UI["UI 层 (React/Ink)"]
SS[SandboxSettings.tsx]
SVE[SandboxViolationExpandedView.tsx]
SD[SandboxDoctorSection.tsx]
end
subgraph Adapter["适配层"]
SA[sandbox-adapter.ts]
end
subgraph Runtime["底层运行时"]
BSM[BaseSandboxManager
@anthropic-ai/sandbox-runtime]
SVS[SandboxViolationStore]
end
subgraph Tools["工具层"]
BT[BashTool]
FET[FileEditTool]
WFT[WebFetchTool]
end
SS --> SA
SVE --> SVS
SA --> BSM
SA --> SVS
BT --> SA
FET --> SA
WFT --> SA
BSM --> |bubblewrap / seatbelt| OS[操作系统沙箱]如上图所示,UI 层通过 sandbox-adapter.ts 与底层运行时通信,而工具层(BashTool、FileEditTool、WebFetchTool)则在执行命令前由适配层决定是否包裹沙箱命令。底层在 Linux 上使用 bubblewrap,在 macOS 上使用 seatbelt,实现操作系统级别的进程隔离。
二、沙箱模式的启用与限制
2.1 启用条件
沙箱并非默认开启,其启用需要满足一系列条件。isSandboxingEnabled() 函数(sandbox-adapter.ts 第 532–547 行)定义了完整的检查链:
function isSandboxingEnabled(): boolean {
if (!isSupportedPlatform()) {
return false
}
if (checkDependencies().errors.length > 0) {
return false
}
if (!isPlatformInEnabledList()) {
return false
}
return getSandboxEnabledSetting()
}具体条件包括:
- 平台支持:仅支持 macOS、Linux 和 WSL2+,WSL1 因内核限制不在支持范围内(第 491–493 行)。
- 依赖可用性:Linux 需要
bubblewrap和socat等工具,macOS 需要seatbelt相关组件。checkDependencies()使用lodash-es的memoize缓存结果(第 451–457 行)。 - 平台白名单:
enabledPlatforms是一个未文档化的设置项,允许管理员仅对特定平台启用沙箱。例如 NVIDIA 的企业部署初期只希望 macOS 启用沙箱(第 505–526 行)。 - 用户显式启用:
sandbox.enabled必须在设置中为true。
如果用户显式启用了沙箱但环境不满足,getSandboxUnavailableReason()(第 562–592 行)会返回具体原因,避免"静默失效"这一安全陷阱。
2.2 三种运行模式
Claude Code 的沙箱有三种互斥模式,在 SandboxSettings.tsx 第 21 行定义为 SandboxMode 类型:
type SandboxMode = 'auto-allow' | 'regular' | 'disabled'- auto-allow:沙箱启用,且所有在沙箱内运行的 Bash 命令自动获得批准。这是推荐的安全与便利平衡点。
- regular:沙箱启用,但 Bash 命令仍需走正常的权限审批流程。
- disabled:完全不启用沙箱。
2.3 文件系统隔离
文件系统隔离是沙箱最核心的功能之一。convertToSandboxRuntimeConfig() 函数(第 172–381 行)负责将 Claude Code 的权限规则转换为底层运行时配置。其策略非常精细:
写保护策略:
- 默认允许写入当前工作目录(
.)和 Claude 临时目录(getClaudeTempDir())。 - 禁止写入所有来源的
settings.json文件,防止通过修改配置实现沙箱逃逸(第 232–236 行)。 - 禁止写入
.claude/skills目录,因为 skills 与 commands/agents 具有相同的自动发现和执行权限(第 248–255 行)。
Git 裸仓库攻击防护:
这是一个非常精妙的安全设计。攻击者可以在工作目录中放置 HEAD、objects/、refs/ 等文件,让 Git 将其识别为 bare repository,再通过 core.fsmonitor 配置执行命令实现沙箱逃逸。代码在第 257–280 行处理了这一场景:
const bareGitRepoFiles = ['HEAD', 'objects', 'refs', 'hooks', 'config']
for (const dir of cwd === originalCwd ? [originalCwd] : [originalCwd, cwd]) {
for (const gitFile of bareGitRepoFiles) {
const p = resolve(dir, gitFile)
try {
statSync(p)
denyWrite.push(p)
} catch {
bareGitRepoScrubPaths.push(p)
}
}
}如果文件已存在,直接加入 denyWrite;如果不存在但后续被创建,cleanupAfterCommand() 会调用 scrubBareGitRepoFiles() 在命令结束后立即清除(第 404–414 行)。
路径解析规则:
Claude Code 使用特殊的路径前缀约定(第 99–119 行):
//path→ 文件系统根目录下的绝对路径(/.aws/**)/path→ 相对于设置文件所在目录~/path和相对路径 → 由sandbox-runtime处理
2.4 网络隔离
网络隔离通过域名白名单和黑名单实现。convertToSandboxRuntimeConfig() 从两个来源提取域名规则:
settings.sandbox.network.allowedDomains等显式配置permissions.allow中的WebFetch(domain:...)规则
当 allowManagedDomainsOnly 策略启用时,只允许来自 policySettings 的域名,用户个人设置中的域名将被忽略(第 181–210 行)。这一设计满足企业环境中"IT 部门统一管控网络出口"的需求。
网络配置还涵盖了 Unix Socket、本地绑定、HTTP/SOCKS 代理端口等细粒度控制(第 360–368 行)。
三、权限验证体系
3.1 多层权限检查
Claude Code 的权限系统采用多层纵深防御。除了沙箱层面的系统级隔离,应用层还有独立的权限规则引擎。sandbox-adapter.ts 第 62–81 行实现了与权限系统的本地适配函数,避免循环依赖:
function permissionRuleValueFromString(ruleString: string): PermissionRuleValue {
const matches = ruleString.match(/^([^(]+)\(([^)]+)\)$/)
if (!matches) {
return { toolName: ruleString }
}
const toolName = matches[1]
const ruleContent = matches[2]
// ...
}权限规则格式为 ToolName(ruleContent),例如 Bash(npm run test)、WebFetch(domain:api.github.com)、FileEdit(./src/**/*.ts)。
3.2 分类器自动审批
classifierApprovals.ts(88 行,约 2.4KB)记录了哪些工具使用被分类器(classifier)自动批准,无需用户手动确认。这是权限验证的"快速通道"。
type ClassifierApproval = {
classifier: 'bash' | 'auto-mode'
matchedRule?: string
reason?: string
}
const CLASSIFIER_APPROVALS = new Map<string, ClassifierApproval>()该模块支持两种分类器(第 10 行):
- bash classifier:专门用于 Bash 命令的风险评估。
setClassifierApproval()(第 19–30 行)在特征开关BASH_CLASSIFIER启用时记录审批结果,包含匹配到的规则名。 - auto-mode classifier(Yolo):基于对话上下文的整体风险评估。
setYoloClassifierApproval()(第 41–49 行)在TRANSCRIPT_CLASSIFIER启用时工作,记录审批原因。
这两个分类器通过信号机制(createSignal())与 UI 联动:
export function setClassifierChecking(toolUseID: string): void {
if (!feature('BASH_CLASSIFIER') && !feature('TRANSCRIPT_CLASSIFIER')) return
CLASSIFIER_CHECKING.add(toolUseID)
classifierChecking.emit()
}当分类器正在评估某个工具使用时,UI 可以展示"正在检查…"的状态,提升用户体验。
3.3 危险命令检测
在沙箱模式下,Bash 命令默认会被包裹在沙箱中执行。wrapWithSandbox() 函数(第 704–725 行)是这一过程的入口:
async function wrapWithSandbox(
command: string,
binShell?: string,
customConfig?: Partial<SandboxRuntimeConfig>,
abortSignal?: AbortSignal,
): Promise<string> {
if (isSandboxingEnabled()) {
if (initializationPromise) {
await initializationPromise
} else {
throw new Error('Sandbox failed to initialize. ')
}
}
return BaseSandboxManager.wrapWithSandbox(command, binShell, customConfig, abortSignal)
}注意第 715 行的严格检查:如果沙箱已启用但初始化未完成(initializationPromise 不存在),直接抛出错误,避免在未准备好的沙箱环境中执行命令。
3.4 安全策略的强制与锁定
企业部署中,安全策略需要防止终端用户私自关闭。areSandboxSettingsLockedByPolicy()(第 647–664 行)检查沙箱设置是否被高优先级配置源锁定:
function areSandboxSettingsLockedByPolicy(): boolean {
const overridingSources = ['flagSettings', 'policySettings'] as const
for (const source of overridingSources) {
const settings = getSettingsForSource(source)
if (
settings?.sandbox?.enabled !== undefined ||
settings?.sandbox?.autoAllowBashIfSandboxed !== undefined ||
settings?.sandbox?.allowUnsandboxedCommands !== undefined
) {
return true
}
}
return false
}flagSettings 和 policySettings 的优先级高于用户本地的 localSettings。如果管理员通过这两个渠道配置了沙箱参数,用户界面会显示设置已被锁定,无法修改。
四、Undercover Mode:防止内部信息泄露
Undercover Mode 是 Claude Code 中一个独特且有趣的安全功能,专门用于防止 Anthropic 内部信息通过 AI 生成的代码贡献泄露到公共仓库。
4.1 功能定位
undercover.ts(89 行,约 3.6KB)的头部注释(第 1–22 行)清晰地描述了这一功能:
"When active, Claude Code adds safety instructions to commit/PR prompts and strips all attribution to avoid leaking internal model codenames, project names, or other Anthropic-internal information. The model is not told what model it is."
简而言之,当 Claude Code 检测到正在向公共或开源仓库提交代码时,它会自动进入"卧底模式",在提示词中注入安全指令,要求模型不要泄露任何内部信息。
4.2 激活逻辑
isUndercover() 函数(第 28–37 行)实现了激活判断:
export function isUndercover(): boolean {
if (process.env.USER_TYPE === 'ant') {
if (isEnvTruthy(process.env.CLAUDE_CODE_UNDERCOVER)) return true
return getRepoClassCached() !== 'internal'
}
return false
}激活逻辑有三层:
- 强制开启:设置环境变量
CLAUDE_CODE_UNDERCOVER=1,即使在内部仓库也强制启用。 - 自动检测:如果仓库远程地址不在内部白名单(
INTERNAL_MODEL_REPOS)中,则自动启用。 - 无强制关闭:注释中强调"There is NO force-OFF"。如果系统不确定当前是否在内部仓库,默认保持 undercover 状态。
值得注意的是,所有代码路径都被 process.env.USER_TYPE === 'ant' 守卫。由于 USER_TYPE 是构建时的 --define 变量,打包器会在外部构建中常量折叠并死码消除这些分支。因此,在公开发布的 Claude Code 版本中,undercover.ts 中的所有函数都会退化为空操作(第 18–22 行注释)。
4.3 内部代号过滤
getUndercoverInstructions()(第 39–72 行)返回注入模型的安全指令。其中明确列出了需要过滤的内部信息类型:
NEVER include in commit messages or PR descriptions:
- Internal model codenames (animal names like Capybara, Tengu, etc.)
- Unreleased model version numbers (e.g., opus-4-7, sonnet-4-8)
- Internal repo or project names (e.g., claude-cli-internal, anthropics/…)
- Internal tooling, Slack channels, or short links (e.g., go/cc, #claude-code-…)
- The phrase "Claude Code" or any mention that you are an AI
- Any hint of what model or version you are
- Co-Authored-By lines or any other attribution指令还给出了正反示例。良好的提交信息应该是:
"Fix race condition in file watcher initialization"
而绝对不应该出现:
"Fix bug found while testing with Claude Capybara"
"1-shotted by claude-opus-4-6"
"Generated with Claude Code"
4.4 员工使用场景
Undercover Mode 主要面向 Anthropic 内部员工在以下场景使用:
- 向开源项目(如 React、TypeScript 等)贡献代码
- 在公共 GitHub 仓库中提交 PR
- 在
/tmp等临时目录进行 crash repro 时,当前目录本身不是 git checkout,但可能推送代码到公共远程
shouldShowUndercoverAutoNotice()(第 80–89 行)还会检测是否首次自动进入 undercover 模式,并向用户展示一次性说明,避免用户在不知情的情况下发现提交行为"异常保守"。
五、安全审计与监控
5.1 违规事件追踪
沙箱系统内置了完整的违规事件追踪机制。SandboxViolationStore 来自 @anthropic-ai/sandbox-runtime,负责收集所有被沙箱拦截的操作。SandboxViolationExpandedView.tsx(99 行,约 11KB)则在 UI 中实时展示这些事件:
export function SandboxViolationExpandedView() {
const [violations, setViolations] = useState([])
const [totalCount, setTotalCount] = useState(0)
useEffect(() => {
const store = SandboxManager.getSandboxViolationStore()
const unsubscribe = store.subscribe(allViolations => {
setViolations(allViolations.slice(-10))
setTotalCount(store.getTotalCount())
})
return unsubscribe
}, [])
// ...
}组件订阅 SandboxViolationStore,始终展示最近的 10 条违规记录,同时显示总计数。每条记录包含时间戳、被拦截的命令和具体行信息(第 96–98 行)。
值得注意的是第 50 行的平台判断:
if (!SandboxManager.isSandboxingEnabled() || getPlatform() === "linux") {
return null
}Linux 平台不展示违规扩展视图,因为 Linux 使用 bubblewrap,其违规报告的粒度与 macOS 的 seatbelt 不同。
5.2 遥测上报
虽然源码中没有直接展示遥测发送的代码(可能位于闭源的 sandbox-runtime 包中),但从架构设计可以推断审计数据的流向。logForDebugging() 函数在多个关键点被调用:
- 沙箱配置更新时:
logForDebugging('Sandbox configuration updated from settings change')(第 780 行) - 初始化失败时:
logForDebugging(\Failed to initialize sandbox: ${errorMessage(error)}`)`(第 787 行) - 网络请求被策略拦截时:
logForDebugging(\[sandbox] Blocked network request to ${hostPattern.host} (allowManagedDomainsOnly)`)`(第 749 行) - 清理 bare repo 攻击文件时:
logForDebugging(\[Sandbox] scrubbed planted bare-repo file: ${p}`)`(第 409 行)
这些调试日志为后续的安全审计和事件溯源提供了关键线索。
5.3 诊断与自检
sandbox-ui-utils.ts 虽然只有 12 行,但提供了一个实用的文本清理函数:
export function removeSandboxViolationTags(text: string): string {
return text.replace(/<sandbox_violations>[\s\S]*?<\/sandbox_violations>/g, '')
}该函数用于在 UI 展示前移除错误消息中的 <sandbox_violations> 标签,避免向终端输出过多内部技术细节。
SandboxDoctorSection.tsx(46 行)则集成在 /doctor 命令的诊断面板中,主动检查沙箱依赖状态并向用户报告:
export function SandboxDoctorSection() {
if (!SandboxManager.isSupportedPlatform()) {
return null
}
if (!SandboxManager.isSandboxEnabledInSettings()) {
return null
}
const depCheck = SandboxManager.checkDependencies()
const hasErrors = depCheck.errors.length > 0
const hasWarnings = depCheck.warnings.length > 0
// ...
}如果存在错误,提示用户运行 /sandbox 获取安装指引;如果只有警告,则显示"可用(但有警告)"。
5.4 异常检测的工作流
sequenceDiagram
participant User as 用户
participant BT as BashTool
participant SA as sandbox-adapter.ts
participant BSM as BaseSandboxManager
participant SVS as SandboxViolationStore
participant UI as SandboxViolationExpandedView
User->>BT: 执行命令
BT->>SA: wrapWithSandbox(command)
SA->>BSM: wrapWithSandbox(...)
BSM-->>BSM: 系统级隔离执行
alt 发生违规
BSM->>SVS: 记录违规事件
SVS->>UI: emit() 通知订阅者
UI->>UI: 更新状态展示最近10条
else 正常完成
BSM->>SA: cleanupAfterCommand()
SA->>SA: scrubBareGitRepoFiles()
end上图展示了从命令执行到异常检测的完整工作流。无论命令是否触发违规,清理阶段都会执行,确保攻击者通过命令执行植入的 bare repo 文件被及时清除。
六、总结
Claude Code 的安全体系展现了生产级 AI Agent 应有的纵深防御思维:
- 系统级隔离通过
sandbox-runtime将进程限制在最小权限空间,文件系统和网络访问都受到严格管控。 - 应用层权限通过分类器自动审批和用户确认相结合,在安全与效率之间取得平衡。
- 信息泄露防护通过 Undercover Mode 这一独特设计,防止 AI 模型在公共场景中"说漏嘴"。
- 持续监控与自检通过违规事件追踪、诊断面板和清理机制,确保安全策略在整个会话周期内持续有效。
对于正在构建 AI Agent 的开发者而言,Claude Code 的沙箱设计提供了一个优秀的参考模板:安全不是单一功能的堆砌,而是从架构分层、策略执行到审计追踪的系统性工程。尤其是 bare repo 攻击防护和 undercover 信息过滤这两个细节,体现了安全设计中对"边缘场景"和"社会工程"威胁的深刻洞察。
参考源码版本:基于 yasasbanukaofficial/claude-code 仓库 main 分支的源码分析,核心文件包括 src/utils/sandbox/sandbox-adapter.ts、src/utils/classifierApprovals.ts、src/utils/undercover.ts、src/components/SandboxViolationExpandedView.tsx 等。