---
title: 'Plugin4Shell: Patch and Harden Your Coding Agent (2026)'
description: Plugin4Shell breaks SHA pinning in four AI coding agents. Two are patched. Here's how to check your version, audit your marketplaces, and lock down the rest.
url: https://nerdleveltech.com/plugin4shell-ai-coding-agent-plugin-security
language: en
date: '2026-09-21'
category: security
tags:
  - plugin4shell
  - claude code
  - github copilot
  - openai codex
  - gemini cli
  - ai agent security
  - supply chain security
markdown_url: https://nerdleveltech.com/plugin4shell-ai-coding-agent-plugin-security.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/plugin4shell-ai-coding-agent-plugin-security'
terms: https://nerdleveltech.com/llms.txt
---

# Plugin4Shell: Patch and Harden Your Coding Agent (2026)

Plugin4Shell is a zero-click bug in how Claude Code, OpenAI Codex, GitHub Copilot CLI, and the Gemini CLI install plugins: all four check out a plugin at a pinned commit SHA but never verify the checkout landed there, letting a repository owner serve different code under an intact-looking pin[^1]. Claude Code and Codex have shipped fixes; Copilot CLI and Gemini CLI have not. This post covers what changed, what didn't, and the exact settings that reduce your exposure on all four.

## TL;DR

Security researchers at AIR disclosed Plugin4Shell on September 17, 2026: a SHA-pinning bypass affecting Claude Code, Codex, GitHub Copilot CLI, and the Gemini CLI, found in May and privately reported to all four vendors in June[^1]. Anthropic patched Claude Code in version 2.1.179 (released June 16, fix confirmed June 17)[^1][^4]. OpenAI patched Codex in version 0.146.0 (released July 29, fix verified August 12), and the fix is a named commit in the public changelog: "Verify Git plugin SHA checkouts"[^5]. Microsoft has not shipped a client-side fix for Copilot CLI, though GitHub separately argues its own hosting already blocks the main attack path; Google has declined to patch the deprecated Gemini CLI at all and is pointing users to Antigravity instead[^1][^3]. No CVE had been assigned as of this writing[^2]. The bug requires no user interaction because it rides the same background auto-update that keeps plugins current — the update path, not the install path, is what makes it zero-click[^1].

## What you'll learn

- What Plugin4Shell actually breaks, without a working exploit — the missing verification step, not the pinning mechanism itself
- Which agent versions are patched, which aren't, and how to check your installed version right now
- Why GitHub and GitLab structurally block one variant of the bug and Bitbucket and self-hosted git servers don't
- How to lock Claude Code to an allowlist of trusted plugin marketplaces with `strictKnownMarketplaces`
- How to pin GitHub Copilot CLI plugins by commit SHA and turn off unattended plugin auto-update
- A verification script that checks your installed agent versions against the patched thresholds

## What happened, and when

AIR's research team — Or Nevo, Dor Granat, and Niv Hoffman — found the bug in May 2026 with working proof-of-concept exploits against all four agents, and disclosed it to Anthropic, OpenAI, Google, and Microsoft under coordinated disclosure the following month[^1]. That timeline matters: by the time AIR published its public write-up on September 17, Claude Code had already been carrying a silent fix for three months, and Codex for about seven weeks. If you've been running an auto-updated, current install of either agent since mid-summer, you were very likely already protected before this story broke[^1].

| Date | Event |
|---|---|
| May 2026 | AIR finds the bug, builds working PoCs against all four agents |
| June 2026 | Disclosed to Anthropic, OpenAI, Google, and Microsoft |
| 2026-06-17 | Anthropic confirms the fix, shipped in Claude Code 2.1.179 |
| 2026-08-04 | Google confirms no fix will ship; Gemini CLI is deprecated |
| 2026-08-12 | AIR verifies Codex 0.146.0 as fixed |
| 2026-09-17 | AIR publishes the public disclosure; GitHub Copilot CLI still has no fix |

Source: AIR's disclosure timeline[^1].

