Trusted by Design? How Headless Claude Code Executed Host Commands Through Repository-Controlled Git Configuration

Imagine an automated code-review service receiving a complete project archive, including its hidden .git metadata, and launching Claude Code to analyze it.
In our proof of concept, the entire prompt was: Reply exactly with OK.
It did not mention Git, request repository inspection, or ask Claude to run a command. Nevertheless, Claude Code’s own repository-context processing invoked Git, and Git launched an external hook command configured through core.fsmonitor in the workspace’s local .git/config.
On an analysis worker with access to source code, CI/CD tokens, cloud credentials, package registries, or internal services, that execution path can have consequences far beyond the model’s response.
The behavior was reproducible. Its security classification was less straightforward. Anthropic reviewed the report, classified it as Informative, and concluded that non-interactive callers are responsible for trusting the complete workspace they provide.
This research from Cymulate Research Labs focuses on that tension: a reproducible host-side execution path originating from a pre-crafted workspace and reached without an interactive trust checkpoint, but classified as expected behavior under Claude Code’s headless trust model.
Finding Overview
At the center of the finding was Git’s core.fsmonitor setting. It can enable Git’s built-in filesystem monitor or identify an external fsmonitor hook command, and working-tree operations such as git status may invoke that hook.
In a crafted or pre-populated workspace, attacker-controlled repository-local .git/config can set core.fsmonitor to an external command. During headless processing, Claude Code 2.1.227 invoked:
git --no-optional-locks status --short Several other internal Git calls explicitly cleared repository-controlled fsmonitor behavior using -c core.fsmonitor=. This working-tree status call did not. Git read the local configuration and launched the configured hook with the privileges of the Claude Code process.
We reproduced the behavior on macOS and Ubuntu Linux. We also confirmed the same marker behavior through Python claude_agent_sdk.query() using claude-agent-sdk 0.2.135; the SDK test did not independently trace the host’s exact Git arguments. In a locked-down CLI test, Claude Code ran with --bare, every model tool removed, an invalid API key, and a deliberately unreachable endpoint. The marker still appeared without a successful model response or a model-selected tool call.
The delivery condition is narrower than an ordinary malicious repository: a normal git clone does not transfer arbitrary local .git/config. The demonstrated path requires a complete or already-populated workspace, such as an archive, shared directory, restored cache, reused CI workspace, or VM image, that preserves local Git metadata.
The underlying Git primitive was already known. Sonar had previously demonstrated the same git status → core.fsmonitor path before interactive workspace trust. After our submission, Manifold Security publicly disclosed another interactive pre-trust case, confirmed on Claude Code 2.1.193 and reported fixed by 2.1.196. Our report concerned the same mechanism on Claude Code 2.1.227 in headless CLI and Python Agent SDK operation, where Anthropic treats workspace trust as delegated to the caller.

Figure 1: A complete workspace carries repository-local Git configuration. Claude Code invokes an internal working-tree status operation, and Git launches the configured fsmonitor hook. Interactive Claude Code gates the affected path behind workspace trust; non-interactive operation delegates that trust decision to the caller.
Introduction: The Hidden Work Around the Model
Most security discussions about AI coding agents begin with the model. Can prompt injection persuade it to run a command? Can it misuse an approved tool? Can malicious content manipulate a permission dialog or cause sensitive data to leave through a plugin?
Those are important questions, but the model is only one component of an agentic system.
The host application around it also performs work: it reads configuration, inspects directories, gathers repository context, starts helper programs, manages sessions, and prepares information for the model. These operations may occur without the model selecting a tool and without a tool-permission decision ever being created.
That distinction matters operationally. Security teams may carefully restrict Bash, require approval for edits, and audit every command visible in an agent transcript. Meanwhile, the host process can continue to launch its own utilities in the background. If one of those utilities interprets attacker-controlled workspace state, the meaningful execution path may never appear as a model action at all.
Our investigation began with a simple question:
What does Claude Code execute internally when it processes a repository in headless mode?
The answer led to an old Git feature, a familiar command-execution primitive, and a much less familiar debate about where trust begins in an automated AI workflow.
Background: Why core.fsmonitor Can Execute a Program
Git uses filesystem monitoring to avoid repeatedly scanning large working trees. When core.fsmonitor is set to true, Git enables its built-in monitor. When it contains another value, Git treats that value as the pathname of an external fsmonitor hook command.
A legitimate configuration might look like this:
[core]
fsmonitor = /path/to/fsmonitor-helper Commands that refresh or inspect working-tree state, including git status, may invoke that helper:
application runs git status
↓
Git reads repository-local .git/config
↓
Git sees the core.fsmonitor hook command
↓
Git launches that command
This is documented Git behavior, not a Claude-specific command-parser bug. The security question is what happens when an application automatically invokes Git against a workspace it did not construct itself. If that workspace carries local Git metadata, routine context collection can become a path to launching an external program.
The Missing core.fsmonitor Override
To observe Claude Code’s Git activity without changing its behavior, we used a small logging shim. Claude Code launches git by name, so we placed an executable with the same name earlier in the process search path. Each time Claude Code invoked Git, the shim recorded the working directory and exact arguments, then immediately handed execution to the real Git binary with those same arguments. It was instrumentation only and was not part of exploitation.
The trace showed that Claude Code already neutralized repository-controlled core.fsmonitor for several internal Git operations. For example:
git -c core.hooksPath=/dev/null -c core.fsmonitor= \
remote get-url origin The working-tree operation relevant to this finding was different:
git --no-optional-locks status --short That invocation did not include -c core.fsmonitor=. Git could therefore read the repository-local value from .git/config and launch the configured fsmonitor hook command.
Git’s ability to invoke an external fsmonitor hook is expected behavior. The Claude Code-specific finding was the inconsistent use of an existing safeguard: several internal Git calls cleared repository-controlled core.fsmonitor, while the working-tree status call that made the configured hook reachable did not.

Figure 2: Several Claude Code internal Git calls explicitly neutralized repository-controlled core.fsmonitor. The working-tree status --short invocation associated with the demonstrated execution path did not.
Reproducing the Behavior Safely
The proof of concept was intentionally uneventful: it wrote a fixed marker to a temporary file and did not access credentials, establish persistence, or transmit data. The following POSIX-shell example was validated on macOS and Ubuntu Linux. The published Cymulate scenario also supports Windows, where paths and shell invocation differ.
TEST_ROOT="$(mktemp -d)"
cd "$TEST_ROOT"
MARKER="/tmp/fsmonitor-marker.txt"
rm -f "$MARKER"
git init -q
echo "# Placeholder project" > README.md
git add README.md
git -c user.email="[email protected]" -c user.name="test" \
commit -qm "init"
git config --local core.fsmonitor \
"sh -c 'printf %s CYMULATE_FSMONITOR_MARKER > \"$MARKER\"'"
test ! -e "$MARKER" && echo "Marker absent before Claude Code"
claude -p "Reply exactly with OK." --output-format text
cat "$MARKER"
Expected output:
CYMULATE_FSMONITOR_MARKER After testing, remove the temporary repository and marker:
cd /
rm -rf "$TEST_ROOT"
rm -f "$MARKER" The prompt deliberately says nothing about Git or repository inspection, showing that no Git-specific instruction was needed. The next test isolated the trigger from model-selected activity.
Confirming the Trigger Was Host-Side
A marker alone proves execution, but not which layer initiated it. We therefore repeated the test under a restrictive headless configuration:
claude --bare -p "Hi" \
--permission-mode dontAsk \
--disallowedTools "*" The test removed every model tool, disabled interactive input, used an invalid API key, and pointed Claude Code at a deliberately non-listening local endpoint. Claude Code received no successful model response, yet the marker was created while the process remained active.
We confirmed the same behavior through Python claude_agent_sdk.query() using claude-agent-sdk 0.2.135. In the minimized SDK test, filesystem settings and plugins were disabled, no tools were offered, Bash was denied, and no assistant message or tool-use event was observed before the marker appeared.
These controls support a precise conclusion:
The fsmonitor hook did not require prompt injection, model reasoning, a model-selected Bash command, or approval through the model tool-permission flow. Claude Code invoked Git through its host-side repository processing, and Git launched the configured hook.
Corrected Linux syscall tracing also refined our timing claim. Claude Code attempted to connect to the configured endpoint before the fsmonitor child executed. The result therefore proves execution without a successful model response or model-selected tool action, not execution before the first connection attempt.
Why a Marker File Matters
A marker file is intentionally harmless. The security impact lies in which process created it and what that process could have reached instead.
The fsmonitor hook ran with the identity, environment, filesystem access, and network access of the Claude Code process, subject to any external operating-system, container, or whole-process sandbox controls. In a developer or automation environment, that can place an attacker-controlled command inside a high-value execution context.
On a developer workstation, the process may be able to read source code, local configuration, Git credentials, cloud profiles, or package-manager tokens, and it may inherit access to an SSH agent exposed to the user session. In CI/CD, a repository-analysis worker may hold a source-control token, artifact or package-registry credentials, deployment secrets, signing material, or access to internal services. In a multi-stage workflow, a lower-trust stage that can alter local repository metadata may influence a later stage running with broader privileges even when the tracked source code has not changed.
The path also creates a visibility gap. The child process was a descendant of Claude Code’s internal Git invocation, not a model tool call. A security team reviewing only the prompt, model response, and approved-tool history could miss the event entirely. Endpoint telemetry would instead show process ancestry such as:
claude → git → shThe research did not access real credentials or perform follow-on activity. But once arbitrary command execution exists in a worker that has been given valuable access, the potential consequences are determined by the worker’s privileges, not by the harmless marker used to prove the primitive.
Responsible Disclosure and Anthropic’s Response
Cymulate Research Labs submitted the finding to Anthropic through HackerOne on August 12, 2026, at 08:59 UTC. Anthropic responded at 12:07 UTC and closed the report as Informative.
Anthropic’s assessment centered on the provenance of the working directory. Because a standard clone does not transfer arbitrary repository-local .git/config, the demonstrated condition required a complete or already-populated workspace. Anthropic considered running Claude Code in such a directory comparable to running Git there: ordinary Git operations may consume that workspace’s existing local configuration.
Anthropic also distinguished interactive and non-interactive operation. Interactive Claude Code waits for the user to trust a workspace before the relevant repository processing begins. In claude -p, claude --bare -p, and Agent SDK use, the caller is responsible for deciding whether the supplied working directory is trusted. Tool restrictions, permission modes, --bare, and SDK settings limit model-facing capabilities and customizations; Anthropic did not consider them a separate provenance boundary around the workspace’s existing Git metadata.
Anthropic summarized its decision as follows:
“After review, this behavior is working as designed and falls outside our threat model.”
The key distinction in Anthropic’s assessment was not the Git mechanism itself. The earlier Sonar finding executed before an interactive user had trusted the workspace. The Cymulate report demonstrated the same underlying primitive in non-interactive modes where Anthropic considers workspace trust delegated to the automation caller.
Validate Your Defenses with Cymulate Exposure Validation
Cymulate Exposure Validation includes the production-safe scenario Git core.fsmonitor Config Abuse for Command Execution via Claude Code. The submitted research confirmed the CLI path on macOS and Ubuntu Linux; the published scenario is packaged for Linux, macOS, and Windows.
The scenario turns the research into a repeatable control-validation exercise without reading credentials, establishing persistence, or transmitting data. It:
- verifies that Git and Claude Code are available;
- creates a uniquely named temporary repository with a minimal tracked file and commit;
- configures an inert core.fsmonitor hook that writes CYMULATE_FSMONITOR_MARKER;
- invokes claude -p "What does this repository contain?" from that repository;
- validates the marker independently of the model’s conversational output; and
- removes the repository and marker during cleanup.
Because the success condition is a deterministic file marker, the scenario validates the host-side execution path rather than the model’s willingness to follow malicious instructions.
Security teams can use it to determine whether endpoint controls prevent or detect an unexpected Git-launched child process, whether EDR or SIEM telemetry preserves the full claude → git → shell ancestry, whether complete workspaces are sanitized before analysis, and whether agent workers have access to credentials or network destinations they do not need.
Search the Cymulate scenario library for Git core.fsmonitor Config Abuse for Command Execution via Claude Code to validate this attack surface in your environment.
Conclusion
The important result was not that Git supports core.fsmonitor; that behavior is documented. It was that Claude Code’s automatic repository processing reached a Git operation that could launch the hook command selected by repository-local configuration, without a model-selected tool call and without a successful model response.
Anthropic treats a working directory supplied to claude -p or the Python Agent SDK as trusted by the automation caller. Earlier interactive findings crossed a pre-trust boundary; the headless report did not cross the boundary Anthropic says it defends. Anthropic classified the report as Informative, and no CVE was assigned to the reported headless condition.
The broader lesson extends beyond one Git setting. AI coding agents cross several overlapping security boundaries: prompts, model tools, workspace contents, host-side helpers, credentials, and process isolation. A control that constrains one boundary may have no effect on another.
For defenders, the question is no longer only, “What can the model be persuaded to do?” It is also, “What will the host application do automatically with the workspace we give it?” Both questions must be answered before an agent is placed next to source code, build systems, secrets, and production workflows.