Plugin4Shell and GitSpawn: coding agents run attacker code out of git state nobody checked

D. Rose · 13 October 2026 · 13 min

Two disclosures sixteen days apart found one mistake in nine coding agents: the agent acts on git state it never verified, before anyone says yes. Some vendors patched in weeks, AIR says two products have no patch, and Manifold published with four of its eight findings unpatched.

On September 17, 2026, the security vendor AIR Security disclosed Plugin4Shell: with a marketplace plugin already installed, an attacker could run code on machines running Claude Code, OpenAI Codex, GitHub Copilot or Gemini CLI, with no click at all where plugins auto-update. Sixteen days earlier, on September 1, Manifold Security had disclosed GitSpawn: the same class of code execution across seven coding agents, triggered by the background git commands an agent runs to orient itself, on some of them the moment a folder is opened.

The 30-Second Version

  • Plugin4Shell (AIR Security, September 17): an agent checks out the commit a marketplace pinned, the attacker makes the repository's default branch carry that 40-character SHA as its name, and git hands over the branch with a warning nobody reads.
  • GitSpawn (Manifold Security, September 1): a folder's own .git/config can name a program, and the git status an agent runs to orient itself runs it, outside the sandbox, before any approval prompt.
  • The shared mistake: the agent acts on git state it did not write and never verified.
  • Two conditions bound the exposure. GitSpawn needs the folder to arrive as files, since a repository's own .git/config does not travel with a clone, though a nested repository committed under an ordinary folder name does; Plugin4Shell's branch-name variant cannot reach a plugin whose repository is on GitHub, which rejects hash-shaped names.

Part 1: One Mistake, Made Nine Times Over

Claude Code, OpenAI Codex, GitHub Copilot, Gemini CLI, Goose, Nous Research's Hermes Agent, Alibaba's Qwen Code, xAI's Grok Build and Cursor are all agents of this kind. A coding agent has to know where it is, so nearly all of them run git on their own behalf before the model is asked anything: the current commit, the changed files, the branch.

Both disclosures attack those calls: Plugin4Shell the ones at install and update time, GitSpawn the ones at startup, when the agent gathers context about the folder you just opened.

AIR's summary of its half: "Every affected agent checks out the pinned commit but never checks that it actually landed there. That one missing check is the whole bug."

Part 2: The Pinned Commit That Becomes a Branch

A marketplace reviews a plugin at one commit and records that commit's 40-character hexadecimal SHA, so the agent installs that snapshot and nothing else. AIR says Claude Code, Codex and Copilot do it like this:

git clone <plugin repo> ./
git checkout aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

The attacker controls the plugin's upstream repository. They create a branch whose name is the exact SHA the marketplace pinned and make it the default. The clone brings it down as a local branch, so the checkout has to resolve a name that is both a valid branch and a valid commit id. In AIR's words, "when a name is both a valid ref and an object id, git prefers the ref and only prints a refname is ambiguous warning."

Two conditions hold it up, both AIR's: git's own git check-ref-format accepts a 40-hex branch name, and the attacker's branch has to be the default, since a non-default branch arrives only as a remote-tracking reference a plain checkout ignores. In AIR's zero-click sequence the marketplace re-pins to a new benign commit, the attacker names the default branch after that new SHA, and the background auto-update re-runs the checkout. AIR notes that GitHub rejects 40-hex branch names outright while Bitbucket and self-hosted git servers accept them, and that Anthropic's documentation lists both as valid marketplace backends.

Part 3: The Gemini Variant, and the One-Line Fix

Gemini CLI pins with a --ref option and installs in three steps:

git clone --depth 1 <plugin repo> ./
git fetch origin 41d0bc0a4aeb2fbf797dacea39e876d98c95024b
git checkout FETCH_HEAD

The fetch retrieves the right commit and records it in .git/FETCH_HEAD. But git checkout FETCH_HEAD does not have to read that file, because FETCH_HEAD is also a name a branch can have, and the same ref preference decides it. If the repository's default branch is named FETCH_HEAD, then in AIR's words "the fetched commit is silently discarded in favor of attacker-controlled default-branch content." In AIR's account, no host restriction touches this variant.

AIR's fix is one assertion that closes both variants:

test "$(git rev-parse HEAD)" = "<pinned-sha>" || abort

It has to read the resolved HEAD rather than the reference that was requested, the distinction the Gemini variant slips through, and it has to run in the agent. As AIR puts it, "the pin is resolved on the client, so no marketplace can enforce the guarantee it advertises."

Part 4: The Config That Runs Before You Type

Manifold found that the agents' context-gathering git calls did not strip the folder's own configuration first, and several git settings name a command git will run. Manifold's is core.fsmonitor, a real performance feature: instead of checking every file on disk, git asks a helper program, and runs it during an index refresh. Git reads that setting from the repository's own .git/config, so a repository can ship a config that reads, in Manifold's rendering:

[core]
 fsmonitor = <command>

git status and git diff both refresh the index, so which one the agent chose does not matter. "That refresh is the sink," Manifold writes, and: "The repository names a command, git runs it, on the host, with the user's privileges, before any approval prompt."

