Skip to content

Build1 publisher2 min readPublished

Four AI coding agents check out a pinned commit without confirming where the tree landed

In AIR's Plugin4Shell research, a branch named after the hash you pinned wins the checkout in Claude Code, Codex, Copilot and Gemini CLI. The check that would catch it is one line of git, run on your disk.

The Engineer · Build desk

Illustration accompanying Four AI coding agents check out a pinned commit without confirming where the tree landed

What happened

  • Researchers at AIR, in work written up on dev.to, found four AI coding agents check out a pinned plugin commit and never confirm the working tree landed on it; they call the flaw Plugin4Shell.
  • In Claude Code, Codex and GitHub Copilot, the clone-then-checkout sequence lets an attacker-controlled default branch named with the pinned 40-character hash serve its tip instead of the commit.
  • Gemini CLI fetches the pinned commit and then checks out the name FETCH_HEAD, so a default branch literally called FETCH_HEAD answers to the checkout in the same way.
  • Claude Code and Codex run plugin auto-update in the background by default, so the substituted checkout executes with the agent's permissions while the user does nothing.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Registry-side scanning cannot close this gap, so a team's actual protection is whatever its agent binary does after the clone, not whatever the marketplace promises about published code.
  • exposure The reachable population is anyone pinning plugins to repos controlled by someone else; pinning inside your own org forces the attacker to obtain push access first.
  • decision Gemini CLI has no patch coming, so users choose between migrating off the tool and accepting the exposure, per the writeup.
  • precedent Internal CI that checks out a pinned SHA from an external repo now has to justify omitting the same comparison, because the failure was always a git failure rather than an AI agent one.

Both vectors work without a git bug, because nothing compares what ended up in the working tree with what the pin asked for [1]. AIR's remediation is one test run after the checkout, `test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort` [9].

That line has to live in the agent. A marketplace or registry control sees what was published in the repo, not what the local git client resolved onto disk, so the comparison only means anything where the checkout happened [10].

The chain AIR describes assumes patience. A clean, useful plugin is published and reviewed at commit `aaa...`, then bumped to `bbb...`, which is also clean, and only then does the attacker create a branch literally named `bbb...` whose default tip carries the payload [5]. Earlier AIR work counted 925 compromised "skills" reaching 134,000 agents through similar takeover patterns [7]. The researchers call Plugin4Shell "the first supply chain vulnerability of the AI agent ecosystem" [14]. The dev.to writeup sums up the victim's contribution to the attack as "You were probably in a meeting" [15].

The exposure figure the post leans on is nearly 90% of Fortune 500 companies using Copilot [11]. It transfers only where someone other than you can create a branch in the plugin repo and make it the default [3]. A plugin pinned to a repo your own org owns requires push access to that repo instead. Pin to someone else's marketplace repo and whoever owns it already has that access.

Of the four agents, the writeup names fix versions for two, Claude Code 2.1.179 and Codex 0.146.0 [12][1]. Per the post, Google did not ship a patched Gemini CLI; it deprecated the product and told users to migrate [8]. For Copilot and Gemini CLI the advice is to assume exposure and disable plugin auto-update [12]. All of this comes from a single dev.to writeup of AIR's research. Vendor advisories are absent.

The writeup also calls this a general git footgun, one that reaches past AI agents [13]. Any pinned checkout from a repo outside your control has the same missing comparison. In my view the assertion belongs in internal tooling too, at a cost of one `git rev-parse` per install [9].

What to watch

  • An AIR advisory or a GitHub security note would settle whether Copilot's plugin install path is still unpatched, which the dev.to writeup asserts without a vendor statement.
  • Whether Copilot's install path adds a post-checkout `git rev-parse HEAD` comparison, and in which release.
  • Whether git itself warns or refuses when a local ref name is a full 40-character hex string.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories