P.K. SHARMA

Cyber security intelligence, AI governance, practitioner analysis

Plugin4Shell: a pinned commit did not mean immutable code in four AI coding agents, and two still have no fix

Air Security showed that Claude Code, Codex, GitHub Copilot and Gemini CLI check out a pinned plugin commit but never check what landed. Anthropic and OpenAI fixed it in June and July without saying so in their release notes.

By Parminder Kumar Sharma · · 23 min read

Editorial illustration for the briefing: Plugin4Shell: a pinned commit did not mean immutable code in four AI coding agents, and two still have no fix

The fix shipped 94 days ago and the release notes never mentioned it

Claude Code 2.1.179 was published to the npm registry at 17:51 UTC on 16 June 2026, 94 days before this was written. Its entry in Anthropic's public changelog lists nine items. They cover mid-stream connection drops, mouse-wheel scrolling in WSL2, a sandbox glob that made the Bash tool description enormous, a feedback survey capturing a single digit as a rating, promotional banners stacking on the welcome screen, two focus bugs, background tasks that looked stuck, and improved plugin loading performance in remote sessions.

None of those nine is the reason security researchers now name that version. According to Air Security, which disclosed the flaw, 2.1.179 is the release in which Anthropic fixed a zero-click remote code execution path that reached any Claude Code user with a marketplace plugin installed. Eighty-two further versions of Claude Code have been published since, up to 2.1.276 on 18 September. Searching the entire changelog for the words the fix would need, pinning, rev-parse, commit hash, produces nothing about it.

What that does not establish. It does not establish concealment. No CVE was requested or assigned, so no vendor was under an obligation to publish anything, and a changelog is a product document, not a security advisory. It does not establish that Claude Code users are exposed today: a native install updates itself in the background, and most will have passed 2.1.179 in June without noticing. And it does not establish that the flaw was serious in the way the headlines imply, which is a separate question this briefing spends most of its length on.

What it does establish is narrower and more useful. If your only way of knowing whether an AI coding agent has shipped a security fix is its release notes, you would not have known. There is no CVE record for this in the National Vulnerability Database, and nothing in CISA's Known Exploited Vulnerabilities catalogue names any of the four affected products. The whole of the public record for Plugin4Shell is one vendor-neutral research post, two news articles, and, if you go looking for it, one pull request in OpenAI's repository.

One piece of research, two headlines, and a miscount

Take the first question first, because it determines how much weight the story carries.

The Register's article of 17 September and The Hacker News article of 18 September describe the same research. Both are written from a single post published on 17 September 2026 by Or Nevo, Dor Granat and Niv Hoffman of Air Security, a startup that came out of stealth on 1 September 2026 selling controls for AI agents. There is one finding here, not two. The Register got there first, at 23:42 UTC on 17 September, and did two things the later piece did not: it obtained a statement from GitHub, and it put Air's researchers back on the record to answer it.

The ZCode item is unrelated. It is separate research by a different person, a developer publishing as ferstar, about a different product from a different company, disclosed on 18 September. It belongs in this briefing for a reason set out later, but it is not part of Plugin4Shell and nobody has claimed it is.

One detail worth correcting before it propagates. The Register's opening sentence names five products: "Anthropic's Claude Code, OpenAI's Codex, Google's Gemini CLI, Microsoft's Copilot, and Microsoft-owned GitHub Copilot". Air's own post names four, and its title says four. The Register is counting Microsoft Copilot and GitHub Copilot as separate entries; Air's remediation section lists one, GitHub Copilot, and its response to The Register about Microsoft Copilot is an argument about marketplace hosts rather than a second tested product. Four agents were tested. Treat the fifth as a reporting artefact until Air says otherwise.

What the agents actually ran

SHA pinning is the industry's answer to the plugin rug-pull. You review a plugin's code at one commit, you record that commit's 40 character hash, and from then on the agent installs that exact commit. If the author's account is later taken over, or the author turns hostile, you keep running the code you reviewed. Anthropic's marketplace documentation describes the field plainly: a plugin entry may carry a sha, and when both a branch reference and a sha are set, "the sha is the effective pin".

Air's finding is that every one of the four agents asked git for the pinned commit and then never checked what it got. That single missing check is the whole bug, and git offers two ways through it.

