AI Blog

Your agent ran git status, and that was enough

Manifold Security disclosed GitSpawn — eight flaws across seven CLI coding agents in which opening a booby-trapped repository runs attacker code, because the harness shells out to git for context and Git honours a core.fsmonitor setting the repository supplied. No prompt, no approval, sometimes before authentication. Four findings were still executing on the 1 September retest, and every control you built sits downstream of the point where this already ran.

By Agentic AI Wiki 13 min read

Every control you built for your coding agent — the approval prompt, the tool allowlist, the sandbox — sits between the model and the world, and GitSpawn never goes near the model. Manifold Security disclosed a class of eight flaws across seven CLI coding agents in which opening a booby-trapped repository is the whole attack: the harness runs git status to gather context, Git honours a core.fsmonitor setting the repository itself supplied, and an attacker's command executes with full user privileges before anyone types a prompt. Four of the eight were still executing repository-supplied commands when the researchers retested on 1 September.

What was disclosed

Git has a performance feature called core.fsmonitor: point it at a helper program and Git runs that program whenever it refreshes the index, so it can skip a full filesystem scan. The setting can live in a repository's own .git/config. Nearly every CLI coding agent examined starts a session by quietly running git status, git diff or similar to work out where it is. Those two facts compose into arbitrary code execution with no exploit involved — the config is read exactly as designed, and the command runs exactly as configured.

StepWhat the developer seesWhat is actually happening
Repository lands on disk A folder from a colleague, a client, a bug report An attacker-authored .git/config arrives with it
Agent opens the directory The tool starting up The harness shells out to git for context
Git refreshes the index Nothing — there is no output core.fsmonitor spawns the attacker's binary
Session begins A prompt, waiting for input Code has already run as the logged-in user
The path the permission system covers, and the path GitSpawn uses Two lanes. The upper lane is the designed path: you type a request, the model proposes a tool call, a permission gate prompts or checks an allowlist, and the command runs inside a sandbox. The lower lane is the GitSpawn path: you open a folder, the harness runs git status to gather context before the model has a turn, git reads core.fsmonitor from the repository's own .git/config, and the attacker's command executes with full user privileges outside the sandbox with no prompt. Every control in the upper lane sits downstream of the point where the lower lane has already executed. Two paths into the same shell The path your controls cover You type a request and press enter The model proposes a tool call Permission gate prompt, allowlist, policy check Sandboxed execution Four opportunities to stop something. All of them downstream of the model. The path GitSpawn uses You open the folder with the agent The harness runs git status to gather context git reads the repo's own .git/config: core.fsmonitor The attacker's command runs No prompt typed. No approval clicked. Full user privileges, outside the sandbox. In some cases before the user has authenticated — so the model never had a turn to be governed. Everything in the upper lane is downstream of everything in the lower lane.
The permission system governs what the model decides to do. This runs before the model has a turn.

The affected list is not a fringe of hobby projects: Claude Code, OpenAI Codex, Cursor, Grok Build, Goose, Hermes Agent and Qwen Code. Seven independent teams, seven independent codebases, one shared blind spot.

The permission prompt was never in the path

Read the agent-security literature of the past year and it converges on a single mental model: the model is the untrusted decision-maker, so put a gate between what it decides and what happens. Approve the tool call. Allowlist the commands. Run the whole thing in a container. Every one of those controls is anchored to a model turn, and it is a good model — for the threat it addresses.

GitSpawn is a reminder that the harness is not neutral plumbing. Before the model sees anything, the harness gathers context: it lists files, reads a config, checks the branch, computes a diff. That work is code your agent runs on your behalf, in your account, on input the attacker controls, and no product in this category treats it as a privileged boundary. The researchers noted that in some cases it fires before the user has even authenticated — which means the process that ran the attacker's command had not yet established who was using it.

  • An approval prompt cannot fire for an action nobody proposed. There was no tool call; there was a subprocess the harness always runs.
  • A command allowlist passes it. git status is on every allowlist ever written, and the dangerous behaviour is inside Git, not in the argument vector.
  • The sandbox is often not up yet. Startup context-gathering frequently runs in the host process, and even where it does not, the container was scoped for the agent's work rather than for arbitrary attacker code.
  • Nothing is logged as suspicious, because nothing suspicious happened by the agent's own accounting. The transcript starts after the damage.

