SHA Pinning Is Not Verification: Plugin4Shell and the First AI-Agent Supply-Chain RCE
925 plugin skills have already been hijacked from their original maintainers, and those skills reach 134,000 agents. On September 17, 2026, AIR Security disclosed Plugin4Shell — the first supply-chain vulnerability of the AI agent ecosystem: a zero-click remote code execution affecting Claude Code, OpenAI Codex, GitHub Copilot, and Google Gemini CLI, all through the same missing check. The mechanism is one git assertion every affected agent skips: the agent checks out the marketplace-pinned commit SHA but never verifies the working tree actually landed on that commit. An attacker who controls a plugin repository names a branch after the pinned 40-character SHA, makes it the repo default, and the agent installs attacker-controlled code while reporting a clean install at the pinned commit (Cyber Security News; Help Net Security).
This matters to any team running coding agents against production systems because the pin was the control. Organizations that go beyond a community marketplace — reviewing plugin code, pinning installs to a reviewed commit — relied on SHA pinning as their safeguard, and Plugin4Shell quietly nullifies it: the review passes, the pin is written, and different code runs (AIR). This article covers four things a Head of Engineering needs before the next plugin auto-update fires: the mechanism in one git command, the five-step attack from benign adoption to background RCE, the vendor-response scoreboard (two patched, one unpatched, one deprecated-unpatched), and the verification items that close the gap for every pinned artifact your agents install — plugins, MCP servers, and skills.
Key takeaways
- Zero-click RCE hit all four major coding agents — Claude Code, Codex, GitHub Copilot, and Gemini CLI — through one missing git assertion — AIR disclosed Plugin4Shell on September 17, 2026: agents check out the marketplace-pinned commit SHA but never verify the checkout resolved to it (AIR).
- 925 skills have already been hijacked from their maintainers, reaching 134,000 agents — AIR's SkillJacking research; Plugin4Shell defeats the SHA-pinning mechanism built to contain exactly these repository takeovers (AIR).
- Git prefers a branch named like a hash over the hash itself — a branch carrying the pinned 40-character SHA as its name, set as the repo default, redirects
git checkout <sha>to attacker code; Gemini CLI's variant shadowsFETCH_HEADinstead (Cyber Security News). - The vendor scoreboard stands at 2 patched, 1 unpatched, 1 deprecated-unpatched — Anthropic fixed Claude Code in 2.1.179 and OpenAI fixed Codex in 0.146.0; Microsoft has shipped no Copilot fix, and Google deprecated Gemini CLI unpatched, so every existing install stays exposed (Help Net Security).
- One assertion closes both variants: verify the resolved HEAD against the pinned SHA after checkout —
test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort— and it must run inside the agent, because no marketplace can enforce a pin it does not resolve (AIR).
The diagram below compresses the disclosure into one minute: the five-step attack chain, the vendor scoreboard three days in, and the assertion that closes the gap.
The mechanism: one assertion, four agents
SHA pinning is the marketplace security model working as designed. A reviewer inspects a plugin at one commit, the marketplace records that commit's SHA, and the agent is supposed to install exactly that code forever. The failure is in the last step. Every affected agent runs roughly this sequence (AIR):
git clone ./
git checkout aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa # the pinned SHA and never asks whether the checkout actually landed on the pinned commit. That omission is exploitable because git resolves names before objects: when a name is both a valid ref and a commit hash, git prefers the ref and only prints a refname is ambiguous warning. The attacker — who already controls the upstream repository — creates a branch whose name is the exact 40-hex pinned SHA and sets it as the repository default. A plain git clone brings that branch down as a local ref, git checkout <sha> resolves to the branch, and the working tree is now attacker-controlled while the agent reports a successful install at the pinned SHA (Cyber Security News).
Two conditions make the trick work. First, nothing universally blocks a branch named like a hash: git's own check-ref-format accepts 40-hex names, and while GitHub rejects them outright, Bitbucket and self-hosted git servers allow them — and Anthropic's own documentation lists Bitbucket and self-hosted git as valid marketplace backends (AIR). Second, the branch must be the repository's default; a non-default branch arrives only as a remote-tracking ref, and the checkout falls back to the real commit.
Gemini CLI fails differently. Its install sequence fetches the pinned commit and then checks out FETCH_HEAD:
git clone --depth 1 ./
git fetch origin 41d0bc0a4aeb2fbf797dacea39e876d98c95024b
git checkout FETCH_HEAD If the repository's default branch is itself named FETCH_HEAD, the checkout resolves to that branch and silently discards the fetched commit (AIR). Different command, same root cause: the agent trusts the name it requested instead of verifying the commit it received.
The attack, end to end: five steps from adoption to RCE
Neither attack path requires controlling the marketplace. AIR demonstrated both:
- Plant. The attacker publishes a genuinely benign plugin, pinned at commit
aaa…aaa. It passes review. - Adoption. Users install it. Every install is pinned to the reviewed commit.
- Re-pin. The attacker ships a routine, still-benign update; the marketplace bumps the pin to
bbb…bbb. - Rug-pull. The attacker creates a branch named
bbb…bbb, sets it as the repo default, and points it at malicious code. The pinned commit itself can stay untouched. - Auto-update to RCE. The changed pin triggers every agent's background auto-update — default behavior in Claude Code and Codex — the checkout resolves the new pin to the branch, and the code runs with no click, no prompt, and no reinstall (AIR).
The alternative path is faster: take over a legitimate maintainer's repository and skip the planting step entirely. AIR's companion research quantifies how common that already is — 925 skills hijacked from their original maintainers, reaching 134,000 agents (AIR). It is the third act in a series: The Story of Skills showed a malicious skill reaching 26,000 agents, and MCPJacking found 155 hijackable MCP servers in the official marketplace through expired domains, which the researchers registered to gain remote prompt execution on every agent that trusted them (AIR). Plugin4Shell is the boundary failure of the mechanism the industry built to contain all of it. The blast radius is also larger than the agent: plugins and add-ons inherit the permissions of the developer running the agent — local source code, cloud credentials, SSH keys, internal repositories, and production systems (Cyber Security News).
The vendor scoreboard: two patched, one unpatched, one deprecated-unpatched
The disclosure timeline shows coordinated disclosure worked — and where it stopped:
| When | What |
|---|---|
| May 2026 | AIR finds the flaw, with a working PoC against all four agents |
| June 2026 | Coordinated disclosure to all four vendors |
| Jun 17, 2026 | Anthropic confirms the fix in Claude Code 2.1.179 |
| Aug 4, 2026 | Google confirms no fix will ship — Gemini CLI is deprecated; users are told to migrate to Antigravity |
| Aug 12, 2026 | OpenAI's Codex 0.146.0 verified fixed |
| Sep 17, 2026 | Public disclosure |
As of September 18–19, the scoreboard is unchanged: Anthropic patched, OpenAI patched, Microsoft has shipped no Copilot fix, and Google deprecated Gemini CLI unpatched — meaning every existing Gemini CLI install stays exposed indefinitely (Help Net Security). GitHub's response — banning SHA-shaped branch and tag names on its platform — does not close the gap, because marketplaces can be hosted on Bitbucket or self-hosted git servers where such names remain legal (Cyber Security News).
AIR's summary is the honest one: "The fix has to ship in the agent, and updating is the only complete mitigation where one exists" (AIR). The structural reason is worth restating for procurement conversations: the pin is resolved inside the agent, so a marketplace cannot enforce the guarantee it advertises. Vendor-side scanning of marketplace uploads (Anthropic shipped Skill/Plugin Scanning on August 6, 2026) reduces the probability that a malicious plugin enters the marketplace, but it cannot substitute for the agent-side checkout verification — the swap happens after review, at resolution time (MCP Security Hardening Checklist). This is also the governance exhibit: four vendors, one shared design flaw, and a three-day-old asymmetric response that a buying organization can read as a vendor's security patch cadence in miniature.
Verification after checkout: the control that generalizes
AIR's one-line fix closes both variants (AIR):
test "$(git rev-parse HEAD)" = "" || abort The details carry the lesson. git rev-parse HEAD resolves what the working tree actually contains — not the name that was requested. That distinction is exactly what the Gemini FETCH_HEAD variant slips through. And the check must run inside the agent, because the pin is resolved on the client. A marketplace that verifies after checkout would only be verifying its own record; the agent is the component that must abort on mismatch.
That pattern — verify after resolution, not before — generalizes to every pinned-artifact control in an agent stack. A pinned MCP server version, a pinned skill, a pinned model weight, a pinned container digest: each is a claim that some installer is trusted to honor and nobody verifies the resolution of. The same missing assertion lives wherever the checkout happens. Four items to work through this week:
- Update the agents that have fixes. Claude Code to 2.1.179 or later; Codex to 0.146.0 or later. Copilot and Gemini CLI have no fix — restrict what those agents can reach (filesystem, credentials, network egress) until one ships, or follow the vendor's migration path.
- Inventory every pinned artifact. Plugins, skills, MCP server versions, internal installers. For each, confirm whether any code path skips the resolved-HEAD assertion — this includes internal tooling your team wrote, not just vendor agents.
- Audit plugin repositories for takeover signals. Unexpected branch changes, default-branch moves, ownership transfers. The 925 hijacked skills were taken before this disclosure; repo takeover is the entry step and it is already operating at scale (Cyber Security News).
- Treat plugin auto-update as a supply-chain delivery channel, not a convenience. Allowlist the marketplaces and repositories agents may pull from; gate re-pins behind re-review where the agent's configuration allows it. The zero-click property comes from auto-update — remove the "zero" and the attack needs a user action again.
Two adjacent CVEs, and the standing exposure
The same week produced two MCP-server CVEs that share Plugin4Shell's theme — trust placed in a component that never verified its caller. CVE-2026-54618 affects Obsidian Web MCP before 0.2.0: the OAuth authorization endpoint issued codes to any caller without authenticating the user, granting an unauthenticated remote attacker full read, write, search, move, and delete access to the entire vault, fixed in 0.2.0 (Rapid7; GitHub advisory GHSA-hwhg-mrjc-8g43). CVE-2026-54446 is a missing-authentication flaw (CWE-306) in NetLicensing-MCP (Practical DevSecOps). They land on top of standing ecosystem numbers that have not moved in months: 97M+ monthly MCP downloads, 82% of sampled servers vulnerable to path traversal, and only 8.5% using OAuth (Practical DevSecOps).
The pattern across all three disclosures is the same at every layer of the agent stack: a trust mechanism — a pin, an OAuth flow, a server endpoint — that performs its check at the wrong time or not at all. Plugin4Shell is simply the first one that crossed from one product to the entire ecosystem at once.
Related reading
- MCP Security Hardening Checklist: 1,467 Exposed Servers and the Controls That Close Them — the 12-control baseline this vulnerability slots into: Plugin4Shell belongs in the supply-chain layer (controls 5–7), next to the Skill/Plugin Scanning update
- Shadow AI Agents: 17,800 Add-Ons, 6.7 Million Installations, and the Runtime Control Gap — the inventory and runtime-control gap that determines whether a hijacked add-on gets noticed
- The First MCP CVE on the KEV List Hit Its Federal Deadline: LiteLLM and the Default-Key Vault — the MCP CVE that reached federal remediation status, and the six gateway verification items that close it
A mid-market industrial distributor runs Claude Code for internal tooling and a procurement agent that quotes against NetSuite and two supplier catalogs. The team inventories every pinned artifact across both surfaces, upgrades Claude Code to 2.1.179, disables background plugin auto-update, and adds the resolved-HEAD assertion to its internal MCP module installer. Plugin sources move to an allowlist of two reviewed repositories, and the audit trail records every re-pin with its diff. The next marketplace incident becomes a version bump and a review, not an incident response.
Request a scoped build. One-week discovery. You get a system inventory, workflow map, and fixed scope — whether or not you build with us.
Want this built for your systems?
Every document here comes from real production work. If you have a target system and a workflow in mind, we can scope a build in one week.
Request a scoped buildOne-week discovery. You get a system inventory, workflow map, and fixed scope — whether or not you build with us.