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

Plugin4Shell: A Pinned Plugin Is Not a Verified Plugin

KU
Kiril Urbonas
last week • 6 min read•0 views

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

Key takeaways

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

Plugin4Shell is a zero-click remote code execution bug in the plugin marketplaces of Claude Code, OpenAI Codex, GitHub Copilot and Gemini CLI, disclosed by Air Security. The agents checked out the commit a marketplace had pinned, but never confirmed the working tree actually landed on it. Our verdict: a SHA pin that nobody verifies is a label, not a control. Upgrade Claude Code and Codex now, 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, git's own ref resolution does the damage: a branch named after the pinned hash can win over the commit it is meant to stand for, so the checkout succeeds, the pin looks honored, and different code lands on disk. A second variant against Gemini CLI abuses the same missing check on a fetched commit.

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: 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#

The vendors were notified before public disclosure, and the outcomes split unevenly.

Claude Code: fixed in 2.1.179. Codex: fixed in 0.146.0. GitHub Copilot: no patch had shipped as of the disclosure. Gemini CLI: Google deprecated the product and does not plan a fix.

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.bash
#!/usr/bin/env bash
# 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

# 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 as $ ./verify-plugin-pin.sh ./plugins/my-plugin 4f9c2a1e... (full 40-character SHA, never an abbreviation). 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.

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.

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.

Explore topics:AI
React

Get the DevOps Troubleshooting Cheat Sheet

Subscribe and get our free one-page reference for the errors that eat an afternoon — CrashLoopBackOff, OOMKilled, Terraform state locks, and more — plus new guides as we publish them.

Share this post
KU

About Kiril Urbonas

DevOps Engineer

549 articles
View all articles by Kiril Urbonas

You might have missed

Evergreen posts worth revisiting.