The Pinned Commit Was a Branch. Check Your Git Host.

Plugin4Shell broke SHA pinning in Claude Code, Codex, Copilot CLI and the Gemini CLI. Two shipped client fixes; the control covering everyone else is a Git host naming rule that GitHub and GitLab enforce and Bitbucket and self-hosted servers do not.

By Rajesh Beri·September 20, 2026·12 min read
Share:
A steel padlock on a workshop door, clicked shut through a loose metal loop beside the hasp instead of through the hasp itself, so the door still hangs open a few inches.

Illustration generated using AI

Your coding agent's plugins are pinned to 40-character commit SHAs, and those pins have been holding for a reason you did not choose. They held because GitHub refuses to create a branch whose name looks like a commit hash — not because your agent ever checked that the code it ran matched the commit you reviewed.

That is the practical shape of Plugin4Shell, the plugin supply-chain bug Air Security disclosed on 17 September. It affects Claude Code, OpenAI Codex, GitHub Copilot CLI and the Gemini CLI. Two of the four have shipped client fixes. The mitigation that actually covers everyone else is server-side, sits on a Git host, and applies only if your plugin marketplace repositories live on the right one. So the remediation question for a platform lead this week is not "which agent version are we on." It is a two-column inventory: agent version, and the Git host behind every marketplace your engineers have added.


The Pin Was Checked Out, Never Verified

The bug is a missing assertion, not a cryptographic break. All four agents resolve a pinned plugin by running a git checkout against the 40-hex SHA and then trusting whatever landed in the working tree. Air's write-up puts it plainly: the agent "check[s] out the pinned commit but never checks that it actually landed there."

That gap matters because Git is permissive about what a ref may be called: nothing in Git stops a repository from carrying a branch named a1b2c3…, which is precisely why both large hosts ban the shape. But the resolution rules do not cut the way the pin's users tend to assume. Asked for a full 40-character hash, Git returns the object and merely warns that a same-named ref "will be ignored when you just specify 40-hex" — the precedence runs toward the commit, not the branch. The ordering that gitrevisions does publish for a <refname>$GIT_DIR/<refname>, then refs/<refname>, then refs/tags/, then refs/heads/, then refs/remotes/ — is about disambiguating refs from each other, not refs from commits. So this is not a pinned hash being shadowed at resolution time. It is staged one step earlier, on the branch a clone checks out, and it survives because nothing afterwards compares HEAD to the pin.

Air describes two variants. Against Claude Code, Codex and Copilot, an attacker who controls the plugin repository creates a branch whose name is the exact pinned SHA and makes it the default branch. Against the Gemini CLI, the repository's default branch is named FETCH_HEAD, so the agent's git checkout FETCH_HEAD resolves to the branch and the correctly-fetched commit is silently discarded.

The reason this earns the "zero-click" label is the update path, not the install path. As Air told Help Net Security, "This is not only an install-time bug... so when the marketplace bumps the pinned SHA, the swap reaches already-installed plugins with no user action." Anthropic's own documentation confirms the machinery: a background refresh checks the marketplace remote for new commits, and when it finds them, "Claude Code clones the marketplace again and swaps the new clone in." Nobody types anything. As of 18 September no CVE had been assigned and no in-the-wild exploitation had been reported.


GitHub Blocks It. So Does GitLab.

The host-side control that neutralises the branch-name variant exists on both of the two largest Git hosts, and most coverage has only credited one of them. GitHub's documentation states the restriction directly: no branch or tag names "which look like Git object IDs (40 characters containing only 0-9 and A-F), to prevent confusion with actual Git object IDs." GitHub told The Register that "To prevent abuse of SHAs, GitHub does not allow users to create branch or tag names that resemble commit SHAs," and that "this mitigation ensures the reported vulnerability cannot be exploited on GitHub."

At least one write-up then grouped GitLab with Bitbucket and self-hosted platforms as hosts with no such protection. That is wrong, and it changes the remediation list. GitLab's branch documentation carries the same rule in almost the same words: "Branch names with 40 hexadecimal characters are prohibited, because they are similar to Git commit hashes." GitLab shipped that validation as a push-side check — strictly enough that users have filed bugs about being unable to delete pre-existing 40-hex branches, because the rule fires on any push touching the ref.

So the host axis sorts into three buckets rather than two. GitHub and GitLab reject the malicious branch name outright. Bitbucket, per Air, permits it. Any self-hosted Git server — Bitbucket Data Center, Gitea, Forgejo, a bare repo behind SSH — permits it unless someone wrote a pre-receive hook that rejects 40-hex refs, and almost nobody has, because until this week there was no reason to.

Two things follow that are easy to get backwards. First, the host block is not a patch and does not fix the FETCH_HEAD variant, which does not need a hash-shaped branch name at all. Second, a host block is a control you do not own: if your marketplace moves, or a team stands up an internal mirror, your protection moves with it and nobody files a ticket.


