商业产业 Hacker News AI 2026-09-17T20:05 正文 ok 1710 words

Plugin4Shell:四大主流编程 Agent 中发现零点击 RCE 漏洞

英文标题:🔶 Plugin4Shell – Zero Click RCE Vulnerability found in top four coding agents

安全研究人员披露了 Plugin4Shell——一种可绕过插件 SHA 固定(SHA pinning)机制的供应链漏洞,影响 Claude Code、OpenAI Codex、GitHub Copilot 和 Gemini CLI。漏洞使攻击者能够让 Agent 在看似仍使用已审核、已固定提交的情况下,检出并执行恶意代码;结合插件自动更新,可实现无需用户交互的远程代码执行(RCE)。Anthropic 已在 Claude Code 2.1.179 修复该问题,OpenAI 已在 Codex 0.146.0 修复;GitHub Copilot 尚无补丁,已弃用的 Gemini CLI 则不会获得修复。该事件表明,仅审查插件并固定 Git SHA 并不足以防御 Agent 插件供应链攻击,Agent 客户端必须在检出后验证实际 HEAD 提交与固定 SHA 一致。
打开原文相关 6 · 机会 1
media3.4 KB media108.4 KB media13.6 KB media134.5 KB media33.7 KB media82.4 KB media48.8 KB media431.0 KB media98.3 KB media105.3 KB media93.5 KB media542 B media251.3 KB media29.2 KB media41.4 KB media105.5 KB media608.7 KB media149.2 KB media52.2 KB media17.5 KB

中文

Agent 附加组件——即你安装到 AI Agent 中的插件和技能——实际上就是应用程序,而企业绝大多数都从开放的社区市场安装它们,而不是从经过审核、达到企业级标准的市场安装。Agent 拥有其操作员工的全部能力:同样访问敏感数据、内部系统和生产环境的权限。插件默认继承这些权限。因此,恶意插件无需提权:它可以在员工机器上实现完整的远程代码执行,并将攻击者对企业核心资产的访问范围提升至与该员工相同的级别。

事件进展

这是我们持续讲述的故事的第三幕。在 The Story of Skills 中,我们展示了入侵是多么容易:我们构建了一个恶意技能,看着它迅速传播,并控制了超过 26,000 个 Agent——将代码植入人们信任的市场并不难。在 SkillJacking 中,我们展示了你甚至无需植入任何内容:攻击者通过接管技能背后的代码仓库,劫持了 925 个已在使用的技能,影响 134,000 个 Agent,甚至绕过了原维护者。针对这种“抽地毯式”攻击,业界给出的应对方式正是 SHA 固定:在某个提交上审查代码,固定该提交,并相信此后始终运行该固定提交。Plugin4Shell 讲述的正是这条安全边界如何失效。

这是一种插件 SHA 固定绕过漏洞:Agent 会检出市场固定的精确提交,却从不验证最终是否真的检出了该提交。因此,控制插件仓库的攻击者可以让检出操作解析为恶意代码,同时固定操作看起来仍被遵守。结果是,Claude Code、Codex、GitHub Copilot 和 Gemini CLI 均可遭受零点击远程代码执行。

使用 Air Marketplace 和 Air Filter 的企业不受 Plugin4Shell 影响。

Plugin4Shell 为何独特

  • 这是 AI Agent 生态系统中首个供应链漏洞。 以往的 Agent 安全研究针对的是模型或 Agent 本身。Plugin4Shell 攻击的是它们之下的分发层,即将 Agent 附加组件送达数百万台机器的市场。
  • 零点击远程代码执行。 无需任何形式的用户交互。攻击结果是 Agent 及其运行所在主机被完全攻陷,进而获得对 Agent 可访问的每一项资产和每一份数据的完整访问权限。
  • 一个缺陷,所有主要实验室都犯了。 相同的设计错误存在于每一个受影响 Agent 中——这并非某个产品的单一实现疏漏,而是整个行业重复出现的同一个错误,使数百万 Agent 暴露于风险之中。
  • 市场无法彻底解决这个问题。 固定引用在 Agent 内部解析,因此只有 Agent 端修复才能恢复这一保障。市场可以通过仅允许会拒绝 SHA 形式名称的托管平台来缓解分支名称变体——实际上等同于仅支持 GitHub——但这会禁止 Agent 官方支持的其他托管平台,且对 Gemini CLI 的变体毫无作用。

