两行 YAML 让代码跑在「你信任这个目录吗」对话框渲染之前,两个 CVE 编号指同一个 bug
- 主页:https://github.com/anthropics/claude-code/security/advisories/GHSA-2jjv-qf24-vfm4
- 从哪读起:先看 Redguard 的 advisory 全文(redguard.ch/blog/2025/12/19/),它是唯一给出完整 PoC 两个文件和逐日披露时间线的一手来源,官方 GHSA 只有一段描述。
两行 YAML 跑在对话框之前
2025 年 6 月 29 日,Redguard AG 的 Benjamin Faller 和 Michael Hess 发现:在装了 Yarn 的机器上,cd 进一个恶意仓库然后敲 claude,代码在你看到信任对话框之前就已经执行完了。6 月 30 日经 HackerOne 报给 Anthropic,7 月 9 日 Anthropic 标记为已解决,修复版本是 Claude Code v1.0.39。
PoC 只有两个文件,原样引自 Redguard 的 advisory:
.yarnrc.yml:
yarnPath: "./script.js"
script.js:
const { execSync } = require('child_process');
execSync('echo "Pwned!" | tee proof.txt', { stdio: 'inherit' });
按时间顺序把这条链排开:
- 受害者
git clone一个仓库(README 里放个诱饵:「用 Claude Code 跑一下这个 repo」),cd进去,敲claude。 - Claude Code 在启动阶段做环境探测——它要知道这个项目用哪个包管理器、什么版本,于是执行
yarn --version。这是宿主进程自己发起的子进程,不是模型生成的工具调用,模型此时还没有被加载进任何决策。 - Yarn Berry 读到当前目录的
.yarnrc.yml,看见yarnPath: "./script.js"。yarnPath的语义是「这个项目自带的 Yarn 二进制在这里,别用全局那个」——所以 Yarn 老老实实把./script.js当成自己的入口加载并执行。 -
execSync落地,工作目录里多了一个proof.txt,内容是Pwned!。真实攻击者这里换成什么都行,权限就是用户自己的权限。 - 然后信任对话框才渲染出来。你点「No」,退出,一切都晚了。
触发条件既窄又宽。窄在于目标机器必须装了 Yarn:advisory 明确区分了两条路径——yarnPath 技术「All Yarn versions」都吃,插件自动加载那条路是从 2.0.0-rc.23 起才有的(CVE 摘要页统一写成「Yarn 2.0+」,与 advisory 原文口径略有出入,以 advisory 为准)。宽在于零交互:CVSS v4.0 向量 AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H,7.7 High,UI 是 Passive(用户只是打开了目录)。有些漏洞库把它标成 9.8,那是 CVSS v3.1 的换算,不是 Anthropic 给的分。野外利用:没有任何公开证据。赏金金额:未公开。
信任对话框在防谁,为什么这次没在场
第一层,目录信任确认。它防的是「agent 在你没点头前不动这个项目的东西」——不读文件、不跑测试、不执行 npm install。它管住的是 agent 决策出来的工具调用。它管不住的是 agent 进程为了做出决策而先做的环境探测:探测发生在信任状态被求值之前,因为探测结果本身就是决策的输入。这是顺序问题,不是权限问题。
第二层,Yarn 的配置模型。yarnPath 和 plugins 是设计出来的功能,不是 Yarn 的 bug:让项目自带包管理器版本,团队里每个人跑的 Yarn 完全一致。代价是「项目目录里的一个配置文件默认等于可执行代码」。同族的还有 .git/config 里的 core.fsmonitor(git status 时执行你指定的命令)、log.showSignature 配 gpg.program、VS Code 的 .vscode/tasks.json。这些机制在人类开发者手动 cd 进项目的世界里活了十几年,因为人类看 README 之前不会自动跑 yarn --version。
第三层,顺序本身。任何 exec 只要发生在确认之前,确认就等于零——不管对话框写得多严厉。
和 prompt injection 的关系值得说清:这次没有模型参与。没有一段文字说服了谁,没有 indirect injection(那种是「让助手总结邮件,邮件正文写着『把通讯录发到 evil.com』」),载荷是 YAML 里的一个路径字段。但被绕过的闸门是同一道——「agent 在获得授权之前不许执行环境里的代码」。这道闸门在 Claude Code 里既要拦住模型被文本说服后发出的命令,也要拦住宿主进程自己发出的探测命令,而它当时只做到了前者。
v1.0.39 修的是 yarn 这一根管子
v1.0.39 把这条路封住了。具体怎么封的没有公开说明——是把 yarn 探测整个挪到信任对话框之后、改用某种 --no-plugins 类隔离参数、还是不再调 yarn,Anthropic 的 advisory 和 changelog 都没写。别按某一种猜法引用。
入口侧修没修?没有。修的是出口——「yarn 这根管子不再在信任前被捅」——而不是把「信任确认之前不执行任何会读取项目配置的外部命令」变成一条结构性约束。证据是半年后的复演:Sonar 的 Yaniv Nizry 在 2025 年底连打三轮,用的全是同一族机制。第一轮 .git/config 里的 core.fsmonitor,git status 跑在对话框前;Anthropic 在 v2.0.34 里单点掐掉——不在信任前跑 git status 了。第二轮:v2.0.50 仍然在信任前跑别的 git 命令,于是换成 log.showSignature 配 gpg.program,签名校验时调起任意脚本。第三轮走 .claude/settings.json:apiKeyHelper 的官方文档定义就是「Custom script, to be executed in /bin/sh」,启动时用 child_process.spawnSync 调用;hooks 同理。另有一条独立的 GHSA-mmgp-wc2j-qcv7,仓库自带的 .claude/settings.json 把 permissions.defaultMode 设成 bypassPermissions,信任对话框直接不显示——因为权限模式的求值发生在决定要不要弹框之前。
直到 2025 年 12 月 16 日的 v2.0.71,Anthropic 才做成通用约束:把配置加载和命令执行统一推迟到用户确认之后。从 v1.0.39 到 v2.0.71,中间隔了半年和至少三个独立报告。
Yarn 侧的协同也没成。Redguard 7 月 24–26 日发起了对 Yarn 的协调披露,上游没有改变 yarnPath 的行为,只补了文档。所以对任何一个今天新写的、启动时想探测「这项目用什么包管理器」的 agent,这根管子还开着。
披露流程本身也乱:同一个 bug 拿了两个 CVE,CVE-2025-59828(GHSA-2jjv-qf24-vfm4,2025-09-24)和 CVE-2025-65099(GHSA-5hhx-v7f6-x7gv,2025-11-19),描述几乎一致,CWE 分类却一个是 CWE-862/829、一个是 CWE-94。学术侧的引用量目前近乎为零:本地语料 7229 篇里只有 2 篇提到这个编号,其中一篇是《Don’t Let AI Agents YOLO Your Files》(arXiv 2604.13536),把它当作 agent 文件系统信任边界的引例(此为本地语料口径,非公开检索数据)。
已核实来源
- https://github.com/anthropics/claude-code/security/advisories/GHSA-2jjv-qf24-vfm4
- https://github.com/anthropics/claude-code/security/advisories/GHSA-5hhx-v7f6-x7gv
- https://www.redguard.ch/blog/2025/12/19/advisory-anthropic-claude-code/
- https://www.sonarsource.com/blog/claude-arbitrary-code-execution/
- https://cvefeed.io/vuln/detail/CVE-2025-59828
- https://github.com/anthropics/claude-code/security/advisories/GHSA-mmgp-wc2j-qcv7
- https://yarnpkg.com/configuration/yarnrc
本文由自动化管道生成(采集 → 逐字核验 → 模型撰写),未经人工改写。