Plugin4Shell 实测:分支名伪装成 SHA,四大 AI 编程助手的插件校验全线失守

Plugin4Shell 实测:分支名伪装成 SHA,四大 AI 编程助手的插件校验全线失守

你什么都没点,插件自己换了。

Claude Code、OpenAI Codex、GitHub Copilot、Gemini CLI——四款最主流的 AI 编程助手,插件市场用的是同一套校验逻辑,也犯了同一个错误。9 月 17 日,安全公司 Air 公开了这处漏洞,取名 Plugin4Shell:不点链接、不装新插件、不改任何设置,只要攻击者控制了你已装插件的上游仓库,恶意代码就会自己走进你的电脑。Air 称它是「AI Agent 生态的第一个供应链漏洞」,受影响的是「数百万个 agent」。

校验通过了,代码为什么还是会被换掉

给 AI 编程助手装插件,走的是包管理器的老路:市场把插件锁在一个具体 commit 的 40 位 SHA 上,这叫 SHA pinning。逻辑很干净——审过的代码就是这一版,谁再改都会露馅,这也是企业敢用第三方插件的前提。

四款 agent 都老老实实执行了「checkout 市场钉下的那个 SHA」,但没有一家检查「checkout 完,工作区里到底是哪个 commit」。这一处缺失就是整个漏洞。git 在解析一个名字时,如果它既能当 ref(分支/标签)又能当对象 id,会优先当成 ref,只在 stderr 打一行 refname '…' is ambiguous。攻击者在自己的插件仓库里建一条名字就是那串 SHA 的分支,把它设为仓库默认分支,agent 的 git checkout 就会把这条分支当成目标——审查、pin、安装流程全部显示正常。

要强调的是,受害者的操作没有任何问题。Air 的原话是:受害者只需要装了一个插件,来自他信任的市场,经过审查、并且「完全按照安全模型的设计」被 pin 住——企业把插件 pin 到审过的 commit 再分发,本来是把风险往下压的做法,这一层也同样被穿透,「所有建立在 pin 上的下游审查流程,都继承了这个失效」。

攻击有两条路。一条是先当好人:往市场里提交一个真良性插件,过审、被装,之后再把它变恶意——这件事 Air 此前已经做过一遍,那个假 skill 拿到了 26,000 个 agent 的控制权。另一条更省事:直接接管别人的仓库,让恶意版本顺着这条路径推给所有已装用户,而 pin 存在的意义本来是拦住这件事。

我把两个变体都跑了一遍

Air 描述了两条路径。我在本地沙盒(git 2.47.3)把它们都复现了一遍。

变体一是 Claude Code、Codex、GitHub Copilot 共用的写法:

git clone <插件仓库> ./
git checkout 59276317d87a72f4396ef9b61b0ba4be3e005e95   # 市场钉下的 SHA

这条 SHA 同时是攻击者建的默认分支名。执行结果:只有一行警告 refname is ambiguous,然后 plugin.txt 的内容是 MALICIOUS PAYLOAD,当前分支显示为那串 SHA。顺带核了一下前提条件,git check-ref-format refs/heads/<40位十六进制> 返回 0——git 自己就接受这种分支名

我还做了对照组:同样存在一条 SHA 同名分支,但它不是默认分支。这次 clone 下来它只是一条 remote-tracking ref,git checkout 落回了真正的 commit,HEAD 与 pin 完全相等,文件还是良性版本,攻击不成立。也就是说,「把 SHA 同名分支设成默认分支」是攻击的必要条件,不是可选动作。

变体二是 Gemini CLI 的三步写法:

git clone --depth 1 <插件仓库> ./
git fetch origin 41d0bc0a4aeb2fbf797dacea39e876d98c95024b
git checkout FETCH_HEAD

如果仓库的默认分支恰好叫 FETCH_HEAD,第三步同样只打一行 ambiguous 警告,然后落地恶意内容;此时 .git/FETCH_HEAD 文件里躺着的,仍然是那个被正确拉取的 commit——拉回来了,然后被丢掉了。

修复只要一行断言,Air 给的原话是:checkout 之后解析工作区真正的 HEAD,不等于 pin 就中止。

test "$(git rev-parse HEAD)" = "" || abort

这行必须写在 agent 里、不能交给市场,因为 pin 是在客户端解析的。这也正是它叫「零点击」的原因:后台自动更新是 Claude Code 和 Codex 的默认行为,市场一改 pin,所有已装用户会在没有任何提示的情况下被换成恶意版本——攻击者甚至不需要你装任何新东西,只需要那个良性插件本来就在你机器上。