Your Official Marketplace Is Fine. Your Internal One Is the Problem.

The exposure here is concentrated almost entirely in privately-hosted marketplaces, which is precisely the thing a large engineering organisation builds. The Hacker News checked the public catalogs and found that every plugin in Anthropic's community catalog and in the default catalogs for Claude Code and Copilot points at a GitHub repository — which means the host block covers them and practical exposure for anyone living on official sources is small.

That is the strongest version of the vendors' position, and it is basically right for individual developers. It is close to useless for an enterprise, because the whole point of an internal marketplace is that it is not on the public catalog. And both vendors explicitly support putting it somewhere else. Anthropic's marketplace documentation says "Any git hosting service works, such as GitLab, Bitbucket, and self-hosted servers," with the example /plugin marketplace add https://gitlab.com/company/plugins.git. GitHub's own Copilot CLI docs say marketplaces "can be located on GitHub.com, in any other online Git hosting service, or on your local or shared file system", and the worked example in the install guide is copilot plugin marketplace add https://gitlab.com/OWNER/REPO.git.

This is a supported, documented, recommended-for-privacy configuration. It is not an edge case somebody abused. If your security team approved coding-agent plugins on the argument that everything is pinned to a reviewed SHA — and that argument was made in a lot of review boards this year — the pin was doing less work than the slide claimed, and the compensating control turned out to be a property of whichever host the repo happened to sit on.

The same inversion applies to the threat model. Plugin4Shell requires the plugin repository owner, or someone who has taken over that repository, to stage the branch. For a public marketplace that is a hostile third party. For an internal marketplace it is anyone with push access to a repo that probably has looser branch protection than your production services, because it "only holds prompts and scripts." We made that argument about plugin commit contents three weeks ago; this is the same repository, failing a different way.


Google Will Keep Supporting Gemini CLI. It Will Not Fix This.

The sharpest enterprise detail in this disclosure is that Google's refusal to patch and Google's promise of continued support are both true at the same time, and they apply to overlapping populations. Air reports that Google confirmed on 4 August it would ship no fix, because the Gemini CLI is deprecated and users should migrate to Antigravity.

But read Google's own transition notice. The 18 June 2026 shutoff applies to Google AI Pro and Ultra subscribers and free Gemini Code Assist for individuals. For organisations, it says something else entirely: "If your organization uses Gemini CLI or our IDE extensions via a Gemini Code Assist Standard or Enterprise license... your access remains unchanged. We'll continue to support Gemini CLI and Gemini Code Assist with access to the latest Gemini models and other updates."

So the individual hobbyist was cut off in June. The enterprise with a Code Assist Standard or Enterprise licence is still running a product Google describes as supported, receiving model updates, carrying a zero-click RCE its vendor has said it will not fix. That is not a contradiction Google has acknowledged anywhere, and the notice does not address security maintenance either way. If you hold that licence, treat "supported" as a billing statement, not a patching commitment, and ask your account team in writing whether Plugin4Shell specifically will be fixed for licensed customers.

Migration is the obvious answer and it is not free. Antigravity's admin URL allowlist was not honoured by its browser subagent as recently as last month, so moving off an unpatched CLI onto a replacement whose own controls have slipped is a trade, not an upgrade. Make it deliberately.

GitHub Copilot sits in a different but not better position: no client fix as of disclosure, and a documented ability to add marketplaces from hosts where the branch trick works.


The Inventory Has Two Columns

Agent version alone does not tell you whether you are exposed; you need the cross-product of agent version and marketplace host. Build the table before you build the remediation plan, because the answer differs per row.

Patched clients, per Air's disclosure timeline: Claude Code 2.1.179, which Anthropic confirmed on 17 June; and OpenAI Codex 0.146.0, released 29 July with the release-note line "Verify Git plugin SHA checkouts" and verified fixed by Air on 12 August. Unpatched: Copilot CLI and Gemini CLI.

On the host column, the controls are better than most teams realise and nobody has turned them on, because until this week they looked like inventory hygiene rather than a security boundary.

