Plugin4Shell: A Git Branch Disguised as a Commit Hash Let Attackers Swap Plugin Code in AI Coding Agents
A branch named after a commit let attackers swap out "pinned" plugin code in AI coding agents, with no click required. What happened, how to reproduce it safely on your own machine, who's still exposed, and how to set up alerts so you hear about the next vulnerability early.
If you pin a plugin to a specific commit, what are you actually protected from? The obvious answer is that nobody can swap the code out from under you later. That's the whole point of pinning.
Turns out that wasn't true for four of the most popular AI coding agents. On September 18, The Hacker News and Help Net Security covered a vulnerability that Air Security calls Plugin4Shell. It affected plugin installs in Claude Code, OpenAI's Codex, GitHub Copilot, and Google's Gemini CLI. A plugin author could get a clean plugin reviewed and pinned, then later swap in different code, and the agent would install it with no click and no prompt.
Claude Code is one of the two agents I use, so this one landed close to home.
What happened
Each of these agents installs a plugin by cloning its repository, then checking out the exact commit that was reviewed. That commit is identified by its SHA, a 40-character hex string. According to Air Security's write-up, none of them checked afterward that the files on disk actually came from that commit.
That gap matters because of how git looks up names. A branch can be named anything, including a 40-character hex string that matches a commit's SHA. If an attacker who controls the repository creates a branch with the pinned SHA as its name and makes it the default branch, git checkout <sha> picks the branch over the commit. So the agent believes it installed the reviewed version, and it actually installed whatever the attacker put on that branch.
See it for yourself
You can watch this happen on your own machine in a few minutes. Everything happens inside one throwaway folder, and the "malicious" plugin is a one-line script that we only ever read, never run.
I ran this on my Bazzite machine (a Linux distro built on Fedora Atomic) with git 2.55.0, and it also works the same on git 2.43. It should behave the same in any shell on macOS or Linux, or in Git Bash on Windows.
The -b flag in step 1 needs git 2.28 or later, and git --version will tell you what you have. Run every step in the same terminal window, because later steps reuse the PIN variable from step 2. If git stops at a commit and asks who you are, set your name and email with git config --global user.name and git config --global user.email, then run the commit again.
Step 1: make a folder and a repository to play the plugin author.
mkdir plugin4shell-demo
cd plugin4shell-demo
git init -b main upstream
cd upstreamGit prints Initialized empty Git repository. The upstream repository stands in for the plugin's repository, the one the attacker controls.
Step 2: commit the version that gets reviewed, and save its SHA.
echo 'echo "This is the reviewed plugin."' > plugin.sh
git add plugin.sh
git commit -m "Reviewed version"
PIN=$(git rev-parse HEAD)
echo "$PIN"The last command prints a 40-character hex string. That's the commit a marketplace would review and pin, so we save it in PIN.
Step 3: create a branch named after that SHA, and put different code on it.
git checkout -b "$PIN"
echo 'echo "This is NOT the code you reviewed."' > plugin.sh
git commit -am "Swapped version"
cd ..Git creates and switches to the new branch silently, with no warning at all, even though the branch's name is the same as the commit's SHA. Since we're still on that branch when we leave, it's now the repository's default branch, and that's the branch a clone checks out first.
Step 4: install the plugin the way the agents did.
git clone upstream installed
cd installed
git checkout "$PIN"This is where it breaks. Git prints warning: refname '<SHA>' is ambiguous., followed by a longer explanation that can vary a bit between git versions, and then Already on '<SHA>'. That last line is the giveaway. Git never moved to the commit at all. Instead, it stayed on the look-alike branch that the clone had already checked out.
Step 5: compare what we got to what we pinned.
git rev-parse HEAD
echo "$PIN"
cat plugin.shThe two hashes don't match, and plugin.sh reads This is NOT the code you reviewed. The checkout named the right commit and still delivered the wrong code.
Step 6: add the check the agents were missing.
if [ "$(git rev-parse HEAD)" = "$PIN" ]; then
echo "Checkout matches the pin."
else
echo "Checkout does NOT match the pin. Stopping."
fiThis prints Checkout does NOT match the pin. Stopping. If you pin by SHA in your own scripts, this is the whole fix: after checkout, compare git rev-parse HEAD to the SHA you pinned, and stop if they don't match. It doesn't depend on how git resolves names, so it catches the swap no matter how it happened.
Step 7: clean up.
cd ../..
rm -rf plugin4shell-demoDo note that git did warn you back in step 4. However, the warning goes to the terminal's error output, and when an agent runs git in the background, nobody is reading that output. That's why the check in step 6 matters more than the warning does.
Who's patched and who isn't
Air Security found the flaw in May and reported it to all four vendors in June. Here's where each tool stands, based on Air Security's timeline and The Hacker News:
| Agent | Status |
|---|---|
| Claude Code | Fixed in 2.1.179 (June 17) |
| OpenAI Codex | Fixed in 0.146.0 (August 12) |
| GitHub Copilot | No fix shipped as of the September 18 reports |
| Gemini CLI | Won't be fixed. Google retired it and points users to Antigravity. |
The patches shipped months before the public write-up. That's how coordinated disclosure is supposed to work, but it also means the release notes didn't flag it, according to The Hacker News. If you weren't updating, you had no way to know you needed to.
Not the first time git's hashes made the news
Plugin4Shell didn't break any cryptography. The SHA was fine, and git just picked the wrong thing when one name matched two things. However, it isn't the first time trust in git's hashes has been tested, and the older case is worth knowing about.
Everything git stores, including files, folders, and commits, is named by a SHA-1 hash of its contents. The promise is that if two things have the same hash, they're the same content. In 2017, researchers at Google and CWI Amsterdam broke that promise for SHA-1 with SHAttered. They produced two visibly different PDF files with the exact same SHA-1 hash. It took roughly nine quintillion SHA-1 computations, about 6,500 CPU-years and 110 GPU-years of work.
Importantly, SHAttered was a collision: the researchers crafted two new files that matched each other. It didn't find a new file that matched a hash that already existed, which is a much harder problem. So SHAttered didn't give anyone a way to forge a replacement for a commit that already existed. What it proved is that SHA-1 was weak enough that you couldn't count on it forever.
The fixes are a good example of detect-and-mitigate in practice. GitHub added collision detection in March 2017, using code written by Marc Stevens (one of the SHAttered researchers) and Dan Shumow. It spots the byte patterns that collision attacks leave behind and refuses the object. Git itself switched to that same hardened SHA-1 by default in version 2.13, per git's own transition docs.
The long-term fix is moving git off SHA-1 entirely to SHA-256, and git is working toward that, but it isn't there yet. You can already create a SHA-256 repository with git init --object-format=sha256, but not every hosting service supports them, and the developers have talked about making it the default in Git 3.0 once the rest of the ecosystem is ready.
Why there's more than one SHA
So why is there more than one family of SHA hashes at all? The reason is less obvious than "newer is stronger." SHA-2, which SHA-256 belongs to, isn't a completely new design. It's built the same basic way as SHA-1, and it holds up because it's bigger and more complex, not because it works differently. As such, a flaw in that shared design could be a problem for both.
That's why NIST ran a public competition for a third family after the attacks on SHA-1 in the mid-2000s. The winner, Keccak, became SHA-3, and it works in a completely different way. NIST's announcement put it plainly: an attack that works on SHA-2 most likely wouldn't work on SHA-3, because the two are designed so differently. NIST still considers SHA-2 secure, so SHA-3 is the insurance policy in case that ever changes.
There's already a small example of a shared weak point. SHA-1 and most of SHA-2 are open to a length-extension attack: if someone has the hash of a secret plus a message, they can make a valid hash for that message with extra data tacked on, without ever knowing the secret. It comes from the way both are built, and SHA-3 isn't affected. (It's also why signing schemes use HMAC instead of a plain hash of a secret and a message.)
A hash is only as trustworthy as the algorithm behind it and the process around it. SHAttered went after the algorithm, and Plugin4Shell went after how git looks up a name. If your git is older than 2.13, update it. git --version will tell you.
Where this left my setup
The two agents I use are Claude Code and OpenCode.
OpenCode wasn't named in any of the reports I read. Its plugin docs describe loading plugins from local folders or installing npm packages at startup, not cloning git repositories and checking out a SHA, so this particular trick doesn't map onto it. That isn't the same as safe, though. npm packages come with their own supply-chain risks, and "not on the list" only means nobody reported it.
I don't use plugins in either tool, so I wasn't exposed to this one. It still got me to check my versions.
On my Bazzite machine, I use the Claude Code CLI as a download-and-run program that doesn't touch any system files, and it doesn't update itself in the background. I updated it last week when the CLI prompted me, and it's on 2.1.282 now, well past the 2.1.179 fix. How Claude Code updates depends on how you installed it. Claude Code's setup docs say the native installer updates itself in the background, while Homebrew, WinGet, and Linux package manager installs don't.
If you use Claude Code, you can check yours in a few seconds:
claude --versionIf the version is 2.1.179 or later, you have the fix. claude doctor also shows whether auto-updates are on and the result of the most recent update attempt, which is a quick way to find out which kind of install you have.
I checked git too, and that one works differently on Bazzite. Bazzite is image-based, so git ships as part of the OS image and only updates when the whole OS does, through Bazzite's normal update process. In my case, I was already on the latest image, and git 2.55.0 is the current release, so there was nothing to update.
If the OS image ever falls behind, you could install a second copy of git through Homebrew, which Bazzite recommends for command-line tools over layering packages onto the system image. I'd rather not keep two copies of the same program around, though, so I stick with the one that comes with the OS. It's the same lesson as Claude Code: how something was installed decides how its updates reach you.
Automatic updates cut both ways
Here's the thing about automatic updates. They're why anyone on a native install of Claude Code was protected from this months before they'd heard of it, because their install picked up 2.1.179 in June without them doing anything. A manual install like mine is the case where "patched" and "protected" stop meaning the same thing.
Automatic updates have real costs too. Every so often an update breaks something that worked yesterday. (It's why I moved off of Windows 11). And just as an update can bring in a fix, it can bring in a new bug, including a security one, that you never chose to install. Plugin4Shell is actually a version of that second problem: the auto-updating plugin system was what made the attack zero-click.
For my own tools, I'd rather take the occasional broken update than sit on a known vulnerability without knowing it. Luckily, both of the agents I use have a middle ground. Claude Code's release channel setting can be switched from latest to stable, which gives you a version that's typically about a week old and skips releases with major regressions. For a Homebrew install, setting the CLAUDE_CODE_PACKAGE_MANAGER_AUTO_UPDATE environment variable to 1 has Claude Code run the upgrade for you in the background.
OpenCode downloads updates at startup by default, and its autoupdate setting can turn that off or change it to "notify", which tells you a new version exists without installing it. The notify option doesn't work, though, if you installed OpenCode through a package manager like Homebrew.
Whatever you pick, pick it on purpose. The worst setup is assuming you're updating when you aren't.
Why this is going to keep happening
Plugin4Shell was found by people, but models are now finding vulnerabilities left and right. Anthropic runs a public disclosure dashboard for vulnerabilities that Claude Mythos Preview and its other Mythos-class models have found in open-source software. As of August 26, it listed 2,300 disclosed across 392 projects, with 421 patched so far. On September 17, InfoQ reported that OpenAI's GPT-6 Astra is the first model OpenAI rates "Critical" for cybersecurity. In OpenAI's testing it found previously unknown browser vulnerabilities and built a working exploit chain in about 29 hours, and OpenAI is limiting access to its strongest cyber capabilities.
The gap between those two numbers is what I keep coming back to: 2,300 found and 421 fixed. Finding bugs is getting faster than fixing them. The fixes will still ship, but there's going to be more news like Plugin4Shell, and it's going to show up more often. Keeping an eye out for this kind of news is good for everyone, because it's how a fix that exists actually ends up on your machine.
Getting security news in front of you
You don't need a dedicated security feed to hear about news like this. A news app you already have can do a lot of it.
Google News
To start, follow the topics you care about:
- Open Google News and tap Search at the top.
- Search for a topic or source, like "cybersecurity" or the name of a tool you use.
- Tap Follow.
Followed topics show up in your Following tab. Next, turn on notifications:
- Tap your profile picture at the top right, then News settings.
- Under Alerts, tap Notifications and turn on Get notifications.
- Set Number of notifications somewhere between Low and High, and turn individual notification types on or off.
These steps are from Google News Help (following topics, notifications).
Do note that Google News notifications are grouped by type, not by the topics you follow, so following "cybersecurity" puts it in your feed without guaranteeing a push for every story. When you want something narrow, like any news that mentions a specific tool, Google Alerts is the better fit. Type a search such as "Claude Code" vulnerability, open Show options, set How often to As-it-happens, and create the alert. It arrives as an email instead of a push notification, but it only fires on the exact thing you asked about.
Apple News
Apple News works by channel, meaning the publication, not by topic. Notifications get turned on for individual channels, so the trick is to follow a tech or security publication you trust and turn notifications on for it. I don't use an iPhone, so these steps come from Apple's documentation, not my own phone.
On a Mac, per Apple's guide:
- Open News and choose File > Manage Notifications & Email.
- Turn notifications on for the channel you want, then click Done.
On iPhone, open News, go to the Following tab, and look for Notifications & Email to turn channels on. If nothing arrives, check that News is allowed to send notifications in your system settings.
Watch the tools you actually run
Watching releases is what I'd recommend most, because it skips the news cycle entirely. Most developer tools publish releases on GitHub. On the tool's repository page, open the Watch menu, choose Custom, and check Releases (and Security alerts if it's offered), per GitHub's docs. Now you get a notification whenever a new version ships, whether or not anyone writes about it. For a manual install like mine, that notification is the reminder to update.
The takeaway
Pinning is still a good idea, and so is updating, but neither one protects you if you never check that it happened. Git has been through this before with SHAttered, and in both cases the people who understood how their tools worked were the first ones protected. Check your versions now, choose your update settings on purpose, and set up one alert so the next Plugin4Shell reaches you before it reaches your machine.
Sources
- Plugin4Shell: Zero Click RCE Vulnerability found in top 4 most popular coding agents (Air Security, the original disclosure)
- Plugin4Shell Lets Repository Owners Swap Pinned Plugin Code Across Four AI Coding Agents (The Hacker News, September 18, 2026)
- Zero-click RCE vulnerability hit four major AI coding agents, two remain unpatched (Help Net Security, September 18, 2026)
- SHAttered (CWI Amsterdam and Google, February 23, 2017)
- SHA-1 collision detection on GitHub.com (GitHub Blog, March 20, 2017)
- Hash function transition (Git documentation)
- Git Developers Talk About Potentially Releasing Git 3.0 By The End Of Next Year (Phoronix, October 12, 2025)
- NIST Selects Winner of Secure Hash Algorithm (SHA-3) Competition (NIST, October 2, 2012)
- Length extension attack (Wikipedia)
- Updates, Rollbacks, and Rebasing and Package Layering (Bazzite documentation)
- Claude Code: Advanced setup (Anthropic docs, on install methods, auto-updates, and release channels)
- OpenCode: Plugins and Config (OpenCode docs)
- Coordinated vulnerability disclosure dashboard (Anthropic, last updated August 26, 2026)
- GPT-6 Astra is the First Model OpenAI Classifies as Critical for Cybersecurity (InfoQ, September 17, 2026)
- OpenAI to limit access to Astra's most powerful cyber capabilities (Axios, September 1, 2026)
- Customize what you find on Google News and Manage your Google News notifications (Google News Help)
- Manage notifications from News on Mac (Apple Support)
- Configuring notifications (GitHub Docs)