谁会受影响,影响是什么

任何运行能够从市场安装插件的主流编程 Agent 的用户都面临风险,包括 Claude Code、OpenAI Codex、GitHub Copilot 和 Gemini CLI。风险并不局限于随意安装插件的用户:受害者只需安装过一个来自其信任市场、且已按照安全模型要求完成审查和固定的插件。

即使正确操作也无法保护你。超越社区市场、对插件进行审查并将其固定到已审查提交的组织,会将 SHA 固定视为安全保障;但 Plugin4Shell 会悄然使其失效:审查通过,固定引用被写入,但实际安装的是不同的代码。所有建立在固定机制之上的下游审核流程都会继承这一失效问题。

使其成为零点击攻击的是插件自动更新。Agent 会在后台更新已安装插件——在 Claude Code 和 Codex 中这是默认行为——因此,当上游替换固定提交时,用户已经信任且已经安装的插件会在无需任何用户交互的情况下被替换为恶意版本:没有安装步骤,没有提示,也没有任何可察觉的迹象。攻击者无需说服任何人安装新内容,只需要那个良性插件本来就已安装。

如何被利用

攻击路径有两种,且攻击者均无需控制市场。

方法 1:发布插件,然后将其变为恶意插件。 攻击者向可信市场提交一个真正良性的插件,通过审核后,再将其内容替换为恶意版本。将插件送入顶级市场并非假设——我们此前已经做到过(The Story of Skills)。

方法 2:劫持合法作者的插件。 攻击者接管他人编写、且市场已信任的插件背后仓库,随后利用 Plugin4Shell 将恶意版本强制推送给每一台安装该插件的 Agent——绕过原本正是为了阻止此类攻击而存在的版本固定机制。我们已在 SkillJacking 和 RepoJacking 中真实演示了仓库接管这一步。整条攻击链已被端到端证明:大规模接管确实发生,而 Plugin4Shell 能击败原本用于遏制它们的机制。

完整攻击流程:

1

植入

攻击者发布一个真正良性的插件,固定在提交 aaa...aaa。该插件通过审核。

2

采用

用户安装该插件。每一次安装都固定到 aaa...aaa,即经过审查且受信任的版本。

3

版本升级

攻击者发布一次常规更新:市场重新固定到新的、仍然良性的提交 bbb...bbb。固定引用发生变化。

4

抽地毯

攻击者创建一个名为 bbb...bbb 的分支,将其设置为仓库的默认分支,并使其指向恶意代码。被固定的提交本身可以保持不变。

5

自动更新至 RCE

变更后的固定引用触发每个 Agent 的后台自动更新。检出操作将 bbb...bbb 解析为该分支——git 会优先选择与提交同名的 ref——随后执行其中代码。没有提示,无需点击。

技术深入分析

每一个受影响的 Agent 都会检出被固定的提交,但从不检查最终是否真的检出了该提交。这一个缺失的检查就是全部漏洞,而 git 为攻击者提供了两种利用方式。

被固定的提交变成分支——Claude Code、Codex、GitHub Copilot

这些 Agent 会克隆插件仓库并检出固定 SHA:

git clone <plugin repo> ./ git checkout aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

现在控制上游仓库的攻击者,会创建一个名称与固定的 40 位十六进制 SHA 完全相同的分支,并将其设为仓库默认分支,因此上述普通的 git clone 会将其作为同名本地分支下载。随后,git checkout 会将固定 SHA 解析为该分支:当一个名称既是有效 ref 又是对象 ID 时,git 会优先使用 ref,并且只输出一条 refname is ambiguous 警告。因此,固定提交是否仍然存在不再重要,实际被检出的将是该分支。攻击得以成立有两个条件:没有机制阻止分支采用类似哈希的名称(git 自身的 git check-ref-format 接受 40 位十六进制名称,遵循该协议的托管平台也会接受);并且该分支必须是仓库的默认分支——非默认分支只会被获取为远程跟踪 ref,此时检出操作会回退到该提交。此时工作树已由攻击者控制,而 Agent 仍报告在固定提交处成功完成安装。