The first variant, in Claude Code, Codex and GitHub Copilot. These agents clone the plugin repository and then check out the pinned hash by name. Git resolves a name that could be either a reference or an object identifier by trying references first. An attacker who controls the plugin's repository can create a branch whose name is the exact 40 hex characters of the pinned commit, and make it the repository's default branch. A plain clone brings that branch down as a local branch of that name. The checkout then resolves the pinned hash to the branch, prints a warning that the name is ambiguous, and puts attacker-controlled files in the working tree. The pinned commit does not even have to still exist. The agent reports a successful install at the pinned commit.

Two conditions are needed. Git itself permits a 40 hex branch name, and so do hosts that follow the protocol, but some refuse: GitHub rejects them. And the malicious branch has to be the repository's default, because a non-default branch arrives only as a remote-tracking reference and the checkout would fall back to the commit after all.

The second variant, in Gemini CLI. Gemini clones shallowly, fetches the pinned commit explicitly, and then checks out FETCH_HEAD. The fetch retrieves the right commit. The checkout does not have to read it. Git's own revision specification sets out the resolution order for an ambiguous name, and the first rule is that a file of that name inside the git directory wins, which is "usually useful only for HEAD, FETCH_HEAD, ORIG_HEAD" and the rest. Name the repository's default branch FETCH_HEAD and the checkout takes the branch instead, silently discarding the commit that was just fetched.

Air's recommended assertion is the same one: after checkout, resolve the commit actually in the working tree and abort unless it equals the pinned hash. It has to check the resolved head rather than the reference that was requested, which is the distinction the Gemini variant slips through, and it has to run inside the agent, because the pin is resolved on the client. That last point is the one with consequences for buyers. No marketplace, however well run, can enforce a guarantee that is resolved on someone else's laptop.

Five stages, left to right. One, an attacker publishes a benign plugin pinned at commit aaa. Two, marketplace review reads that commit and records the pin: the only stage a human looks at. Three, developers install it, every install locked to aaa. Four, a version bump re-pins to a new benign commit bbb, then the attacker makes a branch named bbb, pointing at malicious code, the repo default: nobody reviews this. Five, auto-update runs, git resolves bbb to the branch, and the code executes.
Drawn from Air Security's Plugin4Shell write-up of 17 September 2026 and from OpenAI's pull request 34644. The review markers reflect where the sources say review occurs; no source describes any re-review after a version bump.

What zero-click means here, precisely

"Zero-click" is doing real work in this story and it deserves the same treatment we gave the phrase on 16 September, when Forever Security applied it to five browsers and it turned out to mean "after installation".

Here it means something similar and something narrower. The victim does not click anything at the moment of compromise. But a set of conditions has to be true first, and every one of them is a place where a control could sit.

What must already be true before Plugin4Shell reaches a machine, and what an attacker still has to do. Conditions from Air Security's write-up; the host condition is corroborated by Anthropic's marketplace documentation.

Must already be trueWho controls it
The agent has a plugin installed from a marketplaceThe organisation, or the individual developer
The plugin's repository is on a host that allows a branch named like a commit hash, which includes Bitbucket and any self-hosted git server, and excludes GitHubWhoever chose the marketplace and its plugin sources
The attacker controls the plugin's repository, by having published it or by taking it overThe plugin author, and the host's account security
The marketplace re-pins the plugin to a new commit, which a routine version bump doesThe marketplace, acting on the attacker's own update
The agent's background plugin auto-update runsA setting in the agent, per marketplace

The second row is the one that matters most for a UK enterprise, and it is not a footnote. Air points out that Anthropic's own documentation lists Bitbucket and self-hosted git as valid marketplace backends, and the documentation does say exactly that: "Any git hosting service works, such as GitLab, Bitbucket, and self-hosted servers." A team that moved its internal plugin marketplace onto its own git server, for perfectly good reasons of control, moved itself from the protected column into the exposed one without being told.

The last row is contested, and the contradiction is worth recording rather than resolving. Air states that background plugin auto-update is the default in Claude Code and Codex, and that this is what makes the attack zero-click. The Hacker News reported, in the same story, that auto-update defaults to off for third-party repositories. Both cannot be right. Anthropic's published documentation describes a background marketplace refresh that checks the remote for new commits and swaps in a new clone when it finds them, and the settings it offers around that refresh are for making it fail more gracefully rather than for turning it on. That is consistent with Air, but it is not a statement of the default, and we could not find one. Check your own configuration rather than either article.

Fixed on the record, and not stated

Four vendors, four different answers, and only two of them are a patch.

Vendor status, checked against each vendor's own records on 18 September 2026. Publication dates from the npm registry and the GitHub releases API; changelog contents read in full.

