AI coding agents now work where developers work. They read repositories, edit files, run commands, and pick up new abilities through plugins.
In September, Air Security disclosed Plugin4Shell, a zero click remote code execution bug in how Claude Code, OpenAI Codex, GitHub Copilot and Gemini CLI install plugins. The weak point was the supply chain around the agents.
A pinned commit that wasn’t
If a plugin points at a branch like main, whatever the maintainer pushes next goes straight onto your developers’ machines. That could be an honest mistake, or code from someone who took over the maintainer’s account. Either way, it never gets a review. Tags help a little, but a tag can be moved to different code. A commit hash is computed from the code itself, so the same hash should always mean the same code.
That’s why security teams review a plugin at a specific commit and pin it there. Every engineer installs exactly what was approved, and nothing changes until someone reviews a new commit and moves the pin. Plugin marketplaces recommend this practice, and it rests on one assumption: a pinned commit can’t change.
Plugin4Shell broke that assumption without ever touching the pinned commit. The agents checked out the pinned hash but never confirmed which commit actually landed. Recording an identifier is not the same as verifying what it resolved to.
Attack path
The attacker gets control of a plugin’s source repository. They can publish a useful plugin and wait for it to be approved. Or they can take over a repository an organization already trusts, for example by reregistering an account name its owner deleted.
Teams review the plugin and pin it to a clean commit. There is nothing malicious in it yet, so review passes.
The attacker ships a routine update. A maintainer reviews the clean diff and moves the pin to the new commit.
The attacker creates a branch whose name is exactly that commit hash, points it at malicious code, and makes it the repository’s default branch. The real commit stays clean, so anyone who inspects it finds nothing wrong.
Agents with the plugin installed auto-update, which is on by default in Claude Code and Codex. Each agent checks out the pinned hash, and Git resolves the branch name before the commit ID. Git prints a warning that the name is ambiguous, and the agent carries on.
The attacker’s code runs inside the agent with the developer’s access. Nobody clicked anything.
Gemini CLI fell to a variant of the same idea that used a branch named FETCH_HEAD.
Exploitability
Once an attacker controls the repository, the rest is easy. There is no exploit against the agent itself and no user interaction after the first install. The hard part is the first step: getting a plugin trusted, or taking over one that already is.
The attack works only when all of these hold:
The attacker can push branches to the plugin’s repository.
The repository’s host accepts a 40-character hash as a branch name. GitHub rejects such names. Bitbucket and many self-hosted Git servers accept them.
The victim runs an unpatched agent.
Plugin auto update is on. Without it, the malicious code still runs, but only at the next install or manual update.
That makes internal plugin marketplaces on self hosted Git the most exposed setup. They pair plugins the organization already trusts with a host that allows the branch names.
Plugins inherit the agent’s reach
A plugin runs inside the agent, on a developer’s machine. It has access to source code, SSH keys, cloud credentials and Git tokens that can push to production repos. How far an attacker gets depends on where the agent runs. A laptop with internal repos is one level of exposure. An agent in a CI pipeline holding deployment credentials is a much larger one.
So I read Plugin4Shell as a provenance problem inside a privileged execution environment.
How this could have been prevented
The fix the vendors shipped is one check. After checkout, the agent reads the commit the working tree is on and stops if it doesn’t match the pin. Any of these design choices would also have blocked the attack or made it much harder:
Check out the commit object explicitly. git checkout –detach <hash>^{commit} tells Git to treat the string as a commit, so a branch with the same name can’t win.
Treat Git’s ambiguous-ref warning as a hard error instead of printing it and moving on.
Reject hash-shaped and reserved branch names on the Git host, as GitHub does.
Require a valid signature from a known maintainer key on the commit that actually got checked out. This won’t help if the attacker holds that key. It does stop an attacker who controls only the repository.
Ask the user before an auto-update runs new plugin code, at least when the pin changes.
Each of these makes the installer check the code it got instead of trusting the name it was given.
The AI supply chain is bigger than the SBOM
Supply chain programs track packages, libraries, containers and build artifacts. Agents add plugins, skills, MCP servers and other components that load at runtime. Many of these never appear in a software bill of materials, yet they decide what an agent can run and what it can reach.
After a compromise like this one, responders need fast answers. Where did the plugin come from? Which commit was requested, and which one installed? Which agents loaded it, with what permissions, and what could they reach from there?
What an AIBOM adds
An AI Bill of Materials (AIBOM) can’t tell you a plugin is safe. It records what exists in an AI system and how the parts connect. For Plugin4Shell, that chain looks like this:
agent → plugin → repository → requested ref → resolved commit → execution environment → reachable systems
Without that record, incident responders rebuild the chain by hand after the fact. With it, they can query it.
Here’s the test I’d put to any security team. If a plugin repository were compromised today, could you list every machine that has it installed, and at which commit, within an hour?
Inventory is not verification
An AIBOM would not have stopped Plugin4Shell. A record saying a plugin was pinned protects nothing if the installer resolved that pin to different code.
Provenance has to capture what was loaded as well as what was requested. That means resolved commit hashes, install records, signatures and runtime observations, not just configuration files. The AIBOM gives you visibility. Artifact verification, signing, sandboxing and least privilege do the protecting, and you need both.
What to do now
Update Claude Code to 2.1.179 or later and Codex to 0.146.0 or later.
Plan to move off Gemini CLI, which is deprecated and won’t be fixed.
Copilot had no fix when the research came out, so check its status before relying on it.
In your own install tooling, confirm every pinned checkout with git rev-parse HEAD, and stop if the result doesn’t match the pin.
Alert on branches named like a 40-character hash, or FETCH_HEAD, in any plugin repository you depend on.
Alert on changes to those repositories’ default branches.
Turn off plugin auto-update for engineers who can reach your most sensitive systems.
Record resolved commits in your AIBOM next to the requested ones.




