---
id: "GHSA-6hqx-gqrx-c5r2"
page: "https://infrastack.ai/live/GHSA-6hqx-gqrx-c5r2"
kind: "vulnerability"
aliases: ["GHSA-6hqx-gqrx-c5r2"]
title: "mcp-server-git: arbitrary command execution via a repository's own Git configuration (core.fsmonitor) during read-only tools"
severity: "high"
cvss: 7.8
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "mcp-server-git", "fixed": ["2026.11.11"], "affected": "< 2026.11.11", "ecosystem": "pypi"}]
ecosystems: ["pypi"]
tags: ["claude-code", "cursor", "cwe-829", "cwe-94", "git", "mcp", "package", "python"]
not_affected_if: ["fresh-clones-only", "upgraded"]
published_at: "2026-11-11T07:25:22+00:00"
first_seen_at: "2026-11-11T07:28:35+00:00"
updated_at: "2026-11-11T07:29:58+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-6hqx-gqrx-c5r2", "first_seen_at": "2026-11-11T07:28:35+00:00"}]
exploit_sources: []
---

# GitSpawn: mcp-server-git arbitrary command execution via a repository's own Git configuration (core.fsmonitor) during read-only tools

Link for people: https://infrastack.ai/live/GHSA-6hqx-gqrx-c5r2 (this .md is for agents)

A Git repository's own .git/config can name a program (core.fsmonitor or diff.external) that Git runs when the server inspects that repository, so a read-only git_status call executes attacker-chosen code. The attacker gets code execution as the user running the MCP server, with its environment and credentials.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- pypi:mcp-server-git affected < 2026.11.11 fixed 2026.11.11

## How to check

- Run `pip show mcp-server-git` and confirm the version is below `2026.11.11`.
- Check whether the server is pointed at repositories whose `.git` directory came from an untrusted source, such as an archive, shared folder or vendored dependency.
- Inspect `.git/config` in those repositories for `fsmonitor`, `diff.external` or `hooksPath` entries.

## Fix

- `mcp-server-git` upgraded to `2026.11.11`.

## Timeline

- 2026-11-11T07:28:35+00:00 ghsa-repo https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-6hqx-gqrx-c5r2

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/modelcontextprotocol/servers/security/advisories/GHSA-6hqx-gqrx-c5r2

## From the sources

## Patched versions

Fixed in `mcp-server-git` **2026.11.11** ([#5514](https://github.com/modelcontextprotocol/servers/pull/5514)). Upgrade to 2026.11.11 or later. The report below was written against 2026.11.4, before the fix; its "Fixed versions: none" line predates it.

The fix covers every sink the report lists, not only `core.fsmonitor`:

- Every Git command the server runs overrides, through `GIT_CONFIG_COUNT` (which outranks every configuration file and is inherited by the Git processes Git itself starts), the settings that would make Git run a program: `core.fsmonitor` is `false`; `core.hooksPath` points at the null device, so no hook runs; `commit.gpgSign` and `merge.verifySignatures` are `false`; `submodule.recurse`, `status.submoduleSummary` and `merge.autoStash` are `false`; and `core.pager` is `cat`.
- Every filter driver (`filter.<name>.clean`, `smudge`, `process`), merge driver (`merge.<name>.driver`) and `branch.<name>.mergeOptions` that the repository's own configuration defines is emptied. Drivers from the user's global or system configuration, such as Git LFS, keep working.
- `git_merge` also passes `--no-gpg-sign` and `--no-verify-signatures`, and every diff shown as a patch passes `--no-ext-diff` and `--no-textconv`, so `diff.external`, `diff.<driver>.command` and textconv filters are ignored.
- `git_status` and the diff tools pass `--ignore-submodules=dirty`, so Git never looks inside a submodule under the submodule's own configuration.
- A Git older than 2.31, which ignores `GIT_CONFIG_COUNT`, is refused rather than run unprotected.

`git_log` and `git_show` do not run `git log` or `git show`, so `log.showSignature` is never consulted.

## Workarounds

None for affected versions other than upgrading. Until then, point the server only at repositories whose `.git` directory you trust.

---

## Original report

# mcp-server-git: arbitrary command execution via a repository's own Git configuration (`core.fsmonitor`) during read-only tools

**Asset:** `https://github.com/modelcontextprotocol` (scoped asset of the HackerOne `anthropic` program)
**Component:** `mcp-server-git` (PyPI), the reference Git MCP server in `modelcontextprotocol/servers` (`src/git`)
**Package version tested:** `mcp-server-git==2026.11.4` (PyPI wheel sha256 `0cdaa8f446554d020f8ffa0e44ef45b89df4ed4deaeb961b4305d8a2852b4e0e`, published 2026-11-04T21:34:52Z)
**Default-branch revision tested:** `36380287ed3b15d115b55f546617d723072b1296` (2026-11-04, `src/git` version `2026.11.4`); the installed wheel's `mcp_server_git/server.py` is byte-identical to `src/git/src/mcp_server_git/server.py` at that revision (sha256 `2304871e38ddd95ed807f628cc68b0e0cdd8ec2e82401110211ea8ea647715a5`)
**CWE:** CWE-94 — Improper Control of Generation of Code ("Code Injection"); supporting: CWE-829 — Inclusion of Functionality from Untrusted Control Sphere
**CVSS 4.0:** 7.0 (HIGH) — `CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N`
**Affected versions:** `mcp-server-git` up to and including `2026.11.4` (verified on `2026.11.4` and `2026.10.28`; earlier versions run the same Git commands and are presumed affected)
**Fixed versions:** none as of 2026-11-07
**Also reproduced on:** `mcp-server-git==2026.10.28` (previous release; `evidence/11-older-version.txt`), with and without `--repository` confinement (`evidence/12-unconfined-mode.txt`)

---

## Summary

`mcp-server-git` runs Git commands inside repositories chosen by the client without neutralising the repository's own configuration. Git honours command-bearing configuration keys from `.git/config`; `core.fsmonitor` names a program that Git executes whenever it refreshes the index. A call to the read-only `git_status` tool on an untrusted repository therefore executes an attacker-chosen program with the MCP server's privileges — its environment (including credentials from the client's `env` block), its filesystem access and its user — without any approval prompt and outside any sandbox that governs the model's own tool calls.

A second command-bearing key, `diff.external`, gives the same result through the read-only `git_diff_unstaged` and `git_diff_staged` tools. The command need not ship as a separate file: it can be inlined in the configuration line itself (`core.fsmonitor = sh -c '…'`), so a single edited `.git/config` is enough. The repository's configuration is never overridden: the server's only `-c` override is `core.quotepath=false` (`src/git/src/mcp_server_git/server.py:104`).

This is the vulnerability class publicly tracked as **GitSpawn** (CSA research note, 2026-09-03) and assigned CVEs for other agent tooling that shells out to Git: CVE-2026-45033 (GitHub Copilot CLI, `core.fsmonitor`, 8.5), CVE-2026-72718 (Goose, CWE-94, 7.0) and CVE-2026-71963 (Hermes Agent, 8.6). The `mcp-server-git` reference server has not applied the mitigation those vendors shipped, and no advisory or issue in the repository covers it.

## Attack delivery

The malicious configuration lives in `.git/config`, so the repository must reach the user with its `.git` directory intact. That is the normal case for the delivery channels this class was reported against (Copilot CLI, Goose, Hermes Agent): a project archive or CI artifact, a backup, a shared network folder, a vendored dependency tree checked into or unpacked beside the user's project, or an agent workspace whose files were copied rather than cloned. A plain `git clone` of a repository does not copy server-side `.git/config`, and GitHub's source ZIP excludes `.git`; those two channels alone do not carry the payload. Everything else that preserves the folder structure does, and the user does not have to run anything inside the repository — one `git_status` call from the MCP server is enough.

## Root cause

`open_repo()` (`server.py:981-990`) builds `git.Repo(path)`, and GitPython executes `git` in that repository. The only configuration override anywhere in the server is `core.quotepath=false`:

```python
# server.py:104
output = getattr(repo.git(c="core.quotepath=false"), command)(
    *args, stdout_as_string=False
)
```

Git reads all other configuration from the repository's `.git/config`. Keys that name a program Git then runs include:

| key | runs when | server tool that triggers it |
|---|---|---|
| `core.fsmonitor` | any index refresh | `git_status`, `git_diff_unstaged`, `git_add`, `git_merge` |
| `diff.external` / `diff.<driver>.command` | diff output | `git_diff_unstaged`, `git_diff_staged`, `git_diff` |
| `core.hooksPath` + hooks | merge | `git_merge` |
| `log.showSignature` + `gpg.program` | log | `git_log` |
| `filter.<driver>.clean` | staging | `git_add` |
| `commit.gpgSign` + `gpg.program` | merge commit | `git_merge` |

The demonstrated sink is `core.fsmonitor`, because it fires on the most innocuous, typically auto-approved tool (`git_status`) and the payload need not print anything, so the tool result looks completely normal.

## Steps to reproduce

Full runnable PoC: `poc/` in this packet. The repository is self-contained: the payload script ships inside the repository and is named by its own `.git/config`, so it travels intact through any channel that preserves a `.git` directory (an archive, a shared folder, a vendored dependency tree, a CI workspace).

1. Install the exact published package (Python 3.10+; 3.12/3.15 used here):

   ```bash
   python3 -m venv .venv
   ./.venv/bin/pip install "mcp-server-git==2026.11.4"
   ```

2. Create a project workspace that is itself a Git repository, and a nested "untrusted" repository inside it:

   ```bash
   mkdir -p /tmp/poc/workspace/vendor/untrusted-repo
   git -C /tmp/poc/workspace init -q
   git -C /tmp/poc/workspace/vendor/untrusted-repo init -q
   cd /tmp/poc/workspace/vendor/untrusted-repo
   echo hello > README.md
   git add README.md && git -c user.email=a@b -c user.name=a commit -qm init
   printf '#!/bin/sh\nid > /tmp/poc/payload-ran.txt\n' > payload.sh
   chmod +x payload.sh
   git config core.fsmonitor .

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