## How the bypass works, at the level that matters for defense

All four agents let you extend them with plugins pulled from a marketplace — a git repository containing a manifest that lists available plugins and, for each one, a commit SHA the marketplace maintainer reviewed and pinned. The promise of pinning is that once a plugin passes review, it can't change under you without someone noticing.

AIR's technical write-up states the core defect plainly: every agent "checks out the pinned commit but never checks that it actually landed there"[^1]. Concretely, an attacker who controls the plugin's upstream repository — either by publishing a plugin that starts out genuinely benign and turning it malicious later, or by taking over the repository behind a plugin other people already trust — can create a ref on that repository that collides with the pin. For Claude Code, Codex, and GitHub Copilot CLI, that collision is a branch literally named after the 40-character pinned SHA, set as the repository's default branch. For the Gemini CLI, which pins with `git fetch` and then runs `git checkout FETCH_HEAD`, the collision is a default branch literally named `FETCH_HEAD`[^1]. In both cases, the working tree an agent ends up running is not guaranteed to be the commit its manifest says it reviewed, and the agent reports a successful install at the pinned SHA regardless.

This site isn't going to walk through the exact git resolution steps that make the swap happen — that's the part of AIR's write-up that functions as exploit detail, and independent analysts have already flagged that even AIR's own phrasing of the git-internals mechanics is being debated in follow-on technical coverage[^8]. What's uncontested across every source is the practical shape of it: the pin gets checked once at install, the same checkout logic re-runs on unattended background auto-update — the default in Claude Code and Codex — and nothing after that checkout compares the commit that actually landed against the SHA that was supposed to land[^1][^2]. That's what makes it zero-click: a plugin you already installed, from a marketplace you already trust, can be swapped out from under you with no prompt and nothing to notice[^1].

## Who's patched

| Agent | Status | Patched version | Source |
|---|---|---|---|
| Claude Code | Patched | 2.1.179 (June 16, 2026) | Confirmed via GitHub release page[^4] |
| OpenAI Codex | Patched | 0.146.0 (July 29, 2026) | Fix is PR #34644, "Verify Git plugin SHA checkouts"[^5] |
| GitHub Copilot CLI | No client fix | — | GitHub says its own hosting mitigates the main variant[^3] |
| Gemini CLI | Will not be patched | — | Google has deprecated the product[^1] |

Check what you're actually running before trusting this table — patch numbers only help if you're past them:

```bash
claude --version    # need 2.1.179 or later
codex --version     # need 0.146.0 (rust-v0.146.0) or later
copilot --version   # no patched version exists yet — see mitigations below
```

A small script to check all three at once and flag anything below the patched threshold:

```bash
#!/usr/bin/env bash
# plugin4shell-version-check.sh — flags coding agents below Plugin4Shell's patched versions.
set -euo pipefail

version_lt() { [ "$1" != "$(printf '%s\n%s' "$1" "$2" | sort -V | tail -n1)" ]; }

check() {
  local name="$1" cmd="$2" min="$3" ver
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "  $name: not installed, skipping"
    return
  fi
  ver=$("$cmd" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1 || echo "unknown")
  if [ "$ver" = "unknown" ]; then
    echo "  $name: could not parse version from '$cmd --version'"
  elif version_lt "$ver" "$min"; then
    echo "  $name: $ver — BELOW patched version $min, update now"
  else
    echo "  $name: $ver — at or above patched version $min"
  fi
}

echo "Plugin4Shell version check ($(date -u +%Y-%m-%d)):"
check "Claude Code" claude 2.1.179
check "Codex"       codex  0.146.0
echo "  Copilot CLI: no patched version exists as of 2026-09 — pin plugins by SHA and disable auto-update (see below)"
echo "  Gemini CLI: will not be patched — plan a migration to Antigravity"
```

This only checks version numbers. It doesn't touch your marketplaces, doesn't check any plugin's actual checked-out commit, and makes no network calls — it's safe to run as-is.

## The host mitigation that may already cover you — and the one that doesn't