ProductFixed on the recordNot stated
Claude Code2.1.179, published 16 June 2026. Air says Anthropic confirmed the fix on 17 June.Nothing in the 2.1.179 changelog entry describes a security fix, and no advisory or CVE exists.
OpenAI Codex0.146.0, published 29 July 2026, carrying pull request 34644 merged 22 July. Air verified it fixed on 12 August.The release's headline notes do not mention it; you have to read the commit list. No advisory or CVE.
GitHub CopilotNothing. Air says Microsoft has shipped no fix and did not respond to the disclosure.Whether a fix is planned, and when. Microsoft did not respond to The Register either.
Gemini CLINothing, and nothing is coming. Air says Google confirmed on 4 August that it will not patch because the product is deprecated.Why a product Google still supports for enterprise licence holders is being treated as out of scope.

That last cell is the sharpest thing in the vendor record, and it comes from Google's own announcement rather than from the researchers.

Google's developer blog post of 19 May 2026, the one Air and The Register both link to as evidence of deprecation, sets out two different timelines. For consumers, Gemini CLI stopped serving Google AI Pro and Ultra subscribers and free Gemini Code Assist users on 18 June 2026. For enterprise customers the same post says: if your organisation uses Gemini CLI under a Gemini Code Assist Standard or Enterprise licence, "your access remains unchanged", and Google will "continue to support Gemini CLI and Gemini Code Assist with access to the latest Gemini models and other updates".

So an organisation on a paid Code Assist licence is, by Google's own commitment, running a supported product. It is also, by Air's account, running a product for which Google declined to fix a remote code execution path on 4 August, 77 days after that commitment was published. Nothing we found reconciles those two positions. If Gemini CLI is in your estate under an enterprise licence, that is the question to put to your account team in writing, and the answer belongs in your risk register either way.

GitHub's answer is different again, and it is a real mitigation rather than a dismissal. A GitHub spokesperson told The Register: "To prevent abuse of SHAs, GitHub does not allow users to create branch or tag names that resemble commit SHAs." That defeats the first variant for anything hosted on GitHub, which is most things.

Two caveats. First, it is a host control, not an agent control, so it protects GitHub-hosted plugin repositories and nothing else. Air's reply to The Register is the obvious one: marketplaces are also hosted on Bitbucket and on self-hosted servers, both of which the agents officially support. Second, we could not find this restriction documented anywhere in GitHub's public branch documentation. The statement to a journalist is currently the whole of the public record for a control several organisations will now be relying on. Ask GitHub to document it.

ZCode: the same trust surface, a different failure

On 18 September a developer publishing as ferstar posted a reverse-engineering walkthrough of ZCode, the desktop coding agent from Z.ai, the Beijing company behind the GLM open-weight models. The claim is not a vulnerability. It is a design.

By ferstar's account, whenever the app is logged in it packages the entire workspace, encrypts it and uploads it to Alibaba Cloud object storage. The evidence is a packaging manifest that ZCode stores locally in plain text. For one snapshot of 42,411 files, the manifest breaks down as 196.1 MB of git LFS cache, 102.2 MB of git objects, 0.6 MB of reflogs and 46.2 MB of source and documentation. Add those and the git directory is 86.6 per cent of a 345.1 MB payload, which is the figure ferstar reports. The state file alongside it records an encrypted archive of 313,070,842 bytes built from a workspace of 345,549,173 bytes, a ratio of 90.6 per cent, and a failure counter at 564 attempts.

A git object store is not the working tree. It is the repository's whole lineage: credentials deleted in a later commit, unpushed branch names that describe unreleased work, internal hostnames recorded in the git configuration.

The detail that turns this from telemetry into something else is the key. ferstar describes textbook envelope encryption: the archive is encrypted with a symmetric key, and that key is wrapped with an RSA public key which the server supplies during upload-credential negotiation. The matching private key never reaches the machine. ferstar tried every local private key and could not unwrap it. The archive sitting on the user's own disk cannot be opened by the user or by the client that made it.

ferstar's conclusion is one line and it is the only quote worth carrying: "A key that only the server can use serves exactly one purpose: making sure the server can read your code whenever it wants."

ferstar also reports cross-referencing the two settings toggles against the code. "Optimize Experience" governs only whether data may be used for training. "Repo Snapshot Indexing" governs only whether the server indexes what it received. Neither stops the packaging or the upload, which ferstar says are started by a host-level component instantiated unconditionally at startup, requiring only a valid session token. Deleting the pending archive produced a fresh one within half an hour with the retry counter incremented. The defence that held was making the checkpoints directory immutable at the filesystem level.

