A Git Config Key Ran Code in Seven Coding Agents. The 2022 Fix Does Not Stop It.

grith team··14 min read·security
grith is live

A security proxy for AI coding agents, enforced at the OS level. Install grith and put a real boundary around your agent.

One hostile line in a delivered .git/config, run three ways on git 2.43.0. Stock git status: payload fired. With the 2022 safe.bareRepository mitigation: payload fired. With core.fsmonitor blanked, which is what Claude Code does on every call: blocked. One of three configurations stopped the attack, and it is not the fix everyone cites.
The same payload, three configurations. The widely-cited 2022 mitigation does not cover the delivery route that reaches AI coding agents.

On 1 September 2026, Francisco Rosales of Manifold Security published GitSpawn: eight code-execution findings across seven AI coding agents, all through the same primitive.1 Claude Code, Codex, Qwen Code, Goose, Grok Build and Hermes were all affected. Four of the eight paths were still unpatched when the research went out.

The primitive is a git configuration key called core.fsmonitor. Its value is a shell command. Git runs it during a routine index refresh, which is to say during git status, or git diff, or any of the dozen operations an agent performs to work out where it is. The command executes as the developer, as a child of git rather than of the agent.

Before going further, the constraint that decides whether this matters to you. Cloning a hostile repository is not enough. Rosales is explicit about it: "Cloning a hostile URL does nothing, and neither does fetch or pull. The repository has to arrive as files with its .git directory already inside, so the vector is anything that moves a directory instead of cloning it: a shared .zip, a shared drive, a sync folder, a USB stick."1

That is a real constraint and it narrows the attack considerably. It does not eliminate it. Developers receive directories constantly, and increasingly they hand those directories to an agent and walk away.

The part that is not new

The interesting thing about GitSpawn is not that the primitive works. It is that we have known it works for four years.

Justin Steven published the core.fsmonitor abuse in 2022, in an advisory on buried bare repositories.2 He described the mechanism, demonstrated arbitrary code execution, and stated the same delivery constraint Rosales restated in 2026: "this type of issue is not exploitable via a repo delivered by git clone, because cloning a repo does not allow the remote to sufficiently control files within the client's .git/ directory."2 He also recorded git's position, which has not moved: these are features, not bugs, and "Git currently has no plans to change the behaviour regarding potentially dangerous configuration directives in a repo's .git/config file."

Then, on 3 October 2022, git 2.38.0 shipped a mitigation.3 Setting safe.bareRepository to explicit makes git refuse to operate on bare repositories it finds embedded in a working tree. Steven's advisory tells readers, in bold, to strongly consider setting it, and it is the setting most of the commentary reaches for.

It is opt-in, and four years on it is still opt-in. It is also, for the vector that hits AI coding agents, the wrong fix. That is worth demonstrating rather than asserting.

Three configurations, one payload

I built the smallest honest reproduction I could: a directory that arrives with its own .git inside, exactly the delivery Rosales describes, carrying core.fsmonitor = touch /tmp/fsmonitor-poc-fired; echo "". Then I ran git status three ways on git 2.43.0 and checked for the marker file.4

$ git status --porcelain                             # stock git
  PAYLOAD FIRED

$ git -c safe.bareRepository=explicit status --porcelain
  PAYLOAD FIRED                                      # the 2022 mitigation

$ git -c core.fsmonitor= status --porcelain
  BLOCKED

The 2022 mitigation does not stop it. That is not a bug in the mitigation. safe.bareRepository defends against bare repositories buried inside a working tree, which is one delivery route for the class. It has nothing to say about a perfectly ordinary repository that simply arrives with a hostile config already in it, and that is the route that matters when the thing opening the directory is an agent.

For completeness I confirmed the mitigation does work where it is supposed to. With the payload in a buried bare repo, git refuses outright:

fatal: cannot use bare repository '/tmp/.../buried.git'
       (safe.bareRepository is 'explicit')

Two variants, two defences, and the widely-cited setting covers the one that is not the problem here.

What is actually set on a working machine

I checked my own development machine on 9 September 2026. It runs Ubuntu, git 2.43.0, and both Claude Code and Codex daily.

$ git --version
git version 2.43.0
$ git config --system --get safe.bareRepository; \
  git config --global --get safe.bareRepository; \
  git config --local  --get safe.bareRepository
(nothing)

Unset at every scope. That is the stock condition, not a misconfiguration. Debian and Ubuntu ship git without it, and nothing in a normal setup turns it on.