Because the check that's missing runs inside the agent, no marketplace can fully close the gap on its own — but one variant of the bug depends on being able to name a branch after a 40-character hex string, and two major git hosts refuse to let you do that. GitHub's own documentation states it directly: it restricts branch and tag names "which look like Git object IDs (40 characters containing only 0-9 and A-F)," specifically to prevent confusion with actual commit references[^7]. GitLab's branch documentation carries the identical rule — "branch names with 40 hexadecimal characters are prohibited"[^6] — because they resemble Git commit hashes, enforced as a push-side check. Independent coverage published after AIR's own disclosure has pointed out that GitLab gets grouped with the unprotected hosts in some early write-ups, which is incorrect — the same protection exists on both[^8].

That leaves two categories exposed to the branch-name variant: Bitbucket, which AIR confirmed permits SHA-shaped branch names, and self-hosted git servers — anything from a bare repo behind SSH to Bitbucket Data Center or Gitea — which permit it unless someone has specifically configured a rejection rule[^1][^8]. Anthropic's own documentation explicitly lists Bitbucket and self-hosted git as supported marketplace backends[^1]. GitHub's Copilot CLI marketplace documentation is less specific — it describes marketplaces as installable from GitHub.com, "any other online Git hosting service," or a local file system, without naming Bitbucket or self-hosted git directly, though the generic language covers them the same way[^8]. Either way, that's precisely why an enterprise-run internal marketplace is the realistic exposure, not the public catalogs. And the host-level mitigation does nothing at all for the Gemini CLI's separate `FETCH_HEAD` variant, which doesn't depend on a hash-shaped name in the first place[^1].

The practical takeaway: if every plugin marketplace your team has added points at github.com or gitlab.com, you're not exposed to the branch-name variant regardless of which agent version you run — but you should still update, because that host-side protection isn't something your agent configuration controls, and it moves the moment a marketplace does.

## Audit your marketplaces before you trust any of them

Do this before changing any settings, since it tells you whether you have anything to fix beyond updating:

1. **List every marketplace you've registered.** For Claude Code, check `extraKnownMarketplaces` in every managed, project, and user settings file you have. For Copilot CLI, run `copilot plugin marketplace list`[^9].
2. **Sort the results by git host.** Anything on github.com or gitlab.com is covered by the host-side block described above. Anything else — an internal GitLab-alternative, a bare repo, Bitbucket — is your actual exposed set. If you have one, it's likely the internal marketplace nobody thought to flag as a security boundary, precisely because it was never on a public catalog.
3. **Spot-check one pin by hand.** In an installed plugin's local checkout, run `git rev-parse HEAD` and compare the output to the SHA declared in the marketplace's manifest. This is the exact comparison the agents skip — doing it once tells you whether the pin in your environment was ever actually enforced.

## Lock Claude Code to an allowlist with `strictKnownMarketplaces`

Claude Code supports a managed-settings allowlist for plugin marketplaces, and its enforcement point lines up with this bug: the check runs "before any network or filesystem operation," on marketplace add and on every plugin install, update, refresh, and auto-update[^10]. An organization can restrict marketplaces to specific repositories, entire GitHub owners, or any host matching a regex:

```json
{
  "strictKnownMarketplaces": [
    { "source": "github", "repo": "anthropics/claude-plugins-official" },
    { "source": "github", "repo": "acme-corp/*" },
    { "source": "hostPattern", "hostPattern": "^gitlab\\.example\\.com$" }
  ]
}
```

The `github` source with an `owner/*` wildcard matches every repository under that owner; `hostPattern` matches by regex against the marketplace's host, which is the entry to use if your organization's git host isn't GitHub — useful specifically because it lets you allowlist "our GitLab instance" without listing every repo on it[^10]. The inverse control, `blockedMarketplaces`, supports the same `owner/*` wildcard as of Claude Code v2.1.223[^10]. Setting `strictKnownMarketplaces` to an empty array blocks every marketplace, including Anthropic's own — useful as a starting point you then open up deliberately, not as a permanent setting.

Because this lives in managed settings, individual developers and project-level configuration can't override it[^10]. Pair it with `extraKnownMarketplaces` in the same file to pre-register the marketplaces the allowlist permits, since the allowlist alone doesn't add anything for users automatically.

Background auto-update itself defaults differently depending on the marketplace's origin: Anthropic's official marketplace, most other Anthropic-run catalogs, and marketplaces added from claude.ai have it on by default, while third-party and local development marketplaces default to auto-update off — until a user turns it on for that marketplace, or an administrator sets `"autoUpdate": true` on its `extraKnownMarketplaces` entry in managed settings[^11]. That default cuts in your favor for an internal marketplace nobody has opted into auto-update for; it stops cutting in your favor the moment someone flips that switch for convenience, which is exactly what the setting exists to let them do.

## Pin and lock down GitHub Copilot CLI

Copilot CLI has no version-level fix yet, so the available controls are pinning and update behavior rather than a version bump. Both `github` and `url`-sourced plugin entries accept a `sha` field, documented directly as the way to get "reproducible installs that are immune to force-pushes or tag/branch moves" — exactly the guarantee Plugin4Shell defeats when it isn't paired with a host-side block[^9]:

```json
{
  "source": {
    "source": "github",
    "repo": "owner/repo",
    "sha": "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
    "path": "plugins/my-plugin"
  }
}
```

For auto-update: first-party plugins from Copilot's built-in marketplaces update automatically at the start of each session in a trusted working directory, and any custom marketplace you've added can opt into that same behavior by setting `autoUpdate: true` on its `extraKnownMarketplaces` entry — which also means it's opt-out, not opt-in, for anything you've deliberately enabled. Set `autoUpdate: false` in your Copilot CLI configuration, or export `COPILOT_AUTO_UPDATE=false`, to turn an unattended swap back into a command someone has to run and notice.

## If you're on the Gemini CLI

Google's position is unambiguous on the fix: none is coming, because the standalone Gemini CLI is deprecated in favor of Antigravity[^1]. What Google's own transition notice does not say is anything about security maintenance for the accounts it describes as still supported — organizations on a Gemini Code Assist Standard or Enterprise license keep CLI access and continue receiving model updates past the individual-user cutoff, with no separate statement about whether that includes a fix for this specific bug. If your team holds that license, the open question worth putting to your account team in writing is whether "supported" extends to this vulnerability specifically, not just to model access. Migrating to Antigravity is the vendor-recommended path and it does remove this specific bug's attack surface, since Antigravity has no marketplace plugin SHA-pinning mechanism to bypass[^1] — but treat it as a genuine migration with its own review, not a same-day swap.

## Defense in depth: constrain what a plugin can reach

Pinning and allowlisting reduce which code runs; they don't limit what that code can do once it's running. Claude Code's sandbox supports a `network.allowedDomains` allowlist that restricts outbound connections regardless of which plugin issued them, and Codex's `sandbox_workspace_write.network_access` setting is disabled by default, requiring an explicit opt-in before a sandboxed command gets any network access at all. Neither setting is specific to this bug, and neither is a substitute for the marketplace-level controls above — but both mean that even a plugin that did get swapped has less to reach.

## Verification

Every version number and configuration field in this post was checked against a primary source rather than secondhand summaries. Claude Code's patched version and release date came from Anthropic's own GitHub release page, fetched directly[^4]. Codex's patched version, release date, and the specific fixing commit came from OpenAI's own GitHub release page for `rust-v0.146.0`, which lists "Verify Git plugin SHA checkouts" by name in its changelog[^5]. The GitLab branch-naming restriction was confirmed by fetching GitLab's own documentation page directly, not by trusting secondary reporting of it[^6]. Claude Code's `strictKnownMarketplaces` syntax, enforcement points, and the `owner/*` wildcard's version requirement were confirmed by fetching Anthropic's plugin-marketplaces documentation directly and reading the configuration examples in full[^10]. GitHub Copilot CLI's `sha` field, its `autoUpdate`/`COPILOT_AUTO_UPDATE` behavior, and the `copilot plugin marketplace list` command were confirmed against GitHub's own CLI reference docs[^9]. Claude Code's marketplace-dependent auto-update defaults were confirmed directly against Anthropic's own documentation[^11]. This post was revised on 2026-09-22 after a second verification pass: one footnote had cited the wrong source for the no-CVE-assigned claim (corrected to the source that actually states it), and the auto-update-defaults paragraph above was added after direct source review turned up that nuance. No exploit or proof-of-concept was built, run, or is included anywhere in this post — the version-check script above only reads locally installed version strings and makes no network requests or git operations.