None of the agents' safety machinery engaged, because the agent's own code spawns the subprocess: "The permission model never sees it." Hermes's CVE says the command runs "exposing the full environment including configured provider API keys."

A second Claude Code finding, triggered by a claude ultrareview command, uses "a different git setting of the same kind," which Manifold left unnamed while it stayed unpatched.

Part 5: How the Malicious Folder Has to Arrive

Git does not carry .git/config over the network. The repository has to arrive on disk as files, with its .git directory inside. Manifold: "Cloning a hostile URL does nothing, and neither does fetch or pull." And: "For every proof of concept in this post, we used a .zip."

So the vector is any path that moves a directory instead of cloning it: a shared archive, a sync folder, a USB stick. Goose's advisory adds "a CI checkout," a job restoring a working directory with its .git intact, and "a nested/auto-discovered repository," which can survive a clone: git will not commit a .git directory, but it will commit a bare repository under an ordinary folder name, and obeys that repository's config when it works inside the folder. GitHub Copilot CLI's CVE-2026-45033 and Eclipse Theia's CVE-2026-19884 both turn on it.

GitSpawn is dangerous inside that precondition because of when it fires. Manifold's lead: "Open a folder with Claude Code and it runs git status before you type anything. Before the workspace-trust prompt. On some agents, before you have even authenticated." The trigger differs by agent: opening a folder for Claude Code and Qwen Code, the first message for Hermes, the first keystroke for Grok Build, a review command for Goose.

Part 6: Found From More Than One Direction

CVE-2026-19592, a separate discovery of the same bug class as GitSpawn, covers a core.fsmonitor code-execution flaw in OpenAI's Codex CLI and Desktop, published on September 1, 2026, the same day as Manifold's post. Manifold does not cite it, and says its own Codex report came back a duplicate. The record credits two finders separately: Fudan University's System Software and Security Lab, and a Doyensec researcher reporting through the Zero Day Initiative. OpenAI merged that fix, pull request #22652, on May 14, 2026, six weeks before Manifold's first report.

