Skip to main content
Plugin4Shell: A Pinned Plugin Is Not a Verified Plugin

Plugin4Shell: A Pinned Plugin Is Not a Verified Plugin

KU
· Founder & Chief Editor
• 8 min read•Updated •21 views

A SHA pin that nobody checks is a label, not a control. Plugin4Shell showed that four major coding agents never checked.

Key takeaways

  • Plugin4Shell is a zero-click plugin-install RCE in Claude Code, Codex, GitHub Copilot and Gemini CLI, disclosed by Air Security in September 2026.
  • The agents checked out a pinned SHA but never confirmed the working tree landed on it.
  • Upgrade Claude Code to 2.1.179 and Codex to 0.146.0, and verify pins yourself everywhere else.

Plugin4Shell is a zero-click remote code execution bug in the plugin installers of Claude Code, OpenAI Codex, GitHub Copilot and Gemini CLI. The agents checked out a pinned commit but never confirmed the working tree landed on it. Upgrade Claude Code to 2.1.179+ and Codex to 0.146.0+, and verify pins yourself for Copilot and Gemini CLI.

Verified against

Claude Code 2.1.179 and Codex 0.146.0 fix versions, Copilot and Gemini CLI status, against Air Security's Plugin4Shell write-up (September 17, 2026) and the Cloud Security Alliance research note. Checked October 2026.

Air Security's lab found it in May 2026. Our verdict: a SHA pin that nobody verifies is a label, not a control. Treat Copilot and Gemini CLI plugins as untrusted until proven otherwise, and add your own verification step anywhere you vendor plugins.

What actually broke

Plugin marketplaces sell a simple promise. A reviewer looked at plugin code at a specific commit, the marketplace records that SHA, and every install gets exactly those bytes. Pinning to a full commit hash is the strongest reference git offers, which is why security guidance has pushed it for years.

Air Security's write-up shows where the promise leaked. The agent runs a checkout of the pinned reference and moves on. It never resolves what is really in the working tree afterward and compares it to the pin. In the main variant (Claude Code, Codex and Copilot), git's own ref resolution does the damage: an attacker creates a branch named with the 40-character pinned hash and makes it the default, git prefers that branch over the commit, so the checkout succeeds, the pin looks honored, and different code lands on disk. The Gemini CLI variant is different in mechanics but identical in cause: the agent fetches the right commit and then runs git checkout FETCH_HEAD, which a default branch literally named FETCH_HEAD hijacks.

The code that lands then runs inside an agent that already has your shell, your files and your tokens. That is why this is zero-click: Air's write-up notes that background plugin auto-update is on by default in Claude Code and Codex, so installing or updating the plugin is the whole attack.

Two ways an attacker gets there

Both paths need control of the plugin's repository, and both are ordinary supply-chain moves rather than exotic ones.

The post-review swap: an attacker submits a clean plugin, waits for it to pass review and get pinned, then changes the repository afterward. The review was real. The pin was real. Neither bound the bytes you received.

The hijacked author: an attacker steals a maintainer's credentials or token, pushes to a legitimate plugin's repository, and every agent with that plugin installed pulls the result. This is the same shape as the incidents we covered in npm supply chain CI hardening, moved from your build to your laptop.

Only a check made at install time, on the machine doing the install, closes them.

Where each vendor stands

Air reported the bugs to all four vendors in June 2026, three months before the write-up, and the outcomes split unevenly.

AgentAffected variantFixed inStatus as of October 2026What we'd do
Claude CodeSHA-named branch2.1.179PatchedUpgrade, re-verify old installs
CodexSHA-named branch0.146.0PatchedUpgrade, re-verify old installs
GitHub CopilotSHA-named branchNone shippedUnpatched per AirDisable third-party plugins
Gemini CLIFETCH_HEAD branchWon't fixDeprecated, no patch planned; users pointed to AntigravityMigrate off

GitHub itself rejects 40-hex branch names, but Air points out that Bitbucket and self-hosted git servers allow them, and those are valid marketplace backends.

Read that last pair carefully. If your team still runs Copilot's plugin mechanism or Gemini CLI, there is currently no vendor-side remedy, so the controls below are your only defense. Do not wait for a patch that may not come. Gemini CLI users in particular should treat migration as the fix, not a workaround.

The verification we would put in your setup

The vendor fix is conceptually one step: after checkout, resolve the commit actually in the working tree and abort unless it equals the pinned SHA. You can do the same in any vendored plugin directory, in a pre-commit hook, or in a CI job that guards your team's shared agent configuration.

Bash
#!/usr/bin/env bash
# scripts/verify-plugin-pin.sh, usage: verify-plugin-pin.sh <plugin-dir> <pinned-sha>
set -euo pipefail
dir="$1"; pin="$2"