## FAQ

**What is Plugin4Shell?**
A zero-click vulnerability disclosed by AIR Security on September 17, 2026, affecting Claude Code, OpenAI Codex, GitHub Copilot CLI, and the Gemini CLI. All four check out a plugin at a pinned commit SHA but never verify the checkout landed on that exact commit, letting a repository owner serve different code under an intact-looking pin[^1].

**Do I need to do anything if my plugins only come from GitHub or GitLab?**
The branch-name variant can't be staged on either host, since both reject 40-character hex branch and tag names[^6][^7]. You should still update Claude Code and Codex to their patched versions, since the host-level block isn't a setting you control and doesn't apply if a marketplace ever moves.

**Has a CVE been assigned?**
Not as of this writing[^2].

**Is there a public proof-of-concept exploit?**
AIR built working PoCs against all four agents as part of their research, but has not published exploit code, and this post does not include or link to one[^1].

**What's the single highest-value fix if I can only do one thing?**
Update Claude Code to 2.1.179+ and Codex to 0.146.0+ — that alone closes the bug entirely for those two agents, independent of which git host your marketplaces use[^4][^5].

**Should I stop using GitHub Copilot CLI or the Gemini CLI until they're fixed?**
That's a risk call for your own environment, not a blanket answer. For Copilot CLI, pinning by `sha` and disabling `autoUpdate` meaningfully narrows the window. For the Gemini CLI, no fix is coming at all, and Google's own advice is to migrate to Antigravity[^1].

## Bottom line

Plugin4Shell is a missing assertion, not a broken cryptographic primitive: every affected agent checks out a commit and trusts that the working tree matches it, without ever confirming that afterward. Two vendors closed that gap in their own code months before the public disclosure landed; two have not, for different reasons — one citing a host-side mitigation that only covers part of the bug, one because the product itself is being retired. None of that changes what you can control this week: check your installed versions against the table above, inventory which git hosts your plugin marketplaces actually live on, and turn the allowlisting and pinning controls this post walks through from documented-but-unused into configured. The pin was only ever as strong as the thing verifying it, and for months, the thing verifying it was a naming rule on someone else's git host.

## Related reads