So the question becomes whether the agent defends itself, since the developer's git plainly is not going to. That is checkable in about thirty seconds per tool, and the third line of my test above is the one to watch: blanking core.fsmonitor at invocation time is what actually works.

ToolVersionsafe.bareRepositorycore.fsmonitor
Claude Code2.1.263, 2.1.265, 2.1.266present, set to explicitpresent, blanked
Codex CLI0.153.4absentabsent
git default2.43.0unsethonoured from repo config

Anyone can reproduce this. strings the agent binary, grep for the two keys, and compare against git config --get. Claude Code carries the literal strings safe.bareRepository=explicit and core.fsmonitor=, alongside -c core.hooksPath=/dev/null. That is a recognisable pattern: it hardens its own git invocations with -c overrides rather than trusting ambient configuration.

Codex contains no reference to either key, and it would be easy and wrong to read that as Codex being undefended. Codex CLI was affected, and OpenAI fixed it. CVE-2026-19592 covers 0.102.0 through 0.130.0, patched in 0.131.0, with companion advisories for Codex Desktop on macOS and Windows.5 OpenAI's own note on it is precise about why the class is nasty: "The helper runs outside Codex's command sandbox and without a user-approval prompt, allowing attacker-controlled code to run with the user's privileges."

So the two vendors did not disagree about the severity. They disagreed about the altitude of the fix. OpenAI closed the path. Anthropic, by the versions I can measure, blanks the dangerous key on every git call it makes, which my third test shows is the thing that actually stops this vector. It also carries safe.bareRepository=explicit, covering the burial variant as well.

That is the correct pair, and as far as I can tell nobody has said so out loud. The advisories name versions. The commentary names the 2022 setting. Neither names the one-line invocation flag that a developer, or any other tool that shells out to git, could adopt this afternoon.

One honest limit on that table. I can only check versions present on my disk, and the oldest I have is 2.1.263. Rosales reported the second Claude Code path live in 2.1.252, so the mitigation landed somewhere in the eleven releases between. I cannot pin which, and I am not claiming Anthropic shipped it in response to this specific report.

The disclosure record

The vendor responses are where this stops being a git story.

AgentReportedVendor responseStatusUnder grith
Claude Code (core.fsmonitor)26 June 2026duplicate of a same-day reportpatched in 2.1.196caught
Codex CLIdisclosed 1 Sep 2026three CVEs published by OpenAIpatched in 0.131.0, CVE-2026-19592caught
Qwen Code7 July 2026accepted by Alibaba SRCunpatched at 0.22.3caught
Goose13 July 2026acknowledgedpatched in 1.44.0, CVE-2026-72718caught
Grok Build14 July 2026closed as duplicate of an earlier reportunpatched at 1.0.13caught
Claude Code (ultrareview)15 July 2026duplicate of an internal ticketunpatched at 2.1.252caught
Hermes20 July 2026no triageunpatched at 0.21.0, CVE-2026-71963caught

Sources: Manifold's published table1 and OpenAI's advisories as summarised by The Hacker News.5 The last column is the same in every row on purpose. It is not a property of the vendor, and the reproduction behind it is at the end of this post.

Three of those rows say "duplicate". That word is doing a lot of work. A report closed as a duplicate is a report that was read, matched against something already known, and then not fixed. Grok Build was closed against an earlier report and remained unpatched. The second Claude Code path was closed against an internal ticket in July and Rosales still found it exploitable in September.

Hermes received no triage at all, and its CVE was assigned without the vendor.

Anthropic and OpenAI both come out of this well, and it is worth saying plainly. Anthropic went three days from report to patch on the first path, and by the versions I can measure it now sets the 2022 mitigation rather than patching call sites one at a time. OpenAI shipped fixes across three products with CVEs attached and a candid description of why its own sandbox did not help. That is what the good version looks like, and it makes the three "duplicate" rows above harder to excuse rather than easier.

Why the existing controls miss it

Approval prompts. The agent asks before running a command it recognises as risky. It ran git status. Nothing about that is risky, and the payload is not in the command.

Sandboxes and allowlists. The payload does not execute as the agent. It executes as a child of git, which the agent legitimately invoked. Controls scoped to the agent's own process do not see it, and controls that allowlist git have just allowlisted the delivery vehicle.

Repository trust prompts. VS Code learned this lesson in 2021 and now requires workspace trust before running git.2 A terminal agent has no equivalent. There is no moment at which anyone was asked whether this directory is trustworthy.

Scanners. There is no malware. There is a configuration file with a valid key and a string value, which is what configuration files contain.

