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.
| Step | What the developer sees | What 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 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 statusis 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
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
.gitverbatim. 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
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/configbefore opening code you did not clone. Onegrepforfsmonitor,hooksPath,sshCommand,pagerandeditoracross 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 —
gitspawning 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:
- The agent harness — why the half of the system nobody publishes is usually the half that was wrong.
- Sandboxing & execution — what a boundary has to be up before in order to hold.
- Agent supply-chain security — the category this lands in, and how the rest of it is shaped.
- Ambient authority — why the credential the process already holds is the thing that gets used.
- Blast radius — the property you can bound before you know how the attack works.
- Detecting agent compromise — the behavioural signals that survive a variant with a different config key.