Devlery
Blog/Anthropic

Plugin4Shell: Four Coding Agents Pin a Commit SHA and Never Check They Got It

Disclosed September 17, Plugin4Shell shows Claude Code, Codex, GitHub Copilot, and Gemini CLI never verify they landed on the commit they pinned. Claude Code patched in June, Codex in August, Copilot still has no fix.

Plugin4Shell: Four Coding Agents Pin a Commit SHA and Never Check They Got It
AI 요약
  • A pinned, reviewed commit can be swapped for different code at install time.
  • Claude Code 2.1.179 and Codex 0.146.0 are patched; Copilot is not.
  • GitHub blocks one variant of the attack, but not the FETCH_HEAD one.

Security firm AIR Security disclosed a flaw shared by four coding agents on September 17, 2026, under the name Plugin4Shell. The four are Claude Code, OpenAI Codex, GitHub Copilot, and Gemini CLI.

What breaks is SHA pinning. A plugin marketplace records one reviewed commit per plugin by its commit hash. You read the code once, freeze that state, and get a promise that the same code keeps running. Even if the repository is taken over later, the pinned commit is unchanged, so the install stays safe. That was the premise.

The four agents do ask for that commit. They just never check whether they arrived at it. Plugins run with the same privileges as the person using the agent, so substituted code reaches that person's files, stored credentials, and every system they can log into. The August incident where permissions granted to a Cursor agent became the intrusion path had the same blast radius.

The pin was set; nobody verified it

The attack uses one of git's name-resolution rules. When the same string is valid as both a branch name and a commit ID, git picks the branch. It prints a single warning (refname is ambiguous) and continues.

Claude Code, Codex, and GitHub Copilot clone the plugin repository and then check out the pinned SHA.

git clone <plugin repo> ./
git checkout aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

If whoever controls the repository creates a branch whose name is character-for-character the same 40-hex string as the pinned SHA and sets it as the default branch, that git clone brings the branch along. The checkout on the next line resolves to the branch, not the commit. Whether the original commit still exists in the repository makes no difference.

Plugin4Shell attack flow: the checkout resolves to a same-named default branch instead of the commit hash, and malicious code executes

Gemini CLI takes a different path. It fetches the commit and then checks out FETCH_HEAD.

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

The git fetch pulls the correct commit and writes it to .git/FETCH_HEAD. But git checkout FETCH_HEAD does not necessarily read that file. If the default branch is named FETCH_HEAD, the branch wins and the commit that was just fetched is silently discarded.

One line closes both variants.

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

The thing to inspect is HEAD after the checkout completes, not the ref that was requested. That distinction is exactly where the Gemini CLI variant slips through. The check also has to live inside the agent. The client is what resolves the pin, so no amount of marketplace strictness can guarantee it on the client's behalf.

Plugins you already installed change underneath you

The reason no click is needed is auto-update. Claude Code and Codex enable marketplace auto-update by default, checking the remote repository for new commits in the background on every session. When the marketplace moves a pin to a new commit, the same checkout runs again and a plugin that was already installed is replaced with no user action. No install step, no confirmation dialog.

The researchers published a five-step chain.

1

Plant

Publish a genuinely clean plugin to the marketplace, pinned to commit aaa...aaa. It passes review.

2

Spread

Users install it. Every install is pinned to the reviewed aaa...aaa.

3

Bump

Open a PR moving the pin to bbb...bbb, still clean code, and get it approved.

4

Swap

Create a branch named bbb...bbb, set it as the default branch, and point it at malicious code. The pinned commit itself never has to be touched.

5

Execute via auto-update

The pin changed, so every agent's background update runs. The checkout resolves to the branch and the malicious code executes.

The researchers noted that the organizations doing review properly were leaning on this defense the hardest. Some teams refuse to use a community marketplace as-is: they read the plugin themselves and pin it to the commit they reviewed. That process passes review and records the pin, and the code that gets installed is still different code. Every verification step built on top of the pin inherits the same flaw.

This is AIR Security's third piece of research on the same theme. The previous two covered a malicious skill published to a marketplace that took over more than 26,000 agents, and the hijacking of repositories behind 925 skills already in use, affecting 134,000 agents. SHA pinning was the industry's answer to exactly that class of attack.

Whether you are exposed depends on where your marketplace lives

The dividing line is whether the host allows 40-character hex branch names. Git itself does. git check-ref-format accepts a 40-hex name, and hosts that follow the protocol faithfully accept it too.

  • GitHub rejects them. It blocks creation of branch and tag names that look like commit SHAs outright.
  • Bitbucket and self-hosted git servers allow them. Anthropic's own documentation lists GitLab, Bitbucket, and self-hosted git as officially supported marketplace backends.

A GitHub spokesperson pushed back to The Register on that basis, saying GitHub does not allow branch or tag names that look like commit SHAs and that the reported vulnerability cannot be exploited on GitHub because of that mitigation. The researchers answered that marketplaces can also live on other platforms such as Bitbucket, and Copilot supports those marketplaces, so the exposure remains.

Restricting hosts to GitHub still leaves the Gemini CLI variant. The name that variant abuses is FETCH_HEAD, not a 40-character hash.

Vendor responses diverged.

ProductStatusFixed version and date
Claude CodePatched

2.1.179, June 17, 2026

OpenAI CodexPatched

0.146.0, August 12, 2026

GitHub CopilotUnpatchedNo fix as of September 23
Gemini CLINo fix planned

End-of-support notice August 4, migration to Antigravity advised

The researchers built working proof-of-concept code against all four products in May 2026 and reported it through coordinated disclosure in June. Three months passed between notification and publication. As of September 23, 2026, there is no CVE identifier, no official CVSS score, and no report of exploitation in the wild.

What to check now

This section applies to developers who install marketplace plugins into a coding agent. If you have no plugins installed, this flaw leaves you nothing to do. The patches ship in each tool's ordinary updates, so no separate plan or request is involved, and the version updates and configuration changes below carry no regional restrictions anywhere, including APAC. AIR Security's disclosure names no region-specific exposure either: what determines your risk is your client version and where your marketplace is hosted, not where you are.

Tool you useRequirementWhere you stand
Claude Code2.1.179 or laterAlready covered since June if auto-update is on
OpenAI Codex0.146.0 or laterAlready covered since August
GitHub CopilotNone availableNo patch, so marketplace sources need manual control
Gemini CLINone availableNo fix planned; migrating to Antigravity is the only resolution

Check the installed version with each tool's version command.

claude --version
codex --version

In Claude Code, /plugin marketplace list shows the registered marketplace sources, and /plugin marketplace auto-update off turns auto-update off. Per the documentation, auto-update is on by default, and for git-sourced plugins without a version field the resolved commit SHA is the version.

If you only run patched clients, one version check is the whole job. If your team also uses Copilot, open the list of registered marketplaces and start by looking for anything that did not come from GitHub. For a marketplace hosted on Bitbucket or an internal git server, turning off auto-update for that marketplace is the only mitigation available until a patch ships.