What does see it

Every control above lives at or above the agent. Here is one that lives below it.

grith supervises an agent at the operating-system boundary. It installs a seccomp-BPF pre-filter and a ptrace supervisor over the agent's whole process tree, so every descendant inherits the supervision: the agent, the shell it spawns, the git it runs, and the command git runs out of core.fsmonitor. The escape that makes this payload invisible to the agent - it executes as a grandchild rather than as the agent - is the same thing that keeps it visible to grith. Nothing gets below the syscall.

I reproduced the attack under a live grith session and read the audit log.6 The delivered repo carried core.fsmonitor = cat ~/.ssh/<key> > exfil, a stand-in for the real payload's credential theft, pointed at a decoy key. A bare git status fired it. grith scored four syscalls the payload made, on its own, with no involvement from the agent that triggered the git status:

What the payload attemptedgrith verdict
create a file under ~/.sshqueue - frozen for review
chmod that filedeny - EPERM returned
read the private keyqueue - frozen for review
delete the evidence afterwardsdeny - EPERM returned

The two modifications were denied outright, with EPERM injected into the syscall return and no prompt. The read and write of ~/.ssh were queued, which in grith means the process freezes and a human is asked before the syscall proceeds, with the operation named: a git status wants to read your SSH private key. In this run I approved the read deliberately, to confirm the bytes would flow and that the verdict was real rather than cosmetic. The point of the test was the verdict, not the block. In a non-interactive or CI session, a queued operation is denied by default rather than waiting for an answer that never comes.

None of this depended on which agent invoked git. grith sits under the process tree, not inside any one tool, so the same verdicts hold whether the git call came from Claude Code, Codex, or any other row in the table above. That is why the last column of the disclosure record does not vary while the vendor column ranges from "fixed in three days" to "never triaged".

The gap this sits in

The pattern worth taking from GitSpawn is not that seven vendors shipped the same bug. It is that a four-year-old problem was rediscovered by an industry that did not have the four-year-old context, and then partially answered with a four-year-old mitigation that does not cover the new delivery route.

Rosales does not cite Steven's advisory and does not mention safe.bareRepository at all. Neither did any of the coverage I read. That is not a criticism of the research, which is careful, well documented and correctly disclosed. It is the observation that the security knowledge and the tooling grew up in different rooms, and the join between them is where this keeps happening.

There is a structural point underneath, and it is the same one that shows up in MCP tool poisoning and in agent skill registries. Untrusted input reaches a tool, the tool acts on it, and the thing that finally executes is several processes downstream of anything anyone thought to evaluate. An agent that shells out inherits every hazard of everything it shells out to, and its own permission model stops at its own process boundary.

Which is the uncomfortable version of the question: what exactly is a sandbox for an agent, if the agent's job is to run other programs? Whatever it is, it cannot be a wall around the agent, because the agent walks its authority straight through the wall every time it shells out. It has to sit under everything the agent starts, at the one boundary the payload cannot get below. That is the whole reason grith enforces at the syscall and not in the tool.

Footnotes

  1. Francisco Rosales, Manifold Security, "GitSpawn: A Single Flaw Lets Untrusted Repos Run Code in Claude Code, Codex, Cursor, and Grok", 1 September 2026. https://www.manifold.security/blog/ai-coding-agents-git-hijack 2 3

  2. Justin Steven, "Git's buried bare repos and fsmonitor: various abuses", 2022. https://github.com/justinsteven/advisories/blob/main/2022_git_buried_bare_repos_and_fsmonitor_various_abuses.md 2 3

  3. Git 2.38.0 release, 3 October 2022, introducing safe.bareRepository. https://git-scm.com/docs/git-config#Documentation/git-config.txt-safebareRepository

  4. Reproduction run on git 2.43.0, Ubuntu, 9 September 2026. The payload was a benign touch marker, in a scratch directory, with no network access.

  5. The Hacker News, "Malicious Git Configs Can Make Claude and Other AI Coding Agents Run Attacker Code", 2 September 2026. https://thehackernews.com/2026/09/malicious-git-configs-can-make-claude.html 2

  6. grith reproduction, 9 September 2026, on a machine running Claude Code under grith supervision. The core.fsmonitor payload read a decoy private key placed for the test, not a real credential, and wrote to a local marker with no network egress. Verdicts are taken from grith's own audit log. The protection is a property of where grith enforces rather than of which agent invoked git, so it is presented as holding for every row of the disclosure record though it was reproduced under one agent.

Like this post? Share it.