Forty hexadecimal characters were the last promise left standing: pin the plugin to this commit and it cannot change underneath you. The four most-used coding agents pin every plugin that way. None of them checked the result.
The commit is still there. It is still the right one. It is still the one you reviewed.
And it is not the one that ran.
Forty characters of guarantee
You don't pin a tag, which moves, or a branch, which gets rewritten. You pin a commit: forty characters that cryptographically summarize the content. If the content changes, the number changes.
GitHub wrote it as a promise in the Copilot CLI plugin reference, still published: "sha must be a full 40-character commit SHA. Pin to a sha for reproducible installs that are immune to force-pushes or tag/branch moves." Immune. The word is theirs. Claude Code's documentation promises less, and the same: the sha is the effective pin, and the agent checks out that commit directly.
On September 17, 2026, Air Security published that all four agents run git checkout on the pinned commit and none of them checks where HEAD landed. It named the flaw Plugin4Shell: "a zero-click, high-severity RCE affecting all four major AI coding agents - Claude Code, Codex, Copilot, and Gemini."
Git never promised that
The root isn't in any of the four programs but in a git rule documented forever, which AIR puts in one line: "When a name is both a valid ref and an object id, git prefers the ref". The reference wins, and the object doesn't even appear in the disambiguation list.
And what does git do when it spots the ambiguity? It warns. One line on a console, in a background process nobody watches, and on it goes. It never matters whether the pinned commit is still there.
One piece is missing: someone has to be able to name a branch the way a commit is named. Git's ref format doesn't forbid it. The ban exists, but it belongs to a single company: GitHub rejects forty-character hex names. Every other host that follows the protocol accepts them.
Five steps, zero clicks
The chain AIR describes takes no ingenuity. You publish a genuinely benign plugin, pinned at aaa...aaa, and it passes review. People install it. A routine update re-pins the catalog to bbb...bbb, still clean. Then the repository owner creates a branch named bbb...bbb, sets it as the default, and points it at malicious code.
The agent does the rest: the changed pin triggers the background auto-update, the checkout resolves bbb...bbb to the branch, and the code runs. No screen, no permission, nobody awake. And then the most expensive line in the report: "The agent reports a successful install at the pinned commit."
In Gemini CLI it sits in plain sight, in Google's own code: cloneFromGit, in extensions/github.ts, clones, fetches the ref and runs await git.checkout('FETCH_HEAD');. The comment right above signs off on it: "This results in a detached HEAD state, which is fine for this purpose." Afterward not one line checks anything.
Here's the part that stings. One hundred and eighty lines further down, in the same file, const localHash = await git.revparse(['HEAD']); already lives — the exact primitive of the fix, used to check whether an update is available. Never to check whether what got installed is what was requested. The tool was in the box. It measured. It never verified.
One flaw, and all four made it
"One flaw, and every major lab made it", AIR writes. Four teams built the same guarantee and none checked it, because checking feels redundant: you just asked for the commit by name, what else is there to verify?
That reflex is the entire hole, and a catalog can't patch it: "A marketplace cannot fully close this. The pin is resolved inside the agent". The one who ends up PAILA isn't the one who installed without looking. It's the one who pinned, reviewed, and trusted the number.
With ClawHavoc we covered a catalog with 341 malicious skills out of 2,857, no review and no signature. Plugin4Shell is worse: here there was review and there was a pin, and they were decoration. Nor is this a laboratory concern: according to Microsoft, almost 90 percent of the Fortune 500 use Copilot.
The fix has been sitting there for sixty-six days
On July 16, 2026, two months before anybody published anything, DavidAPierce opened pull request #28422 on gemini-cli: it describes the Gemini variant — if the default branch is named FETCH_HEAD, the checkout resolves to the branch — and adds what was missing: git rev-parse HEAD, to confirm you landed on the commit you asked for. bdmorgan approved it nine minutes later. Hardanish-Singh approved it on August 12, and that approval is the last activity on the thread: as of September 20 it is still open, unmerged, with conflicts. How many approvals does it take?
Not an abandoned repository, either: GitHub's API answers archived=false and 107,090 stars, with commits on September 17 and 18. The product is deprecated; the repository is alive; the fix is written and approved; the vulnerable file hasn't been touched since August 13.
On August 4 Google confirmed to AIR that no fix will ship: Gemini CLI is deprecated. Two and a half months earlier, its own transition announcement promised the product would stay accessible through paid API keys, and more: "We'll continue to support Gemini CLI and Gemini Code Assist with access to the latest Gemini models and other updates." An unpatched RCE, apparently, is not one of the "other updates".
The two who did fix it never called it security. Claude Code 2.1.179 shipped on npm on June 16, and Anthropic confirmed the fix to AIR on the 17th. That version's entry in the CHANGELOG has nine bullets, and the only one that names plugins reads "Improved plugin loading performance in remote sessions". Loading performance. Not one of the nine mentions the fix.
In Codex the patch is public: pull request #34644, Verify Git plugin SHA checkouts, with the flaw written down plainly — "Git can interpret a requested commit SHA as a branch name when the remote's default branch has the same name" — and a regression test. copyberry[bot] opened it and copyberry[bot] merged it, one hundred and thirty-five seconds later: that's not urgency, it's an automated mirror. In the 0.146.0 version notes it appears once, in the raw list of pull requests: not under New Features, not under Bug Fixes, and that line doesn't carry the word security.
Anthropic wrote it nowhere. OpenAI wrote it on one line, number 34644 of a list of hundreds.
The ecosystem answered in kind: eleven points and three comments on Hacker News. One complained about the prose. Another shrugged: "Pretty bad for a package manager, but this seems like something I would unfortunately expect from a harness/agent." Expected, apparently.
Attribution
Perpetrator: the four agents. Each asked for a commit by name and accepted whatever came back. The fix AIR proposes fits on one line: test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort. Two of them already have it. Copilot and Gemini CLI, as of September 20, do not. Microsoft never even answered the disclosure; AIR blames the volume of reports it receives.
Accomplices: the documentation. Both promises that open this article are still published, with no warning and no line saying the agent verifies the resulting commit. GitHub has a legitimate argument, made by a spokesperson to The Register: "This mitigation ensures the reported vulnerability cannot be exploited on GitHub." True, and insufficient: its own documentation allows a Copilot catalog to live on any other git service. The protection doesn't come from the agent. It comes from one host, and only one. The same pattern was still alive in another corner of Claude Code, and the report documenting it was closed by a bot for inactivity.
Systemic failure: pinning to a SHA is a ritual, exactly like reviewing. You do it once, it feels final, and nobody looks again. We already wrote this with the confused deputy: permission is granted up front and nobody revisits it afterward. Here the deception doesn't even come through the conversation: it comes through the installer. The counterpoint goes in whole — whoever publishes the alarm sells the alarm. AIR came out in public on September 1, sixteen days earlier, with a product it sells as a firewall for the context agents operate in; and its writeup includes the line "Enterprises using Air Marketplace and Air Filter were not affected by Plugin4Shell." Pebblous says it without accusing anybody: the severity and the reach come from the side selling the remedy, and the millions of agents in the headline are the finders' own estimate, with no third-party verification.
As of September 20 there is no CVE, no GHSA, and no vendor has published an advisory. The Hacker News checked on the 18th and added the sentence that explains it: "there is no sign it has been used in a real attack." No known victims. That's why nobody panicked.
The commit you reviewed is still in its repository, intact, with its correct hash. It never moved. How many of your certainties are like that — true, verifiable, and about the wrong thing?