|
# eight findings, seven agents, four live at publication
Open a folder with a command line coding agent and it runs git before you type anything. On several agents it runs git before the workspace trust prompt is accepted. On one it runs git before you have authenticated. Whatever sits in that folder's .git/config decides what that command is.
Manifold Security published eight findings across seven agents on September 1, under the name GitSpawn. Four were still live at publication. The affected list includes Claude Code, OpenAI Codex, Cursor, Grok Build, Qwen Code, goose and Nous Research's Hermes Agent. The same day, OpenAI published three CVE records of its own for Codex, credited to four separate research groups.
All three of those records point at the same file. They name three different settings inside it.
# a performance setting whose value is a command
core.fsmonitor is a git performance setting. It names a helper program that git asks for the list of changed files, rather than walking every file on disk. Think of it as a cache daemon git consults before it does the slow thing. Git reads the value from the repository's own config, and it runs that program during an index refresh, which is what git status and git diff both do first. This is documented, intended git behaviour.
Agents call those two commands in the background to work out the branch and the modified files. That is the whole vulnerability. The agent's own process spawns git, so the program git then runs sits outside the agent sandbox, and no approval dialog covers it. OpenAI's record for the attr.tree variant states that the program runs "without a workspace-trust prompt, command approval, or interaction with a model".
That clause is the part worth sitting with. Permission modes, tool allowlists, approval prompts and sandbox policy are all controls an engineer configures around a model. This fires above all of them, in the plumbing an agent uses to work out where it is. The goose advisory puts the same thing in a line: "no submitted prompt, no model call, no tool approval, no trust prompt".
Delivery is the narrow part, and it is worth being exact about, because git never carries this over the wire.
| how the repository arrives |
config preserved |
reachable |
| git clone from a hostile URL | no | no |
| git fetch or git pull | no | no |
| a zip or tar handed to you | yes | yes |
| shared drive or sync folder | yes | yes |
| USB stick or restored backup | yes | yes |
# fig 1 · a clone rebuilds the local config, a file copy carries it
Read that list against how code actually reaches you. The take home assignment from a candidate. The client handoff from a consultant. The vendor sample dropped in a shared folder. The project a colleague zipped because the repository was private. Every one of those preserves the directory. Manifold used a zip for every proof of concept in the post.
| |
[03 · the setting was never the bug] |
# one researcher defeated one fix by reading the git manual
The clearest account of what is wrong here is not in the September research. It is in Sonar's April 30 write up of two earlier Claude Code findings, and in what happened between the two halves of it.
Sonar reported the fsmonitor path. Anthropic's change in version 2.0.34 stopped running git status before the trust dialog. Sonar then looked at the version current at the time, 2.0.50, and found five other git commands still running before approval, including git log and git worktree list. So they went looking through the git documentation for a second sink.
Two candidates missed. core.pager did not fire, because the agent runs git from Node and captures standard output as a string, so there is no interactive terminal and no pager launches. diff.external did not fire, because the agent passed --name-only and never printed file contents. The third worked. log.showSignature adds a signature check to git log, a signature check calls gpg, and gpg.program names the binary git runs in place of gpg. Point it at a script and the script runs before the trust dialog.
That is a fix defeated by enumeration, published in the same post as the fix. The patch removed one call site. The file still had others. Here is the inventory as it stands across every source below.
| setting |
lives in |
what runs it |
reported by |
| core.fsmonitor | .git/config | any index refresh | Sonar, Apr 2026 |
| log.showSignature + gpg.program | .git/config | git log | Sonar, Apr 2026 |
| apiKeyHelper | .claude/settings.json | agent startup | Sonar, Apr 2026 |
| hooks | .claude/settings.json | agent events | Sonar, Apr 2026 |
| core.hooksPath | .git/config | automated git operations | OpenAI, Sep 2026 |
| attr.tree + a clean or process filter | .git/config | working tree status | OpenAI, Sep 2026 |
| withheld while unpatched | .git/config | claude ultrareview | Manifold, Jul 2026 |
# fig 2 · seven sinks, two files, and one that has no public name yet
Two of those are not git at all. Claude Code's own project settings file carries apiKeyHelper, which Anthropic documents as a custom script run through the shell, and hooks, which bind commands to agent events. Both are project local. Both take precedence over the global file. Both are read at startup. The pattern generalises past git: an agent picks up a configuration file it did not write and trusts it because it arrived in the working directory.
| |
[04 · the disclosure record] |
# five of eight came back as duplicates, one filed the same day
| agent |
reported |
vendor response |
status at publication |
| Claude Code (fsmonitor) | Jun 26 | duplicate of a same day report | patched, 2.1.196 |
| Qwen Code | Jul 7 | accepted by Alibaba SRC | live at 0.22.3 |
| Cursor | Jul 8 | duplicate of an earlier report | patched |
| goose | Jul 13 | acknowledged, CVE assigned | patched, 1.44.0 |
| Grok Build | Jul 14 | duplicate of a report closed as informative | live at 1.0.13 |
| Claude Code (ultrareview) | Jul 15 | duplicate of an internal ticket | live at 2.1.252 |
| Codex | Jul 20 | duplicate of an earlier report | patched |
| Hermes Agent | Jul 20 | no triage after six contacts | live at 0.21.0 |
# fig 3 · dates are 2026, statuses as recorded by Manifold on Sep 1
Five of the eight reports were closed as duplicates. Three of those five ended patched. Two ended live at publication: the second Claude Code path, closed as a duplicate of an internal ticket, and Grok Build, closed as a duplicate of a July 1 report that xAI had already closed as informative. A duplicate closure tells a reporter that someone else got there first. It tells them nothing about whether the bug is fixed, and in two cases out of five here it resolved to nothing shipping.
The identifier record is stranger. Every one of these records was pulled from the CVE Services API for this issue. The same mechanism carries four different weakness classifications.
| record |
assigned by |
weakness class |
vector |
score |
| CVE-2026-72718, goose | GitHub | CWE-94 | local | 7.0 |
| CVE-2026-71963, Hermes | VulnCheck | CWE-78 | network | 8.6 |
| CVE-2026-19592, Codex | OpenAI | CWE-15 | none given | none given |
| CVE-2026-19593, Codex | OpenAI | CWE-15 | none given | none given |
| CVE-2026-19590, Codex | OpenAI | CWE-427 | none given | none given |
| Claude Code fsmonitor fix | no record | no record | no record | no record |
# fig 4 · pulled from the CVE Services API on Sep 13 2026
Four weakness classes for one mechanism: code injection, operating system command injection, external control of a configuration setting, and uncontrolled search path element. Two published scores, 1.6 points apart, with opposite attack vectors for what is materially the same act. Three records with no score at all. One fix with no record at any severity.
The practical consequence is that no single query against a vulnerability feed returns this class. A team that tracks CWE-78 misses the Codex records. A team that filters on severity misses everything OpenAI filed. The credits on those three records name Sina Kheirkhah of Summoning Team, the System Software and Security Lab at Fudan University, Doyensec reporting through the Zero Day Initiative, and Satoki Tsuji of Ikotas Labs. Four groups, three records, one file, arriving independently.
# release dates pulled from the npm registry, Sep 13 2026
| published |
version |
what changed |
| Nov 5 2025 | 2.0.34 | git status moved after the trust dialog |
| Dec 16 2025 | 2.0.71 | both Sonar findings patched |
| Jun 25 2026 | 2.1.193 | fsmonitor path confirmed live again |
| Jun 29 2026 | 2.1.196 | fixed, four days after the report |
| Jul 14 2026 | 2.1.210 | second path reported the next day |
| Aug 31 2026 | 2.1.252 | second path re-confirmed live |
| Sep 12 2026 | 2.1.270 | current published release |
# fig 5 · 39 versions were published between 2.1.211 and 2.1.252
Two things in that column deserve separating. The first response was fast: reported June 26, fixed June 29, four days. That is a good number and it should be said plainly.
The second is the shape of the line. The behaviour Sonar had mitigated in November 2025 was present again in a June 2026 build, seven months and 202 published versions later. The second path was reported on July 15 and was still there on August 31, with 39 published versions in between. The package also carries a stable tag pointing at 2.1.236 from August 19, so the conservative channel is three weeks behind the latest.
Stating the limit carefully: the second path was last confirmed live on 2.1.252, and the key it turns on has not been published. No source states whether the releases since have closed it. That is the honest position, and it is also the reader's problem, because with no advisory there is nothing to check a version against.
| |
[06 · what to do with this] |
# four checks, one of them permanent
If a repository arrives as files rather than through a clone, open the config in an editor before you open the directory with an agent. Look for any key whose value is a path or a command.
# inspect what a received repository is carrying
cat .git/config
git config --get core.fsmonitor
# audit and disable the setting globally
git config --global --list | grep fsmonitor
git config --global core.fsmonitor false
|
# fig 6 · checks as published by Manifold
The vendor side advice is to strip the repository config on background calls, for example by passing -c core.fsmonitor=false to git. That closes the reported sink. It does not close the class, for the reason section three walks through: the strip list is built from the settings someone has already reported, and the inventory keeps growing. The durable version treats every configuration file that arrives inside a working directory as untrusted input, whether it belongs to git or to the agent.
For the four agents with a live finding, the check that matters is version pinning. Codex CLI below 0.131.0 is affected and the current release is 0.152.1, so an installation pinned in July still carries it. goose below 1.44.0 is affected. Hermes 0.18.2 through 0.21.0 is affected, and the CVE record published on September 3 lists a fix commit, two days after Manifold recorded it as unpatched.
# what holds this back, in the interest of getting it right
Nothing here has been seen in use. No source reports exploitation of any of these findings, and The Hacker News checked the United States Cybersecurity and Infrastructure Security Agency's exploited vulnerability catalog on September 2, at 1,687 entries, and found none of these identifiers listed.
The delivery requirement is real, not a formality. Cloning a hostile repository does nothing at all. Someone has to hand you a directory. That rules out the most common way engineers acquire code and leaves the ways they acquire code from people they already half trust.
The class is five years old. Sonar points at the identical trust dialog bypass in Visual Studio Code before 1.63.1 (CVE-2021-43891) and JetBrains IDEs before 2021.3.1 (CVE-2022-24346), and Cobalt published fsmonitor abuse as a red team technique in December. Agents did not invent this. They re-implemented the startup sequence that had it.
Most vendors moved quickly. Four of the eight were fixed, goose in 1.44.0, Claude Code in four days, Codex in 0.131.0 with three public records and named credits, Cursor after a duplicate closure. The weak part of this record is the tail and the paperwork, not the response as a whole.
Manifold sells the thing it is warning about. The post ends in a product pitch for runtime monitoring of agent behaviour, which is worth knowing while reading it. The findings themselves sit in CVE records, vendor pull requests and version histories that anyone can check, which is how the numbers in this issue were assembled. Separately, the startup Accomplish told Upstarts Media this week that one unrelated issue it reported to Anthropic took around 50 days and roughly 30 releases to patch, while its Cursor and OpenAI reports were closed in about a week.
|
The industry has spent two years building approval prompts, permission modes and sandboxes around the model, and every one of these findings lands above that layer, in the subprocess an agent spawns to work out which branch it is on. The security boundary was never the sandbox. It is the moment an agent reads a file it did not write.
|
|