这不仅是安装时漏洞,这也正是它成为零点击漏洞的原因:同一条 git checkout 会在后台自动更新时再次执行——这是 Claude Code 和 Codex 的默认行为——因此,当市场更新固定 SHA 时,替换会在无需用户操作的情况下影响已经安装的插件。

此方法只适用于分支可被命名为哈希的平台。这是 git 的默认行为,但部分托管平台禁止这一点——GitHub 会直接拒绝 40 位十六进制的分支名称——而包括 Bitbucket 在内的其他平台以及任何自托管 git 服务器则允许这样做。这些托管平台上的市场属于受支持配置;Anthropic 自己的文档将 Bitbucket 和自托管 git 列为有效的市场后端。

固定引用被获取但从未被检出——Gemini CLI

Gemini 使用 --ref 固定版本,并通过三个步骤安装:

git clone --depth 1 <plugin repo> ./ git fetch origin 41d0bc0a4aeb2fbf797dacea39e876d98c95024b git checkout FETCH_HEAD

git fetch 会获取正确的提交,并将其记录在 .git/FETCH_HEAD 中。但 git checkout FETCH_HEAD 不一定会读取该文件:如果仓库默认分支本身命名为 FETCH_HEAD,检出操作会解析为该分支,而已获取的提交会被静默丢弃,转而使用由攻击者控制的默认分支内容。

一个断言即可封堵两种变体:在检出后,解析工作树中实际的提交;除非它等于固定 SHA,否则中止。

test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort

必须检查解析后的 HEAD,而不是被请求的 ref——这一差异正是 Gemini 变体所利用的漏洞。而且,检查必须在 Agent 内部执行:固定引用是在客户端解析的,因此市场无法强制执行其所宣称的保障。

如何缓解

由于固定引用是在 Agent 内部解析,没有市场能够强制执行它——修复必须随 Agent 一同发布;在已有修复的情况下,更新是唯一完整的缓解措施:

  • Claude Code - Anthropic 在我们披露后已于 2.1.179 修复该问题。
  • Codex - OpenAI 在我们披露后已于 0.146.0 修复该问题。
  • GitHub Copilot - 我们已向 Microsoft 披露相同漏洞,但其尚未发布修复,因此用户目前没有补丁可用。
  • Gemini CLI - Google 已弃用 Gemini CLI,且不会修复该问题,因此所有安装将永久处于易受攻击状态;这些用户应迁移至 Antigravity,该攻击无法影响它——它没有可被绕过的市场插件 SHA 固定机制。

时间线

时间 事件
2026 年 5 月 由 Air Security 研究实验室发现,并针对全部四个 Agent 制作出可运行的 PoC
2026 年 6 月 在协调披露机制下向四家厂商披露
2026-06-17 Anthropic 确认 Claude Code 2.1.179 中的修复
2026-08-04 Google 确认不会发布修复,原因是 Gemini CLI 已被弃用;建议用户迁移至 Antigravity
2026-08-12 验证 Codex 0.146.0 已修复

使用 Air Marketplace 和 Air Filter 的企业不受 Plugin4Shell 影响。

English / 原文

Agent add-ons - the plugins and skills you install into an AI agent - are effectively applications, and enterprises overwhelmingly install them from open community marketplaces rather than a vetted, enterprise-grade one. The agent runs with the full capabilities of the employee operating it: the same access to sensitive data, internal systems, and production environments. Plugins inherit those permissions by default. A malicious plugin therefore does not need to escalate anything: it achieves full remote code execution on the employee's machine and hands an attacker the same reach over the enterprise's crown jewels that the employee has.

The story so far