四家的响应,和我在 npm 上核到的版本

厂商 产品 当前状态
Anthropic Claude Code 已修复,2.1.179(2026-06-17 确认)
OpenAI Codex 已修复,0.146.0(2026-08-12 验证)
GitHub / 微软 GitHub Copilot 未发补丁
谷歌 Gemini CLI 不修,直接弃用

Air 给出的时间线是:2026 年 5 月发现并对四款都做出可用 PoC,6 月完成披露,8 月 4 日谷歌确认不修、让用户迁到 Antigravity,9 月 17 日公开。

修复版本是不是真的发出去了,可以直接在 npm 上核:Claude Code 当前 2.1.278、Codex 0.155.1,都已经越过修复线;@github/copilot 停在 1.0.86,仍在更新但没修这个洞。Gemini CLI 更有意思——谷歌 8 月就说弃用,可 npm 上的 nightly 一直发到 0.62.0-nightly.20260919(9 月 19 日),仓库活着,只是这个漏洞不打算修了,已装的每一台都永久暴露。

微软一侧的说法值得单独记一笔。GitHub 发言人对 The Register 表示:为防止 SHA 被滥用,GitHub 不允许创建形似 commit SHA 的分支名或标签名,因此这个漏洞在 GitHub 上无法利用。Air 的回应是这条缓解不够,因为 Claude Code 官方文档明确写着市场可以托管在「任何 git 服务」上——GitLab、Bitbucket、自建服务器都行,而 Bitbucket 和自建 git 都允许 40 位十六进制分支名;微软自家的 Copilot 同样支持这些来源。Air 还补了一句:6 月起就向微软报告过,因为对方披露量太大,没有收到回复。

规模上有一组可查的数字:近 30 天 npm 下载量,Claude Code 6,486 万次、Codex 7,850 万次、GitHub Copilot 1,026 万次、Gemini CLI 139 万次,合计约 1.55 亿次(下载量不等于机器数,CI 会放大,但量级可参考)。这也不是 Air 第一次做这件事——此前它用一个假 skill 拿到过 26,000 个 agent 的控制权,SkillJacking 里发现 925 个在用 skill 的仓库可被接管、波及 134,000 个 agent,8 月又找出 155 个依赖过期域名、可被劫持的 MCP。

需要声明的是,Air 是一家 9 月 1 日刚走出隐身状态的创业公司,卖的产品正是 agent 防护,研究文末还写着「使用我们产品的企业不受此漏洞影响」「数据来源方在卖解药」这件事,读数字时要一起考虑。漏洞本身不受影响,因为上面的复现是我自己跑的。中文圈的报道到 9 月 19 日基本还是快讯:新浪财经、凤凰网各一条,其中凤凰写的是「除 GitHub 外其余三家已修复」——按 Air 的时间线,谷歌是明确不修、直接弃用,不是修复。

这次坏掉的其实是一个假设

Plugin4Shell 里没有一行复杂的利用代码,它打掉的是行业默认的一个安全假设:把 hash 钉死,就等于把代码钉死

这个假设在包管理世界里早就被拆过一遍——npm 后来补上了包签名和透明日志,因为「内容的哈希」只能证明内容没变,不能证明「你解析到的就是那份内容」。agent 生态正在把插件、skill、MCP 当作基础设施大规模铺开,校验逻辑却还停在「拿到一个 SHA 就 checkout」的水平上。四家实验室写出了同一个 bug,不是巧合,是这一层还没有人认真做过。

你该做什么

  • Claude Code 与 Codex 用户:升级到 2.1.179 / 0.146.0 以上(当前版本已远超),这是唯一完整的修复。
  • GitHub Copilot 用户:官方没有补丁。Air 给出的现实约束是,尽量只从 GitHub 托管的 marketplace 装插件,因为 Bitbucket 和自建 git 托管的市场都能被这种手法打穿。
  • Gemini CLI 用户:官方建议迁到 Antigravity,它没有这套插件 SHA pinning 机制,因此攻击面不存在;留在 Gemini CLI 上就是永久暴露。
  • 所有人:如果你所在团队的内部市场是自己 pin 的 commit,去检查一遍默认分支名有没有 40 位十六进制的;同时考虑关掉插件的后台自动更新,把更新动作变成需要人确认的一步。

一行断言就能修的东西,从披露到公开花了四个月,还有两家没修。Agent 的供应链安全,现在还处在包管理器的 2015 年——插件越多,这个账越难还。

相关阅读: