This research began with a single question: what does a CLI AI agent actually do when it starts up? What happens under the hood?
Upon looking at several AI agents, we observed the same behaviour, they gather context about the project they have just been opened in. A good part of that gathering is done with git, called for different purposes at different moments, some on start-up, some once the session begins.
Looking at how git is used, we found a vulnerable pattern. Those context-gathering calls ran without stripping the repository's own git configuration, and several git settings are command execution sinks. The repository names a command, git runs it, on the host, with the user's privileges, before any approval prompt.
This post covers only a few of them, Claude Code, Goose, Grok Build, Hermes and Qwen Code. We found it in more agents than we name here.
What an agent asks git for depends on what it is trying to do. It might want the current branch, and which files have been modified or staged. It might want the files a change touched, or every path the repository tracks, or a fresh worktree for a sub-agent to work in.
Two samples out of many, from different products:
git status --porcelain=2 --branch
git diff --name-only HEAD
Neither is unusual. Both are the sort of command you would write yourself. And both, like most git commands that touch the working tree, make git refresh its index first.
That refresh is the sink.
core.fsmonitor is a performance setting for large repositories. Instead of checking every file on disk, git asks a helper program what changed, and runs it during an index refresh. Documented, intended behaviour.
Git reads that setting from the repository's own .git/config. So a repository can ship this:
[core]
fsmonitor = <command>
and any git command that refreshes the index runs it: git status, git diff. Which one the agent chose does not matter. And core.fsmonitor is not the only setting of its kind, which is why one of the findings below is not a core.fsmonitor bug at all.
Delivery is worth being precise about, because git never carries this. 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. Colleagues pass projects around this way, consultants hand them to clients. For every proof of concept in this post, we used a .zip.
From there the command runs as you, with your privileges, on your machine. This is the agent's own code spawning a subprocess to use git, so the command runs outside the sandbox, without an approval prompt. The permission model never sees it.
We call it GitSpawn. Below are examples of what we found, in different CLI AI agents.