The reason this sits in the same briefing as Plugin4Shell is not that they are the same kind of finding. One is a vulnerability with a patch; the other, if accurate, is a product working as designed. They meet at the same place: the harness around the model is a piece of software with its own supply chain, its own background processes and its own network egress, and almost nobody has inventoried it. ZCode's own release notes for 3.12.3 announce that "Plugins can now be installed per workspace" and that the app "shows a notification when a plugin update is available, allowing direct updates". The plugin surface Air attacked in four agents is arriving in the fifth.

Who is selling what

Air Security came out of stealth sixteen days before publishing this. Its post says twice, once in the body and once directly above the demo booking button, that enterprises using Air Marketplace and Air Filter were not affected by Plugin4Shell. It sells an agent marketplace and a filtering product, and this research is the argument for both.

That is worth knowing and it changes nothing about the finding. The mechanism is confirmed independently by OpenAI's own pull request, written by OpenAI's engineers, describing the same git behaviour and shipping the same assertion Air recommends. The dates check against the npm registry and the GitHub releases API. This is a real bug, found properly, disclosed in June and fixed by the two vendors that chose to fix it.

But notice what the commercial framing quietly asks you to accept. Air's own analysis says the pin is resolved inside the agent, so no marketplace can enforce it. If that is true, and OpenAI's fix confirms it is, then a curated marketplace was never the control for this class of flaw either. The product that would have protected you here is an agent that checks what it checked out. Two of the four now have one.

From the trusted channel to the distribution layer

Two days ago we wrote about BragJack and Mandiant, and the conclusion there was that neither report was really about AI. Forever Security showed extensions speaking through the web pages that browser agents were built to obey. Mandiant's first case study described an attacker who had already compromised a software provider, hijacked a developer's live AI coding session, and used it to get a poisoned package installed. In both, the model was never the target. Whoever could speak through the channel the assistant trusted held the assistant's authority.

Plugin4Shell is the layer below that. Mandiant's attacker needed to be inside a session already. The Plugin4Shell attacker needs only to own a repository that a marketplace once reviewed. The channel is no longer a page or a session; it is the distribution pipe that carries code into thousands of sessions at once, on a schedule the agent sets for itself.

Our recommendation on 16 September was to treat an assistant session as a privileged session. That still holds, and it is now incomplete. A privileged session that installs its own software from a marketplace on a background timer is not just a privileged session. It is an unmanaged software distribution channel with root-equivalent reach into a developer's machine, and it is one your configuration management database almost certainly does not list.

What to do

Take this with you

In the order it is worth doing

  • Get the versions. Claude Code must be at 2.1.179 or later and Codex at 0.146.0 or later. Both fixes are months old, so a machine below them is a patching failure, not a zero-day.
  • Check how Claude Code was installed, because it decides whether you are already safe. Anthropic's documentation states that native installs update in the background, while Homebrew, WinGet, apt, dnf and apk installs do not auto-update by default. Your exposed population is the package-manager installs and any machine where auto-update was disabled by policy.
  • Find the Gemini CLI installs. Google will not patch it. If you hold a Gemini Code Assist Standard or Enterprise licence, ask Google in writing to reconcile its commitment to continue supporting Gemini CLI with its decision not to fix this, and record the answer.
  • Ask Microsoft for a GitHub Copilot position and a date. There is no patch, no advisory and, on the public record, no response to the researchers.
  • Inventory the plugins, skills, hooks and MCP servers installed in every coding agent in the estate, with the marketplace each came from and the git host behind each plugin source. If you cannot produce that list today, that is the finding, and it matters more than any single CVE.
  • Approve marketplace sources centrally rather than per developer. In Claude Code this is the strictKnownMarketplaces managed setting, enforced before any network or filesystem operation on add, install, update, refresh and auto-update.
  • Treat a plugin source on Bitbucket or a self-hosted git server as higher risk than one on GitHub until your agent version is known to verify the checked-out commit, because the host restriction that blunts this attack is GitHub's, not yours.
  • Pin by digest and then verify the digest, wherever the tooling lets you. For archive-based plugin sources Claude Code supports a sha256 field and refuses the install when the download does not match it, which is the property people believed a commit pin already had.
  • Control egress from developer machines and from agent processes, and log which outbound connections and which package installs originated in an assistant session, so an investigation can separate what a developer did from what was done through their agent.
  • Keep long-lived credentials out of the agent's reach. A plugin runs with the operator's permissions, so cloud keys, registry tokens and signing material in the environment of a developer's shell are in the blast radius of a plugin update.
  • Decide a policy on closed coding agents that phone home, and test it rather than reading the privacy policy. Capture what a candidate tool transmits when logged in, and ask who can decrypt anything it stores. ZCode's policy would not have told you.