This is the third act of a story we have been telling. In The Story of Skills we showed how easy it is to get in: we built a malicious skill, watched it go viral, and seized control of over 26,000 agents - planting your code in a marketplace people trust is not the hard part. In SkillJacking we showed you don't even need to plant anything: 925 skills already in use were hijacked out from under their maintainers, affecting 134,000 agents, by taking over the repositories behind them. The industry's answer to exactly this kind of rug-pull is SHA pinning - review the code at one commit, pin that commit, and trust that the pinned commit is what runs forever after. Plugin4Shell is the story of that boundary failing.

It is a plugin SHA-pinning bypass: the agent checks out the exact commit the marketplace pinned but never verifies it landed there, so an attacker who controls the plugin's repo makes the checkout resolve to malicious code while the pin still looks honored. The result is zero-click remote code execution across Claude Code, Codex, GitHub Copilot, and Gemini CLI.

Enterprises using Air Marketplace and Air Filter were not affected by Plugin4Shell.

Why Plugin4Shell is unique

  • It is the first supply chain vulnerability of the AI agent ecosystem. Previous agent security work has targeted the model or the agent itself. Plugin4Shell attacks the distribution layer underneath them, the marketplaces through which agent add-ons reach millions of machines.
  • Zero-click remote code execution. No user interaction of any kind is required. The result is full compromise of the agent and the host it runs on, and with it full access to every asset and every piece of data the agent can reach.
  • One flaw, and every major lab made it. The same design error sits in every affected agent - not an implementation slip in one product, but a single mistake repeated across the industry, leaving millions of agents vulnerable.
  • A marketplace cannot fully close this. The pin is resolved inside the agent, so only an agent-side fix restores the guarantee. A marketplace can blunt the branch-name variant by allowing only hosts that reject SHA-shaped names - effectively, GitHub-only - but that bans hosts the agents officially support, and does nothing for Gemini CLI's variant.

Who is affected and what is the impact

Anyone running a major coding agent that installs plugins from a marketplace is exposed. That is Claude Code, OpenAI Codex, GitHub Copilot, and Gemini CLI. The exposure is not limited to users who install plugins carelessly: the victim only has to have a plugin installed, from a marketplace they trust, that was reviewed and pinned exactly as the security model intends.

Doing the right thing does not protect you. Organizations that go beyond a community marketplace - reviewing plugins and pinning them to a reviewed commit - rely on SHA pinning as their safeguard, and Plugin4Shell quietly nullifies it: review passes, the pin is written, and different code installs. Every downstream vetting process built on pinning inherits the failure.

What makes it 0-click is plugin auto-update. Agents update installed plugins in the background - in Claude Code and Codex this is the default - so when the pinned commit is swapped upstream, a plugin the user already trusts and already has installed is replaced with a malicious version without any user interaction: no install step, no prompt, nothing to notice. The attacker does not need to persuade anyone to install anything new. They only need the benign plugin to already be there.

How it can be exploited

There are two paths, and neither requires the attacker to control the marketplace.

Method 1: publish a plugin, then turn it malicious. The attacker contributes a genuinely benign plugin to a trusted marketplace, passes review, and later swaps its content for a malicious version. Getting a plugin into a top marketplace is not hypothetical - we've already done it (The Story of Skills).

Method 2: hijack a legitimate author's plugin. The attacker takes over the repository behind a plugin someone else wrote and the marketplace already trusts, then exploits Plugin4Shell to force the malicious version onto every agent that has it installed - bypassing the version pinning that exists precisely to stop this. We demonstrated the takeover step in the wild in SkillJacking and RepoJacking. Together, the chain is proven end to end - takeovers happen at scale, and Plugin4Shell defeats the mechanism built to contain them.

The attack, end to end:

1

Plant

The attacker publishes a genuinely benign plugin, pinned at commit aaa...aaa. It passes review.

2

Adoption

People install it. Every install is pinned to aaa...aaa, the reviewed, trusted version.

3

Version bump

The attacker ships a routine update: the marketplace re-pins to a new, still-benign commit bbb...bbb. The pin has changed.

4

Rug-pull

The attacker creates a branch named bbb...bbb, sets it as the repo's default , and points it at malicious code. The pinned commit itself can stay untouched.