This is the same structural point as the block-log injection channel from a different direction. There, attacker text reached a trusted store and the agent read it as instruction. Here the attacker never needs the model to cooperate at all — the harness is enough. Both are cases where the interesting surface is the plumbing around the loop rather than the loop.

git clone is safe, and that is the trap

Which ways of receiving a repository carry the payload Three columns comparing how a repository reaches a developer's disk. A normal git clone from a forge rebuilds .git/config locally and does not carry the attacker's setting, which is why the class looks unreachable. An archive, shared drive or synced folder copies the .git directory byte for byte and does carry it. A pre-built dev container or restored backup ships a working tree that was prepared elsewhere and carries whatever was in it, while also being the path a developer is least likely to inspect. How the repository reached your disk git clone from a forge Git rebuilds .git/config locally from your own defaults and the remote URL. Carries the payload: no. The setting never travels. Which is why the class reads as unreachable on first inspection. Archive or shared folder A zip, a tarball, a network share, a sync client — each copies .git byte for byte. Carries the payload: yes. Nothing rewrites the config. The ordinary way a bug report, a takehome or a client hands over code. Pre-built container / backup An image or a restore ships a working tree prepared on somebody else's machine. Carries the payload: yes. And is opened unread. The path a developer inspects least and trusts most.
The one delivery path everybody pictures is the one that is clean.

Cloning from GitHub does not carry the payload. Git builds .git/config locally from your own defaults and the remote URL; the source repository's config is not transferred. That single fact is why the first reaction to this disclosure is usually to file it under theoretical — and it is exactly why it is worth reading twice, because the delivery paths that do carry the directory are the unremarkable ones.

  • Archives. A zip or tarball of a working tree copies .git verbatim. This is how a client hands over a codebase, how a takehome exercise arrives, how a repository gets attached to a support ticket.
  • Shared and synced folders. A network share, a sync client, a USB stick, a folder mounted into a VM. None of them rewrite a config.
  • Pre-built development containers and images. The working tree was prepared on somebody else's machine and shipped whole. This is also the path a developer is least likely to inspect and most likely to trust, because inspecting it is the thing the image exists to save you.
  • Restored backups and snapshots. A restore reproduces the config that was in place at capture time, including one placed there by an earlier compromise.

Put those together with how coding agents are actually used and the exposure sharpens. Agents get pointed at unfamiliar code for a living — triage this repository, review this contractor's work, migrate this legacy project. "Open a directory somebody else prepared" is not an edge case for this category. It is the job.

The disclosure record is the more useful artefact

GitSpawn findings by agent and patch status Eight findings across seven CLI coding agents. Goose was fixed and received CVE-2026-72718. The Claude Code core.fsmonitor finding was reported on 26 June and fixed in version 2.1.196 on 29 June. Cursor was fixed. Four findings were still executing repository-supplied commands when retested on 1 September: a second Claude Code configuration path, Grok Build, Qwen Code, and Hermes Agent, the last of which was assigned CVE-2026-71963 independently after six unanswered contact attempts. Eight findings, seven agents, four still open AGENT STATUS IDENTIFIER goose Fixed CVE-2026-72718 Claude Code — fsmonitor path Fixed in 2.1.196, three days after report closed as duplicate Cursor Fixed Claude Code — second path Still executing at the 1 September retest Grok Build Still executing at the 1 September retest Qwen Code Still executing at the 1 September retest Hermes Agent Still executing at the 1 September retest CVE-2026-71963 Hermes Agent's identifier was assigned independently after six unanswered contact attempts.
Half the findings closed quickly. The other half are still open, and the pattern in which half is which is not random.

Goose shipped a fix and took CVE-2026-72718. Claude Code's core.fsmonitor finding was reported on 26 June and fixed in 2.1.196 three days later — the report itself was closed as a duplicate of one filed earlier the same day, which tells you two researchers found the same thing within hours of each other. Cursor fixed its path. That is a functioning disclosure process.

The other half is the story. Four findings were still executing repository-supplied commands when retested on 1 September: Grok Build, Qwen Code, Hermes Agent, and a second configuration path in Claude Code. Hermes Agent's CVE-2026-71963 was assigned independently after six contact attempts went unanswered — a vendor with a shipped agent and no reachable security contact.