The position

The interesting thing about Plugin4Shell is not that four engineering organisations made a mistake. It is that they made the same one, independently, and that the mistake was to trust a tool to answer the question they had asked it. Each of them wrote code that asked git for a commit. None of them wrote the next line, which asks git what it actually gave them. Git's behaviour here is documented, decades old and entirely uncontroversial: when a name could be a reference or an object, references win.

That is what a supply chain failure looks like when the supply chain is three months old. There is no registry, no signing, no attestation, no advisory format, no CVE, no severity score anyone agrees on, and no convention that a security fix gets a line in the changelog. There is an enormous amount of code moving automatically onto developer machines with the developer's own permissions, and a word, pinned, that everybody trusted to mean something it did not mean.

The question worth putting to any vendor shipping an agent that installs its own extensions is not whether they support pinning. They all do. It is this: after your agent installs a pinned plugin, what does it compare, and what does it do when the comparison fails? If they cannot answer in one sentence, the pin in your change record is a note about a name.

Key facts

Sources

  1. PrimaryThe researchers' own write-up of Plugin4Shell: mechanism, both git variants, attack chain, vendor responses and disclosure timeline. Used for every technical claim attributed to Air.Air Securityaccessed 2026-09-18
  2. PrimaryPull request 34644, Verify Git plugin SHA checkouts. OpenAI's own description of the cause and the fix, used as independent vendor confirmation of the mechanism.OpenAIaccessed 2026-09-18
  3. PrimaryRelease record for Codex 0.146.0, used for the publication date and to locate the fix in the commit list rather than the headline notes.OpenAIaccessed 2026-09-18
  4. PrimaryClaude Code changelog, used to check what version 2.1.179 says it fixed and to establish the current version.Anthropicaccessed 2026-09-18
  5. PrimaryPublication timestamps for Claude Code releases, used to date 2.1.179 and to count the releases shipped since.npm registryaccessed 2026-09-18
  6. PrimaryClaude Code plugin marketplace documentation, used for the sha pin field, the supported git hosts, and the background marketplace refresh.Anthropicaccessed 2026-09-18
  7. PrimaryThe git revision specification, used for the documented order in which an ambiguous name is resolved.Gitaccessed 2026-09-18
  8. PrimaryGoogle's announcement transitioning Gemini CLI to Antigravity CLI, used for the deprecation dates and for the statement that enterprise access and support continue.Googleaccessed 2026-09-18
  9. PrimaryThe original reverse-engineering write-up of ZCode's workspace snapshot upload, used for the manifest figures, the encryption design and the defence.ferstaraccessed 2026-09-18
  10. PrimaryZCode's privacy policy, read in full and word-counted to check whether workspace snapshots or git history are disclosed anywhere in it.Z.aiaccessed 2026-09-18
  11. PrimaryZCode release notes, used for the current version and date and to check whether snapshotting is described in any release.Z.aiaccessed 2026-09-18
  12. PrimaryNVD keyword search for Plugin4Shell, used to establish that no CVE record exists.NIST National Vulnerability Databaseaccessed 2026-09-18
  13. PrimaryThe Known Exploited Vulnerabilities catalogue, checked for any entry naming these agents or vendors.CISAaccessed 2026-09-18
  14. Reported byFirst news coverage, and the only source for GitHub's statement about SHA-shaped branch names and for Air's reply about Bitbucket-hosted marketplaces.The Registeraccessed 2026-09-18
  15. Reported bySecond-day coverage of the same research, used to record its differing claim about the auto-update default and its statement that no CVE was assigned.The Hacker Newsaccessed 2026-09-18
  16. Reported byAggregated coverage of the ZCode finding, used for the reported vendor reply and the reach figures of the original threads.Tokensteadaccessed 2026-09-18
  17. Reported byOur 16 September briefing on BragJack and Mandiant, which recommended treating an assistant session as a privileged session.P.K. Sharmaaccessed 2026-09-18

Share this briefing

Know someone who owns this problem? Send it to them.

Related briefings

The briefing, in your inbox

Practitioner analysis of cyber and AI security news. No vendor noise.

One email per briefing. Unsubscribe any time.