actual="$(git -C "$dir" rev-parse HEAD)"
if [ "$actual" != "$pin" ]; then
  echo "FAIL: $dir is at $actual, pinned $pin" >&2
  exit 1
fi

# A ref named like the pin can shadow the commit; refuse it outright.
if git -C "$dir" rev-parse --verify --quiet "refs/heads/$pin" >/dev/null; then
  echo "FAIL: branch named after the pin exists in $dir" >&2
  exit 1
fi

# Gemini-style variant: a branch named FETCH_HEAD shadows the fetched commit.
if git -C "$dir" rev-parse --verify --quiet "refs/heads/FETCH_HEAD" >/dev/null; then
  echo "FAIL: branch named FETCH_HEAD exists in $dir" >&2
  exit 1
fi

# Uncommitted changes mean the tree is not the pinned content.
git -C "$dir" diff --quiet HEAD -- || { echo "FAIL: dirty tree" >&2; exit 1; }
echo "OK: $dir == $pin"

Run it with the full 40-character SHA, never an abbreviation:

Terminal
./scripts/verify-plugin-pin.sh ./plugins/my-plugin 4f9c2a1e...

The point is that the comparison happens on your side, using values you wrote down, against the directory that will actually execute. For the wider habit of treating agent tooling as an untrusted dependency, see our AI agent security guide; GitSpawn's git config hijack is the same lesson from a different git corner.

What to do this week

  • Upgrade: Claude Code to 2.1.179 or later, Codex to 0.146.0 or later. Check with claude --version and codex --version.
  • Audit installed plugins: list every plugin on every developer machine and CI runner, note its source repository, and delete anything nobody can justify. Plugins installed before your upgrade were fetched by the vulnerable code, so re-verify them.
  • Turn off plugin auto-update: an update is a fresh chance to pull swapped code. Update on purpose, from a reviewed change, not on a timer.
  • Allowlist marketplaces: permit one or two sources your security team has looked at, and block the rest at the configuration level where the agent supports it.
  • Contain the agent: run it in a sandboxed container with scoped, short-lived credentials, no ambient cloud keys and restricted egress. A compromised plugin then reaches a disposable box instead of your laptop. Our post on running AI CLI agents in CI pipelines covers the container and token scoping in detail.

The last item matters most for Copilot and Gemini CLI, where the patch story is weakest.

The decision, concretely

  • Are you on Claude Code or Codex? Upgrade to 2.1.179 or 0.146.0 today, then re-verify every plugin installed before the upgrade.
  • Are you on Copilot or Gemini CLI plugins? Assume exposure. Disable third-party plugins, and plan a move off Gemini CLI since it will not be fixed.
  • Do you vendor plugins into your own repos? Add the pin verification script to CI so a mismatch fails the build.
  • Do agents run with your real credentials? Move them into sandboxed containers with scoped tokens before you install another plugin.

Questions people ask

Which Claude Code version fixes Plugin4Shell?

Claude Code 2.1.179, confirmed by Air Security as fixed on June 17, 2026, three months before public disclosure. Codex's fix is 0.146.0. Run claude --version and codex --version to check. Upgrading protects new installs and updates, but plugins installed by an older client were fetched by the vulnerable checkout, so re-verify or reinstall them after upgrading.

Is GitHub Copilot affected by Plugin4Shell?

Air Security reported the SHA-named-branch variant to Microsoft and says no client fix has shipped. GitHub's own hosting rejects 40-hex branch names, which blunts the attack for plugins hosted there, but not for Bitbucket or self-hosted git. Until a patch lands, disable third-party Copilot plugins you can't vendor and verify yourself.

Does pinning a plugin to a commit SHA protect me?

Only if something checks the result. A pin records which commit you meant to get; it does not prove what landed on disk. Plugin4Shell showed git can resolve a SHA-shaped branch name ahead of the commit. Compare git rev-parse HEAD against the pin after every checkout, on the machine doing the install, and fail closed on a mismatch.

The call we'd make

Upgrade the two patched agents, freeze plugin updates, and put the verification script in front of anything you vendor. Then move agents into containers with scoped credentials, because the next bypass will not announce itself with a name. A pin is a claim about what you will get. Only a comparison run on your own machine turns that claim into a control.

Sources

Explore topics:SecurityAI
React
Share this post
Cite this article
Plain text

Kiril Urbonas (2026). Plugin4Shell: A Pinned Plugin Is Not a Verified Plugin. DevOpsNess. Updated 2026-10-31. https://www.devopsness.com/blog/plugin4shell-coding-agent-plugin-pinning

Markdown

[Plugin4Shell: A Pinned Plugin Is Not a Verified Plugin](https://www.devopsness.com/blog/plugin4shell-coding-agent-plugin-pinning) (Kiril Urbonas, DevOpsNess, updated 2026-10-31)

KU

Kiril Urbonas

Founder & Chief Editor

590 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.