Your Coding Agent Ran Somebody Else's Plugin: What Plugin4Shell Teaches Us
Three of the four most popular AI coding agents could be tricked into executing attacker-controlled plugin code. The fix vendors shipped matters less than the habit you still do not have: treating agent plugins as depend
Three of the four most popular AI coding agents could be tricked into executing attacker-controlled plugin code. The fix vendors shipped matters less than the habit you still do not have: treating agent plugins as dependencies.
Picture a developer named Priya. In June, she installs a Terraform helper plugin for her coding agent from the official marketplace. It has 40,000 downloads, a reassuring name, and a README with badges. She reviews the code once, pins the commit she reviewed, and gets back to work.
In August, the repository behind that plugin changes hands. A new commit appears, wearing the exact SHA of the commit Priya reviewed, like a name tag peeled off someone else's jacket. Her agent updates the plugin, checks out what it believes is the reviewed commit, and runs it. The new code reads her ~/.aws/credentials, lists her S3 buckets, and phones home. Priya clicked nothing. She approved nothing. The attack needed zero interaction from her.
Yes, this is a hypothetical scenario. But the mechanism behind it is not. It was disclosed this week, it has a name, and until very recently it worked against Claude Code, OpenAI Codex, and GitHub Copilot. That is the gap this piece fills. The coverage of Plugin4Shell tells you to update your agent. Nobody is showing you how to stop trusting plugin code by default in the first place. Here is the part the explainers skip: your agent's plugins are a software supply chain, and you are running it without an inventory, without pinning, and without verification. Let me show you how to fix that in an afternoon.
The News Peg, Precisely
Researchers at the cybersecurity startup AIR published their findings this week in a blog post. They call the flaw Plugin4Shell. The affected agents: Anthropic's Claude Code, OpenAI's Codex, Google's Gemini CLI, and Microsoft's GitHub Copilot. The vulnerability was first discovered in May and disclosed to vendors in June 2026.
The patch status, as of the disclosure: Anthropic fixed it in Claude Code version 2.1.179. OpenAI fixed it in Codex version 0.146.0. Google deprecated the Gemini CLI rather than patching it, pointing users toward Antigravity instead. GitHub had not released a fix at disclosure time, though it told The Register it had applied restrictions on creating version or tag names that resemble commit SHAs. AIR's researchers replied that those restrictions may not be enough, because plugin marketplaces can be hosted on platforms other than GitHub, such as Bitbucket.
One more number worth sitting with: reporting on the disclosure indicates around 925 plugins were hijacked in related supply-chain activity. The marketplace is not a curated garden. It is a street market, and some of the stalls changed owners while you were not looking.
How the Attack Actually Works
Here is the mechanism, and it is worth understanding precisely, because the same shape of bug will show up again.
When you install a plugin for one of these coding agents, the agent downloads the plugin's code from a Git repository. To make sure it runs the copy you approved, it uses the SHA of the reviewed commit, the unique cryptographic identifier Git assigns to every commit. You hand the agent a SHA; the agent checks out that SHA. In theory, this means you always run reviewed code.
In practice, Claude Code, Codex, and GitHub Copilot passed the SHA to Git for checkout and then never verified that Git had actually checked out the commit matching that SHA. That missing verification is the entire vulnerability.
An attacker who controls the plugin's repository, either by taking over the repo behind a trusted plugin or by publishing a benign plugin and later turning it malicious, exploits the gap like this: they create a new version of the repository containing malicious code, and they name it with the SHA of the legitimate commit. Git can resolve a name to a branch or tag rather than to the commit object, so when the agent asks Git to check out the SHA, Git hands back the attacker-controlled version. The agent runs it, believing it executed your reviewed commit.
The Gemini CLI variant is a different flavor of the same failure. Gemini CLI used the SHA to fetch the legitimate plugin code, then told Git to check out that code using the name FETCH_HEAD. An attacker who controlled the repository could create a malicious version of the plugin also named FETCH_HEAD, a second version Git would return when the CLI asked for the code. Same root cause, stated plainly: the agent never confirmed that the code it received was the code it asked for.
A diagram of the trust chain, so you can see where it breaks:
You review plugin code at commit 9f3a1c...
|
v
Marketplace listing ---> Plugin repository (git)
|
attacker controls the repo
and creates a malicious ref
NAMED 9f3a1c...
|
+-------------------------+
v
Agent: git checkout 9f3a1c...
|
v (never verifies what came back)
Agent executes the attacker's code
with YOUR file, network, and cloud access
I understand why the vendors built it this way. Passing a SHA to Git feels like verification. It looks like pinning. Nobody wrote a comment saying "skip the verification step"; the verification simply was never there, because the checkout was treated as the verification. That is the design lesson worth carrying out of this incident: a checkout is a request, not a confirmation. Treating the request as the confirmation is how zero-click remote code execution happens.
And note what the malicious plugin inherits. As Pareekh Jain, a principal analyst quoted in the disclosure coverage, put it: these plugins mostly run with the same access the developer has. Source code, API keys, cloud credentials, CI/CD systems. The blast radius of a compromised plugin is not the plugin. It is everything your credentials can touch.
The Part Every Explainer Skips
Here is the uncomfortable truth the "just update your agent" advice papers over. The patch fixes how the agent validates a checkout. It does not fix the fact that you have no idea what is installed.
Most developers I know cannot answer three simple questions about their own machines: which agent plugins are installed, which exact commits they are pinned to, and when each one was last reviewed. If you cannot answer those, you are not running a plugin ecosystem. You are running a hope.
The gap this piece fills is the practitioner part: build an inventory of your agent's plugins the way you would build a software bill of materials for your dependencies. You already do this for npm packages and container images. Your agent's plugins deserve the same treatment, because they run with more privilege than either.
Audit What You Have Installed
Start with the inventory. Every coding agent stores its plugins somewhere on your disk; find yours and list what is actually there. For Claude Code, installed plugins live under ~/.claude/plugins. A first pass looks like this:
# List every installed plugin and its manifest
for d in ~/.claude/plugins/*/; do
echo "=== $(basename "$d") ==="
cat "$d/.claude-plugin/plugin.json" 2>/dev/null \
|| cat "$d/plugin.json" 2>/dev/null \
|| echo "(no manifest found)"
done
A few things are worth noting about this command. First, it is read-only; it changes nothing, so there is no excuse not to run it. Second, the || echo "(no manifest found)" branch matters more than the happy path. A plugin directory with no manifest is a plugin your agent will still load and your inventory cannot describe. That is a finding, not a quirk. Third, run the equivalent for every agent on your machine, not just your favorite one. Plugin4Shell hit four agents at once; your exposure is the union of all of them.
Now capture provenance for each entry. For every plugin, record where its code actually comes from: the repository URL, the commit SHA you believe you are running, and the date you last reviewed it. If you cannot fill in one of those fields, write "unknown" and treat that plugin as untrusted until proven otherwise.
Pin It in a Lockfile
An inventory in your head is not an inventory. Write it down in a lockfile, the same way package-lock.json records the exact dependency tree your build resolved. Here is a minimal format:
{
"plugins": [
{
"name": "terraform-helper",
"source": "https://github.com/example-org/agent-plugins",
"pinned_sha": "9f3a1c2e4b5d6f708192a3b4c5d6e7f8091a2b3c4d",
"reviewed_on": "2026-09-20",
"reviewed_by": "priya",
"auto_update": false
},
{
"name": "db-migration-runner",
"source": "https://bitbucket.org/example-org/db-plugins",
"pinned_sha": "unknown",
"reviewed_on": "unknown",
"reviewed_by": "unknown",
"auto_update": true
}
]
}
Notice the second entry. That is what an honest inventory looks like: most developers will have at least one plugin they installed months ago from a marketplace listing and never thought about again. The lockfile forces the admission. An "unknown" SHA next to "auto_update": true is the precise configuration Plugin4Shell exploited, and you probably have one sitting on your disk right now.
The cross-links are not decoration. The pinned_sha is the commit you reviewed; auto_update: false is the commitment that no code runs until a human updates that field. Together they express a policy most teams already apply to production dependencies and almost nobody applies to agent plugins.
Run the Check the Agents Skipped
Here is the verification the vulnerable agents never performed, as a script you can run yourself before you let any plugin code execute. It clones the repository, checks out the pinned SHA, and then confirms that the checked-out commit is actually the commit you asked for:
#!/usr/bin/env bash
# verify-plugin.sh: the check Plugin4Shell proved necessary
set -euo pipefail
REPO="$1" # plugin repository URL
EXPECTED_SHA="$2" # SHA from your lockfile
DEST="$3" # scratch directory
git clone --quiet "$REPO" "$DEST"
cd "$DEST"
git checkout --quiet "$EXPECTED_SHA"
ACTUAL_SHA=$(git rev-parse HEAD)
if [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then
echo "MISMATCH: asked for $EXPECTED_SHA, got $ACTUAL_SHA"
exit 1
fi
echo "OK: running verified commit $ACTUAL_SHA"
Run it against your lockfile:
while read -r repo sha; do
dir=$(mktemp -d)
./verify-plugin.sh "$repo" "$sha" "$dir" || echo "FAILED: $repo"
rm -rf "$dir"
done < <(jq -r '.plugins[] | select(.pinned_sha != "unknown") | "\(.source) \(.pinned_sha)"' plugin-lock.json)
Here is what to notice about this script. The git rev-parse HEAD after checkout is the entire fix, in one line. The vulnerable agents passed the SHA and trusted the outcome; this script passes the SHA and checks the outcome. That asymmetry, request versus confirmation, is the whole incident in miniature. Also notice the select(.pinned_sha != "unknown") filter: plugins you have not reviewed cannot be verified, so the script skips them loudly rather than blessing them silently. Verification that quietly passes unknown inputs is worse than no verification, because it manufactures confidence.
Gate It With Policy
A lockfile on your laptop helps you. A policy in your team helps everyone. If your team standardizes agent plugins, express the allowlist as code and evaluate every install against it. Here is a minimal policy in Rego, the language of the Open Policy Agent:
package agent.plugins
# Default deny: a plugin runs only if it is explicitly allowed.
default allow = false
allow {
some p in input.plugins
some a in data.allowlist
p.name == a.name
p.pinned_sha == a.pinned_sha
a.reviewed == true
}
Evaluate it with the real tool and real commands:
# allowlist.json holds the team's reviewed plugins; installed.json is generated
# from each developer's plugin directory by the audit step above.
opa eval \
--data allowlist.json \
--data policy.rego \
--input installed.json \
'data.agent.plugins.allow'
Let me give you an example of what this buys you. A developer installs a helpful new plugin from the marketplace on Monday. On Tuesday the CI job that evaluates this policy fails, because the plugin is not on the allowlist. The developer now has a choice: submit it for review, or remove it. Before this policy existed, the choice was made silently by the marketplace, and the developer never knew there was a decision at all. Policy-as-code does not prevent anyone from installing plugins. It makes the installation a visible, reviewable event, which is the part that was missing.
Where This Breaks
No hedging here. These are the limits, stated plainly.
- Pinning a SHA does not protect you from a plugin that was malicious from the day you installed it. Verification answers "is this the code I reviewed," not "is this code safe." Your first review still has to be real.
- The first-install problem is unsolved. The marketplace listing, the README badges, and the download count are not evidence of anything. Somebody has to read the code, and that somebody is you.
- Hash pinning assumes the source repository is the thing being pinned. If the marketplace serves code from somewhere other than the repo you verified, your pin attests to the wrong artifact. Verify the serving path, not just the source.
- Auto-update is the enemy of pinning. Every agent that silently updates plugins reintroduces the exact exposure your lockfile removed. Turn it off, or accept that your inventory is fiction.
- This approach scales to dozens of plugins, not thousands. If your organization runs a large internal plugin catalog, you need signing and a real artifact pipeline, not a JSON file and a shell script. Know which side of that line you are on.
Build It If, Skip It If
Build this if your coding agent can reach production credentials, source code, or CI/CD systems. That describes nearly every professional setup, which is why the disclosure coverage kept coming back to enterprise exposure. The minimal viable version fits in an afternoon: run the audit command, write the lockfile, turn off auto-update, and add a weekly calendar reminder to re-run the verification script and diff the results.
Skip the Rego policy if you are a solo developer with three plugins. The lockfile and the script are enough; the policy layer pays off when more than one human installs plugins on more than one machine. Skip all of it only if your agent runs fully sandboxed with no credentials, no network, and no repository write access. If that describes your setup, you have already solved a harder problem than this one.
One more consideration before you decide. The vulnerable versions are patched, or deprecated, or restricted, depending on the vendor. Patches fix known bugs. They do not fix the habit of running unaudited code with your identity. Plugin4Shell will get a CVE-style writeup and fade from the news cycle; the next supply-chain flaw in agent tooling will not wait for you to build the inventory afterward.
Close the Loop
Here is the imperative version. This afternoon, list your agent's plugins, write down where each one comes from and which commit you believe you are running, and turn off silent auto-updates. Run the verification script against your own lockfile and sit with whatever it tells you. Then decide, from data rather than from hope, which of those plugins has earned the right to run with your credentials.
The vendors fixed the checkout bug. The inventory bug is yours to fix, and nobody is going to patch it for you.
What is the oldest unreviewed plugin on your machine right now, and what would it take for you to trust it again?
Resources
- A zero-click RCE flaw in AI coding agents could have exposed enterprise systems: InfoWorld's full writeup of the Plugin4Shell disclosure, including the per-vendor patch status.
- AI Agents News Brief: Security Vulnerabilities, Enterprise Adoption, and Consumer Reach: September 18 brief with the Plugin4Shell headlines and the hijacked-plugin reporting.
- Critical Paperclip bugs expose AI agent trust failures: CSO on a related disclosure: the same broken trust assumptions, this time in an agent control plane's identity boundaries.
- Browser Extension Vulnerabilities Enable AI Assistant Hijacking via Trusted Page Injection: another entry point worth knowing: malicious browser extensions driving AI assistants as the vendor, across Chrome, Edge, Comet, Opera Neon, and Claude in Chrome.
- Open Policy Agent documentation: the policy engine used in the gating example above.
- SPDX specification: the open standard for software bills of materials; the vocabulary your plugin inventory is borrowing.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.