- [AI Agent Visibility Gap: Snyk's 2026 Numbers, Checked](https://nerdleveltech.com/ai-agent-visibility-gap-snyk-agentic-adoption)
- [AI Agent Containment: What Four 2026 Incidents Show](https://nerdleveltech.com/ai-agent-containment-evaluation-incidents)
- [Claude Managed Agents: ant apply Tutorial (2026)](https://nerdleveltech.com/claude-managed-agents-ant-apply-tutorial)
- [AI SDK Tool Drift Detection: What It Misses in 2026](https://nerdleveltech.com/ai-sdk-tool-drift-detection-mcp-rug-pull) — the same swap-after-trust problem one layer down, at the MCP tool definition
- [Claude Code Mod to Block .env Reads: Tested (2026)](https://nerdleveltech.com/claude-code-mod-block-env-secrets-tested) — a `tool.call` mod guard for secret files, with its measured bypasses and the docs' warning that mods are not sandboxed.
- [Claude Code Mods: 10 Tested Use Cases (2026)](https://nerdleveltech.com/claude-code-mods-use-cases-examples): 10 working mods with tests, plus what a mod can reach on your machine and how to check one before you install it.

[^1]: AIR Security, "Plugin4Shell – Zero Click RCE Vulnerability found in top 4 most popular coding agents" — https://www.air.security/blog-posts/plugin4shell (by Or Nevo, Dor Granat, Niv Hoffman; published 2026-09-17; primary disclosure including technical mechanism, affected agents, mitigation status, and full timeline table; fetched 2026-09-21)
[^2]: The Hacker News, "Plugin4Shell Lets Repository Owners Swap Pinned Plugin Code Across Four AI Coding Agents" — https://thehackernews.com/2026/09/plugin4shell-lets-repository-owners.html (published 2026-09-18, Swati Khandelwal; independent verification that no CVE identifier had been assigned and no vendor had published a security advisory as of September 18; fetched 2026-09-22)
[^3]: InfoWorld, "A zero-click RCE flaw in AI coding agents could have exposed enterprise systems" — https://www.infoworld.com/article/4223907/a-zero-click-rce-flaw-in-ai-coding-agents-could-have-exposed-enterprise-systems.html (published 2026-09-18, Anirban Ghoshal; reports GitHub's statement to The Register that its branch-naming restriction blocks the reported vulnerability on GitHub; fetched 2026-09-21)
[^4]: GitHub, "Release v2.1.179 · anthropics/claude-code" — https://github.com/anthropics/claude-code/releases/tag/v2.1.179 (released 2026-06-16 by ashwin-ant; primary source for Claude Code's patched version and release date; fetched 2026-09-21)
[^5]: GitHub, "Release 0.146.0 · openai/codex" — https://github.com/openai/codex/releases/tag/rust-v0.146.0 (released 2026-07-29; changelog lists PR #34644, "Verify Git plugin SHA checkouts"; primary source for Codex's patched version, release date, and fixing commit; fetched 2026-09-21)
[^6]: GitLab Docs, "Branches" — https://docs.gitlab.com/user/project/repository/branches/ ("Branch names with 40 hexadecimal characters are prohibited, because they are similar to Git commit hashes"; fetched directly 2026-09-21)
[^7]: GitHub Docs, "Dealing with special characters in branch and tag names" — https://docs.github.com/en/get-started/using-git/dealing-with-special-characters-in-branch-and-tag-names (states GitHub restricts branch/tag names "which look like Git object IDs (40 characters containing only 0-9 and A-F)"; fetched directly 2026-09-22, confirmed against the doc's raw source)
[^8]: THE D*AI*LY BRIEF (beri.net), "The Pinned Commit Was a Branch. Check Your Git Host." — https://www.beri.net/article/plugin4shell-sha-pin-bypass-coding-agent-plugin-git-host-inventory (by Rajesh Beri, published 2026-09-20; independent follow-on analysis correcting the GitLab-as-unprotected claim in earlier coverage and providing the host-inventory framing; fetched 2026-09-21)
[^9]: GitHub Docs, "GitHub Copilot CLI plugin reference" — https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-plugin-reference (documents the `sha` pinning field, `copilot plugin marketplace list` command, and `--json` output shape; fetched directly 2026-09-21)
[^10]: Claude Code Docs, "Plugin marketplaces" — https://code.claude.com/docs/en/plugin-marketplaces (documents `strictKnownMarketplaces`, `blockedMarketplaces`, the `owner/*` wildcard's v2.1.223 requirement, `hostPattern`/`pathPattern` sources, and the background auto-update mechanism; fetched directly 2026-09-21)
[^11]: Claude Code Docs, "Discover and install prebuilt plugins through marketplaces" — https://code.claude.com/docs/en/discover-plugins (documents that the official Anthropic marketplace, most other Anthropic-run catalogs, and marketplaces added from claude.ai have auto-update enabled by default, while third-party and local development marketplaces default to disabled, and that administrators can override this per marketplace via `autoUpdate: true` on an `extraKnownMarketplaces` entry; fetched directly 2026-09-22)