Claude Code ships a managed-settings allowlist, strictKnownMarketplaces, and its enforcement point is exactly right for this bug: restrictions "are checked before any network or filesystem operation," and "the check runs on marketplace add and on plugin install, update, refresh, and auto-update." An empty array blocks everything including Anthropic's official marketplace. A hostPattern entry constrains marketplaces to a host you name by regex. That is the lever: allowlist the hosts that reject SHA-like refs, and GitHub's server-side block becomes a control you are deliberately relying on instead of one you are accidentally benefiting from. blockedMarketplaces handles the inverse case, and supports the owner/* wildcard from v2.1.223.

Copilot CLI's sha field is documented as "must be a full 40-character commit SHA" and is recommended to "pin to a sha for reproducible installs that are immune to force-pushes or tag/branch moves" — the guarantee Plugin4Shell voids. Since there is no client fix, the available control is the update path: autoUpdate in settings, or COPILOT_AUTO_UPDATE=false, which turns a silent swap into a command someone has to run — though GitHub documents that auto-update as covering first-party plugins from the built-in marketplaces.


Three Checks Before Your Next Agent Session

This Week:

  1. Enumerate marketplaces, not agents. Collect extraKnownMarketplaces from every managed, project and user settings file, plus the output of copilot plugin marketplace list. Sort the results by Git host. Anything not on github.com or gitlab.com is your exposed set, and it will be shorter than you fear and more interesting than you expect.
  2. Verify one pin by hand. In an installed plugin's checkout, run git rev-parse HEAD and compare it to the SHA declared in the marketplace manifest. This is the assertion the agents skipped; doing it once tells you whether the pin was ever real in your environment.
  3. Upgrade the two that have fixes. Claude Code to 2.1.179 or later, Codex to 0.146.0 or later. Do this even if every marketplace you own is on GitHub — the host block is not yours to keep.

This Month:

  1. Set strictKnownMarketplaces in managed settings, scoped to the hosts and repositories you have actually reviewed. Then re-run check 1 and confirm a non-allowlisted marketplace is refused on update, not just on add.
  2. Turn off unattended plugin updates where no client fix exists. COPILOT_AUTO_UPDATE=false for Copilot CLI. For the Gemini CLI, plan the Antigravity migration with its own control gaps written into the plan, not discovered after.
  3. Apply branch protection to plugin repositories at the tier you use for production code. A pre-receive hook rejecting 40-hex ref names is four lines and is the only thing standing between a self-hosted marketplace and this attack.

Before Renewal:

  1. Get the Gemini Code Assist answer in writing. If Google is billing you for a supported product, ask whether the support includes security fixes for disclosed RCEs, and put the response in the file you take to renewal.

The Bottom Line

Every supply-chain control the industry has adopted in the last decade — lockfiles, pinned digests, signed artifacts — works because something verifies the thing you got is the thing you named. Plugin4Shell is what happens when a system performs the ritual and skips the verification: the manifest said SHA, the UI said SHA, the checkout said SHA, and the bytes on disk were whatever the repository owner put on a branch. We have watched this failure mode arrive by hook, by docs build, by unpinned MCP server and by a hub that simply shut down. The agent layer keeps reinventing package management without reinventing its verification.

The uncomfortable part is not that four vendors made the same mistake. It is that the thing protecting most of us was a GitHub naming rule written to stop refs being confused with object IDs, long before anyone pinned an agent plugin to a SHA — and nobody in your review board knew they were depending on it.

A pin you did not verify is a preference.

Continue Reading

Share:

Frequently Asked Questions

What is Plugin4Shell?

Plugin4Shell is a plugin supply-chain vulnerability disclosed by Air Security on 17 September 2026 affecting Claude Code, OpenAI Codex, GitHub Copilot CLI and the Gemini CLI. The agents check out a plugin's pinned 40-character commit SHA but never verify the commit they landed on, so a repository owner who creates a branch named after that SHA can serve different code under an intact-looking pin.

Which AI coding agents are patched against Plugin4Shell?

Anthropic fixed Claude Code in 2.1.179, confirmed to Air on 17 June 2026. OpenAI fixed Codex in 0.146.0, released 29 July 2026 with the release-note entry 'Verify Git plugin SHA checkouts' and verified by Air on 12 August. GitHub Copilot CLI had no client fix at disclosure, and Google declined to patch the Gemini CLI because it is deprecated.

Does GitLab block SHA-like branch names the way GitHub does?

Yes. GitLab's branch documentation states that 'branch names with 40 hexadecimal characters are prohibited, because they are similar to Git commit hashes,' enforced as a push-side check. At least one write-up on Plugin4Shell listed GitLab as unprotected alongside Bitbucket; that is incorrect and it shortens the list of marketplaces you need to remediate.

Is my Claude Code or Copilot plugin marketplace affected if it is hosted on GitHub?

The branch-name variant cannot be staged on GitHub, because GitHub rejects branch and tag names made of 40 hex characters. The exposure sits with marketplaces on Bitbucket or a self-hosted Git server, which both vendors document as supported hosting options. Upgrade the patched clients anyway — the host block is a control you do not own and it moves if the repository moves.

How do I restrict which plugin marketplaces my engineers can add?

Claude Code supports a managed-settings allowlist, strictKnownMarketplaces, which is checked before any network or filesystem operation and runs on marketplace add and on plugin install, update, refresh and auto-update. A hostPattern entry constrains marketplaces to hosts you name by regex. For Copilot CLI, set autoUpdate in settings or COPILOT_AUTO_UPDATE=false to stop unattended plugin swaps.

Newsletter

Stay Ahead of the Curve

Weekly enterprise AI insights for technology leaders. No spam, no vendor pitches—unsubscribe anytime.

Subscribe

Latest Articles

View All →