5

Auto-update to RCE

The changed pin triggers every agent's background auto-update. The checkout resolves bbb...bbb to the branch - git prefers a ref over a commit of the same name - and it runs. No prompt, no click.

Technical deep dive

Every affected agent checks out the pinned commit but never checks that it actually landed there. That one missing check is the whole bug - and git gives an attacker two ways to exploit it.

The pinned commit becomes a branch - Claude Code, Codex, GitHub Copilot

These agents clone the plugin repository and check out the pinned SHA:

git clone <plugin repo> ./ git checkout aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

The attacker, who now controls the upstream repository, creates a branch whose name is the exact 40-hex pinned SHA and makes it the repository's default, so the plain git clone above brings it down as a local branch of that name. git checkout then resolves the pinned SHA to that branch: when a name is both a valid ref and an object id, git prefers the ref and only prints a refname is ambiguous warning - so it never matters whether the pinned commit is still present, the branch is what gets checked out. Two conditions make it work: nothing blocks a branch from being named like a hash (git's own git check-ref-format accepts 40-hex names, and hosts that follow the protocol accept them too), and the branch must be the repository's default - a non-default branch is fetched only as a remote-tracking ref, and the checkout would fall back to the commit. The working tree is now attacker-controlled, and the agent reports a successful install at the pinned commit.

This is not only an install-time bug, and that is what makes it zero-click: the same git checkout re-runs on background auto-update - the default in Claude Code and Codex - so when the marketplace bumps the pinned SHA, the swap reaches already-installed plugins with no user action.

This only works where a branch can be named like a hash. That is git's default behavior, but some hosts forbid it - GitHub rejects a 40-hex branch name outright - while others, Bitbucket among them, and any self-hosted git server, allow it. Marketplaces on those hosts are a supported configuration; Anthropic's own documentation lists Bitbucket and self-hosted git as valid marketplace backends.

The pin is fetched but never checked out - Gemini CLI

Gemini pins with --ref and installs in three steps:

git clone --depth 1 <plugin repo> ./ git fetch origin 41d0bc0a4aeb2fbf797dacea39e876d98c95024b git checkout FETCH_HEAD

The fetch retrieves the correct commit and records it in .git/FETCH_HEAD. But git checkout FETCH_HEAD does not have to read that file: if the repository's default branch is itself named FETCH_HEAD, the checkout resolves to the branch, and the fetched commit is silently discarded in favor of attacker-controlled default-branch content.

One assertion closes both variants: after checkout, resolve the commit actually in the working tree and abort unless it equals the pinned SHA.

test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort

It has to check the resolved HEAD, not the ref that was requested - that distinction is exactly what the Gemini variant slips through. And it has to run inside the agent: the pin is resolved on the client, so no marketplace can enforce the guarantee it advertises.

How it can be mitigated

Because the pin is resolved inside the agent, no marketplace can enforce it - the fix has to ship in the agent, and updating is the only complete mitigation where one exists:

  • Claude Code - Anthropic patched it after our disclosure, in 2.1.179.
  • Codex - OpenAI patched it after our disclosure, in 0.146.0.
  • GitHub Copilot - we disclosed the same flaw to Microsoft, which has not shipped a fix, so users have no patch.
  • Gemini CLI - Google has deprecated the Gemini CLI and will not patch it, so every install stays vulnerable for good; those users should migrate to Antigravity, which this attack does not reach - it has no marketplace plugin SHA pinning to bypass.

Timeline

When What
May 2026 Found by the Air Security research lab, with a working PoC against all four agents
June 2026 Disclosed to all four vendors under coordinated disclosure
2026-06-17 Anthropic confirms the fix in Claude Code 2.1.179
2026-08-04 Google confirms no fix will ship, as the Gemini CLI is deprecated. Users are advised to migrate to Antigravity
2026-08-12 Codex 0.146.0 verified fixed

Enterprises using Air Marketplace and Air Filter were not affected by Plugin4Shell. Book a demo to learn more about how can Air protect from agentic supply chain vulnerabilities.