It was published this week and it is called Plugin4Shell. It affected the four AI coding agents the researchers describe as the most widely used — Claude Code, Codex, GitHub Copilot and Gemini CLI — and the flaw sits in two git commands, not in the model or in the permissions the tool asks for at startup. The agent clones the plugin repository, checks out the exact commit the catalogue had pinned, and never verifies that it landed there. We reproduced this afternoon, in our lab and with git 2.43, the behaviour it rests on: the command finishes with exit code 0, prints a warning no automation reads, and leaves the attacker's content in the working tree.
Skimmed, it looks like a developer-tooling problem, the kind that stays inside the product team. What takes it out of there is the list of what a plugin reaches when it runs: the source code on the machine, the laptop's SSH keys, the cloud credentials sitting in the environment, the internal repositories and whatever those credentials open in production. A plugin runs with the permissions of whoever runs the agent. In most software companies we know, those permissions are considerably broader than anything anyone has written down.
What was actually published
The finding comes from Air Security, which says it built a working test in May 2026, reported it to the four vendors in June and went public this week. They summarise the mechanism like this: "the agent checks out the exact commit the marketplace pinned but never verifies it landed there, so an attacker who controls the plugin's repo makes the checkout resolve to malicious code while the pin still looks honored". And on what is required of the victim: "No user interaction of any kind is required. The result is full compromise of the agent and the host it runs on."
OpenAI's explanation, as relayed by the technical press, cuts straight to it: git "can interpret a requested commit SHA as a branch name", and that is why a plugin source can "materialize a different commit than the one it pinned". That is the whole thing. Pinning the commit is the right practice — it is what all of us do so that a dependency cannot shift under our feet — and it still is. What fails is the next step, the one nobody takes: confirming that what is in front of you is what you asked for.
We built it in the lab
Before writing anything we wanted to see the behaviour with our own eyes, because a headline like this gets repeated badly. What we reproduce is the git behaviour the attack rests on, not the agents' code. One repository, two commits and a branch name chosen with bad intentions.
# commit legítimo: 5c61cf42... commit del atacante: 536fe217...
git branch 5c61cf4299f1e060978d6676edc410ebe8cbbfd1 536fe217a392034ee6c1e42ac2621841469b5d45
git checkout 5c61cf4299f1e060978d6676edc410ebe8cbbfd1
The branch is named exactly like the commit the catalogue had pinned, and it points at the attacker's commit. This is what git answers:
warning: refname '5c61cf4299f1e060978d6676edc410ebe8cbbfd1' is ambiguous.
Git normally never creates a ref that ends with 40 hex characters
because it will be ignored when you just specify 40-hex. [...]
Switched to branch '5c61cf4299f1e060978d6676edc410ebe8cbbfd1'
The file in the working tree contains what the attacker put there. The warning is eloquent, and it also contradicts what just happened: it says such a ref "will be ignored", and yet git switched to the branch. It goes to the error channel, it breaks nothing and it returns zero. Any automation that checks the exit code — which is what automation checks — carries happily on.
One caveat we are not skipping, because this is where the story gets told badly: in our lab, when the forty-hex branch existed only as a remote branch of the clone (refs/remotes/origin/…) and the legitimate commit was in the repository, git checkout landed on the good commit. The trick needs the ref to exist locally — because it is the clone's default branch, as in the next test — or the pinned commit to be nowhere at all. So the test above shows the ambiguity; the one that follows is the one that describes the attack.
The second test is even less spectacular and more uncomfortable. We built a repository in which the pinned commit does not exist, with the trap branch as the default branch. On cloning, the clone lands on that branch by itself; and the checkout of the pinned commit does not fail: it answers Already on 'aaaa…' and exits with zero. The agent asked for a commit that is nowhere to be found and kept the attacker's code, without a single red line along the way.
And the detail that strikes us as most dangerous for anyone auditing this after the fact: in the first scenario, with the trap branch created and the good commit present, git rev-parse 5c61cf42… returns the legitimate commit and git log -1 --format=%s 5c61cf42… prints "commit legitimo". The same 40-character string gives a different answer depending on which git command reads it. Anyone verifying with git log will see exactly what they expect to see while the working tree holds something else.
The check the researchers propose does settle it, because it asks about HEAD rather than about the string:
test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort
We ran it in both tests and it aborts in both, because it returns the attacker.s commit instead of the pinned one. It is needed in more places than the agent: any in-house continuous integration pipeline that clones a repository and checks out a pinned commit is missing the same piece. AI agents did not invent this; they have put it to work millions of times a day.
Where it cannot be done, and where it can
Here the matter stops being a git anecdote and becomes an infrastructure decision. The trick requires someone to be able to create a branch whose name is forty hexadecimal characters, and the two big hosting platforms do not allow it. GitHub told The Register: "GitHub does not allow users to create branch or tag names that resemble commit SHAs", adding that "this mitigation ensures the reported vulnerability cannot be exploited on GitHub". GitLab has it written in its branch documentation, in one dry line: "Branch names with 40 hexadecimal characters are prohibited, because they are similar to Git commit hashes."
The research flags as exposed other hosts that do accept such names, and names Bitbucket and companies' own git servers. Today, for this specific flaw, whoever is protected is protected by where the repository lives, not by how well configured their agent is. And the git server you set up three years ago, the one nobody looks at because "it is only an internal remote", does not ship with that check.
That is fixed in ten minutes and without buying anything. A pre-receive hook on the server that rejects the push of any branch matching that pattern. We tested it in the lab against a bare repository: the push is rejected and the message reaches the client.
remote: rechazado: nombre de rama con 40 caracteres hexadecimales
! [remote rejected] aaaaaaaa… -> aaaaaaaa… (pre-receive hook declined)
One flaw, four answers
All four vendors were notified in June. This is what stands today, according to the researchers' publication and the responses gathered by the technical press:
- Anthropic (Claude Code): fixed in version 2.1.179.
- OpenAI (Codex): fixed in version 0.146.0, with a public explanation of why.
- Microsoft (GitHub Copilot): no patch. The researchers say they reported it in June and got no reply, and put the silence down to the volume of disclosures being received. The exposure comes from accepting catalogues hosted on platforms that do allow those branch names.
- Google (Gemini CLI): will not be fixed. The tool is retired — Google stopped serving the consumer version in June — and the recommendation is to move to Antigravity, which the researchers say this attack cannot reach.
One figure we did check ourselves, because it is public and takes a second to look up: in the npm registry, Claude Code version 2.1.179 was published on 16 June 2026 and Codex 0.146.0 on 29 July. Today they are on 2.1.278 and 0.155.1, published yesterday and the day before. Anyone who updates their tools as a matter of routine has had this fixed for months and only learned this week why it mattered; anyone pinned to a first-half version has not. Checking which version each workstation runs takes less time than reading this paragraph.
That list interests us more than the flaw. The flaw is the same for all four, and it is an honest oversight: the kind of thing any of us would have written the same way. What separates them is what happened after the email arrived, and that is something you buy on. When a tool enters your team's workflow, so does the response history of whoever maintains it. "No patch three months later" and "we are not fixing it, switch products" are legitimate vendor answers; the question is whether you would have accepted them had you known before adopting it.
The development laptop is part of the control plane
Plugin auto-update is on by default in Claude Code and Codex, according to the research. It is a reasonable decision — that is how fixes get distributed — and at the same time it is the path by which a compromised version reaches every workstation without anyone approving anything. A silent rollout, the kind you only see in the log once you already have something to explain.
We have been writing around this same idea from different angles for months. When we discussed whose name ends up in the log when an agent uses a borrowed credential, the subject was identity. When we described how locking the SSH agent switched off its own restrictions, the subject was the tool that holds the keys. Here it is the channel the code arrives through. All three times, the same object sits in the middle: a work machine that can talk to production and appears in no inventory of critical systems.
What we would do this week
- Update what has a fix: Claude Code to 2.1.179 or later, Codex to 0.146.0 or later. It is the only item on this list that takes a minute.
- Know which agents are in use and which catalogue they load plugins from. In many companies the honest answer is "whatever each person installed"; then the list does not exist and it has to be made before anything else.
- Check where those repositories live. If they are on GitHub or GitLab, the door is shut by the host. If they are anywhere else or on your own server, the pre-receive hook above is this afternoon's job.
- Add the one-line check to any in-house automation that clones and checks out a pinned commit. Starting with continuous integration, which usually does it with more privileges than the laptop.
- Ask what the laptop's credentials open. If the answer includes production with a key that never expires, today's problem is an old one and Plugin4Shell has only put a date on it.
- Decide who approves an automatic plugin update on machines that touch production. It can be "nobody, let it update itself"; what does not work is never having decided.
And a recommendation that comes not from this news but from the trade: the repository where your code lives also needs a backup of its own, because cloning is not having a backup. An attacker with write access to a repository can rewrite history; if your only copy is the remote itself, you have lost your point of comparison exactly when you need it.
Do you know today what a plugin on your team's laptop can reach?
We approach cybersecurity in the same order as this post: first the inventory of what can touch production, then the permissions on each piece, and only at the end the tool. And when what needs governing is AI automation, we build the off switch and the log before the automation itself. We do not resell licences for any of the products named here, so if the conclusion is that your setup is fine and what is missing is a pre-receive hook, that is the answer you will get.
Talk to everyWANWhat we are not claiming
We have not attacked any agent. Our lab reproduces the git behaviour the attack rests on — which is what we could verify ourselves and what struck us as easiest to repeat badly — not the code of Claude Code, Codex, Copilot or Gemini CLI; that each of them runs exactly that sequence is what the research says, not what our test shows. The fixed version numbers (2.1.179 and 0.146.0) and each vendor's status come from AIR Security's publication and the responses gathered by the technical press; we have checked in the npm registry that both versions exist and when they were published, but not that the fix is inside them: neither product's changelog attributes it, so that part rests on the researchers. We have not verified with a test of our own whether Bitbucket accepts forty-hex branch names today, so we give it as what it is: what the researchers state. GitHub's claim about its own platform is GitHub's, and we have not tested that either. As far as we could check today there is no assigned CVE identifier, so anyone searching by CVE will find nothing; that does not mean there will not be one. And we have no public evidence of exploitation in the wild: what exists is the researchers' proof of concept and a three-month window between the report and publication.
Note on sources
All consulted on 19 September 2026. One, the research: AIR Security's publication on Plugin4Shell, source of the mechanism (clone and check out the pinned commit without verifying where it landed), the sentence "No user interaction of any kind is required. The result is full compromise of the agent and the host it runs on", the timeline (working test in May 2026, vendor notification in June, publication this week), the list of exposed hosts, plugin auto-update being on by default in Claude Code and Codex, and the one-line check using git rev-parse HEAD. Two, the technical coverage: the pieces by Help Net Security (18 September) and The Hacker News, from which we take the formulation "check out the pinned commit without verifying the checkout landed there, letting an attacker swap in malicious code while the pin still looks intact", OpenAI's explanation ("can interpret a requested commit SHA as a branch name", "materialize a different commit than the one it pinned"), the fixed versions 2.1.179 and 0.146.0, the absence of a Copilot patch with the caveat that the researchers received no reply, the retirement of Gemini CLI. Three, The Register (17 September), source of GitHub's statement on branch names resembling SHAs and of the researchers' explanation of why Microsoft never replied. Four, GitLab's documentation: the branches page of the official docs, with the sentence "Branch names with 40 hexadecimal characters are prohibited, because they are similar to Git commit hashes." Five, our lab: git 2.43.0, three scenarios (a branch named with 40 hex characters pointing at a different commit; a repository where the pinned commit does not exist and the trap branch is the default branch; and a pre-receive hook rejecting pushes of branches matching that pattern). The outputs reproduced in the body are from that session, abbreviated where we mark it with brackets. Six, the npm registry: consulted today at registry.npmjs.org, source of the publication dates for @anthropic-ai/claude-code 2.1.179 (16 June 2026) and 2.1.278, and @openai/codex 0.146.0 (29 July 2026) and 0.155.1. What is our opinion, and is stated as such: that a vendor's response to a coordinated report is a purchasing input; that today's effective defence lies in the repository host; that the development laptop belongs to the production control plane even when it is not in the inventory; and the list of what we would do this week.
Cover image: royalty-free stock photograph. The text and branding are ours, added on top.