Manifold: "Five of our reports came back as duplicates of findings other researchers had already filed independently, one on the same day as ours. This is being found from more than one direction." Someone reported the Claude Code bug earlier on June 26, the day Manifold did, and someone filed a Grok Build report of the same class on July 1, which xAI closed as informative. The same sink already had CVEs in other tools before Manifold published: GitHub Copilot CLI on May 13, the Zed editor on May 28, Hermes WebUI (a separate project from Nous Research's agent) on June 9, Mistral Vibe on August 5 and Eclipse Theia on August 14. The primitive dates to 2021-22: fish patched its git prompt against it as CVE-2022-20001, and Theia's record cites advisory OVE-20210718-0001. AIR arrived on a different code path.

Part 7: Who Patched, and What Each Claim Rests On

AIR says Anthropic confirmed a Plugin4Shell fix in Claude Code 2.1.179 on June 17, 2026, and that Codex's, which AIR verified on August 12, is in 0.146.0. Only the Codex claim has corroboration: the release notes for 0.146.0 list "Verify Git plugin SHA checkouts," and pull request #34644 rejects the source "when the resolved commit does not exactly match the requested SHA." The Claude Code changelog entries for 2.1.179, which AIR cites, and 2.1.196, which Manifold cites, mention neither pinned-SHA verification nor core.fsmonitor, so both rest on researcher testing. AIR assigns Plugin4Shell no CVE.

Status is as of October 9, 2026.

AgentBugFixed inStatus
Claude CodePlugin4Shell2.1.179 (AIR: confirmed June 17)Fixed per AIR; changelog silent
Claude CodeGitSpawn core.fsmonitor2.1.196Fixed per Manifold; changelog silent
Claude CodeGitSpawn ultrareview (live on 2.1.252)—Open at September 1; no changelog entry or advisory names it through 2.1.295 (October 8); 2.1.285 to 2.1.293 add /ultrareview upload refusals
Codex CLIcore.fsmonitor, CVE-2026-19592 (0.102.0 through 0.130.0)0.131.0 (PR #22652, May 14)Fixed; vendor CVE
Codex Desktopsame CVEmacOS 26.519.22136; Windows 26.519.21041; Store 26.519.2081.0Fixed
CodexGitSpawn, Manifold's variant (reported July 20)not statedFixed per Manifold; no vendor artifact
CodexPlugin4Shell0.146.0 (PR #34644; release July 29)Fixed; vendor release notes
GitHub CopilotPlugin4Shell (surface, version not stated)—No fix per AIR; GitHub told The Register it "cannot be exploited on GitHub," and AIR replied that Copilot supports marketplaces on other hosts
Gemini CLIPlugin4Shell, FETCH_HEAD variant—No fix per AIR; installer on main unchanged; stable v0.63.0 on October 6
GooseGitSpawn, CVE-2026-72718 (below 1.44.0)1.44.0 (July 23)Fixed; vendor advisory
Hermes AgentGitSpawn, CVE-2026-71963 (0.18.2 through 0.21.0)0.21.1 (tag v2026.9.7), September 7; PR #101483 merged September 2Fixed; vendor PR; CVE assigned by VulnCheck
Qwen CodeGitSpawn (on 0.19.6, 0.22.3)0.23.4, September 14 (PR #11669)Startup-snapshot sink fixed; the PR itself says the environment-inheritance and team-memory status-probe findings remain open
Grok BuildGitSpawn (on 0.2.93, 1.0.13)unconfirmedOpen on 1.0.13 per Manifold at September 1; source has overridden core.fsmonitor since the August 31 sync (commit bb7f39d); no changelog entry names a fix; no GitHub releases or tags
CursorGitSpawnnot statedFixed per Manifold; no vendor artifact

Codex carries separate fixes for separate bugs, and being on one is not being on the other.

AIR's account of Google: "Google has deprecated the Gemini CLI and will not patch it, so every install stays vulnerable for good." The repository is not archived, and the likely reason releases continue is in Google's own transition post of May 19: Gemini CLI stopped serving consumer tiers on June 18, while Gemini Code Assist Standard and Enterprise customers keep "the latest Gemini models and other updates," and paid API keys keep access. AIR dates Google's no-fix answer to August 4. In August the project merged pull request #28792, which runs git subprocesses in a clean environment "disabling custom pagers, external hooks, and background monitors" but names neither disclosure, and as of October 9 the installer on main still runs git checkout FETCH_HEAD with no check of the resolved HEAD.

Neither firm claims to have seen any of this used in the wild, and CISA's enrichment on the three CVEs in the table reads Exploitation "poc", Automatable "no", Technical Impact "total".

Part 8: What to Change

The Plugin4Shell check has to run again on every update, because the auto-update path is where the zero-click lives. GitSpawn cannot be checked after the fact, since by the time the call returns the attacker's command has run; it closes only by stripping the configuration first; Manifold's minimal example is git -c core.fsmonitor=false status.

Hermes's pull request pins core.fsmonitor, core.hooksPath, the pager, editor and credential helper to inert values through git's configuration-override environment variables, and ignores global and system config. Pinning those was not enough: .gitattributes can assign files a diff driver under a name the attacker picks, and the config defines that driver's program, out of reach of those overrides, so Hermes also inserts --no-ext-diff --no-textconv on diff-rendering subcommands and reports both flags were required, each alone leaving the other path live.

That sanitization has to wrap the context-gathering path, which runs before the tool-permission logic engages. Claude Code ran those calls before its workspace-trust prompt was accepted, in one finding before it was shown, and Goose's ran with no trust prompt at all. A trust decision, where one exists, has to gate the first git call.

One operator control does not depend on knowing which setting is dangerous: run agents against folders you did not clone yourself inside a container or a throwaway account with no keys or tokens on it. Before an agent touches a received directory, read .git/config for any setting that names a program and .gitattributes for a diff driver assigned to a file type.

The Big Misconceptions

“Zero-click means Plugin4Shell can reach anyone.”

AIR's own conditions narrow it: the victim must already have the plugin installed, and the background auto-update must run. AIR names that update as the default in Claude Code and Codex and says nothing about Copilot or Gemini CLI. Codex auto-upgrades every Git marketplace a user configures (pull request #17425), but Claude Code's documentation turns auto-update on by default only for Anthropic's official marketplaces and ones added from claude.ai, and off for every third-party marketplace. The Hacker News found on September 18 that every plugin in the default catalogs for Claude Code and Copilot points to a GitHub repository, where the branch-name variant fails. The population at risk is people who installed a plugin that was benign at the time, on an agent that updates plugins on its own.

“This is a git vulnerability, and the attacker needs to own the marketplace.”

Neither. Git behaves as designed in both variants, and it is not even quiet: both print a refname ... is ambiguous warning, which the agent, running git out of sight, never shows. Both of AIR's entry paths, turning your own benign plugin malicious after it is adopted or hijacking a legitimate author's plugin, leave the marketplace doing its job, reviewing code and pinning a commit.

If You Remember Only Five Things

  1. The two fixes sit at opposite ends of the same call.
  2. A guarantee advertised by one party and enforceable only by another is not a guarantee.
  3. At least eight products carry a published CVE for this git-config sink, May to September.
  4. The dangerous moment is after acquisition.
  5. One control survives a setting nobody has named: a throwaway container with no keys.

Cheat sheet

TermMeaning
SHA pinningRecording the 40-character git fingerprint of a reviewed commit so an agent installs that snapshot only.
Plugin4ShellAIR's name for the flaw where an agent checks out a pinned commit but never verifies it landed, the attacker having named the default branch after that SHA (or FETCH_HEAD) to win the resolution.
GitSpawnManifold's name for the flaw where an untrusted folder's own .git/config runs a command when an agent's background git call refreshes the index.
core.fsmonitorA git setting naming a helper program git runs during an index refresh; the named sink in GitSpawn.

Sources & Further Reading