Two things are worth taking from that split rather than from the vulnerability itself:

  • A patched vendor is not a patched class. The same agent had one path fixed in three days and another still open two months later. Vendor-level statements — "we fixed GitSpawn" — do not survive contact with a class of eight findings, and neither does a version-pinned mitigation.
  • Security maturity is now a procurement axis for agent tooling. Whether a project has a security contact, an advisory feed and a track record of turning a report into a release is not a nice-to-have for a tool you point at untrusted code with your own credentials. It is the property that determines your exposure window, and it varies enormously across a list of seven products that all look similar in a feature comparison.

What actually helps

The immediate fix belongs to the vendors, and it is not subtle: refuse repository-supplied executable config. Git ships the mechanism — safe.directory gates repositories not owned by the current user, and the agent can force its own configuration scope when it shells out. Every agent in this category should be running its context-gathering with repository-level config for executable settings disabled, and should say so in its documentation.

What you can do without waiting is narrower but real, and it generalises past this particular setting:

  • Treat opening a directory as the privileged action. Not the first tool call — the moment the agent is pointed at a tree. That is where the trust decision actually happens, and no product currently prompts there.
  • Inspect .git/config before opening code you did not clone. One grep for fsmonitor, hooksPath, sshCommand, pager and editor across incoming trees. It is a two-line pre-commit-style check and it covers the current known shapes.
  • Re-clone rather than open in place. If a repository arrived as an archive, clone from the canonical remote and apply the diff. This discards the attacker-controlled config by construction — a workflow change rather than a control.
  • Run unfamiliar repositories in a sandbox that is already up. The value here is not that the sandbox is clever; it is that it exists before the agent process starts, so it bounds startup code the agent's own permission system never sees. Devcontainers help only if the image is yours.
  • Watch for process spawns from the agent's own subprocesses. The behavioural signal — git spawning something that is not a Git subcommand — is cheap to alert on and survives the next variant, which will use a different config key.

The general lesson outlasts core.fsmonitor. Every tool a coding agent shells out to has its own configuration language, and several of those languages are Turing-complete or can name a program to run: build systems, test runners, linters, formatters, package managers, editors. An agent that reads a project's configuration in order to be helpful is executing that project's intent. The threat model that treats the model as the only untrusted component is a model of the wrong system.

FAQ

What is GitSpawn?

A class of eight vulnerabilities across seven CLI coding agents, disclosed by Manifold Security, in which a repository's own core.fsmonitor Git setting causes an attacker-chosen command to run when a coding agent performs a routine background git status. No prompt is typed and no approval is clicked; in some cases it fires before the user has authenticated.

Am I affected if I only clone from GitHub?

Not through that path. git clone builds .git/config locally and does not copy the source repository's version. The exposure comes from repositories that arrive as archives, on shared or synced drives, inside pre-built development containers, or through a restored backup — anything that copies the .git directory rather than reconstructing it.

Does my sandbox protect me?

Partly, and only if it is running before the agent process starts. Startup context-gathering frequently executes in the host process, and where it does run inside a container, that container was scoped for the agent's work rather than for arbitrary attacker code. A sandbox bounds the damage; it does not prevent the execution.

Which agents were fixed?

Goose (CVE-2026-72718), Cursor, and Claude Code's core.fsmonitor path, fixed in 2.1.196 three days after the 26 June report. Four findings — Grok Build, Qwen Code, Hermes Agent (CVE-2026-71963) and a second Claude Code configuration path — were still executing repository-supplied commands when retested on 1 September. Check your own version rather than a vendor statement.

Is this prompt injection?

No, and the distinction matters. Prompt injection persuades a model to do something; this never involves the model. It is arbitrary code execution through a trusted tool the harness invokes on its own initiative, which is why every defence aimed at what the model decides is irrelevant to it.

What is the smallest useful check?

Grep incoming working trees for fsmonitor, hooksPath, sshCommand, pager and editor in .git/config before opening them with an agent. It covers the known shapes, costs nothing, and is scriptable as a pre-open hook in whatever wrapper you already use.

Further reading

On this wiki:

Sources: