cd /news/ai-agents/a-nested-git-file-let-the-git-mcp-se… · home › topics › ai-agents › article
[ARTICLE · art-163540] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↓ negative

A nested .git file let the Git MCP server out of its --repository until 2026.10.28. Which version does your uvx run?

A security advisory published 2026-10-28 (GHSA-w766-5x43-whvw, CVSS 5.3, reported by ShieldPi) disclosed that the reference Git MCP server's --repository confinement could be bypassed by a nested directory containing a plain-text .git file pointing at another repository, letting tool calls read committed content and create branches outside the allowed path. The flaw affects all mcp-server-git versions before 2026.10.28, the patched release, and the reporter noted a realistic trigger is a deployment that also grants a filesystem MCP server write access to the same directory, since its write_file has no .git exclusion. The advisory follows two earlier path-validation fixes (2025.12.18 and 2026.1.14), and the README's unpinned uvx launch command means users may not know which version they run.

by read7 min views2 publishedNov 4, 2026

mcp-server-git --repository <path> is the one setting that keeps the reference Git MCP server inside a single repository. An advisory published on 2026-10-28 showed that a nested directory with a .git file pointing elsewhere let tool calls read and change a repository outside that boundary; the fix is version 2026.10.28. The configuration the project's README shows runs the server through uvx with no version in it, and a sibling advisory for the filesystem server, a regression, shows what each way of pinning costs.

The advisory is GHSA-w766-5x43-whvw, mcp-server-git: nested gitdir file bypasses --repository confinement, reported by ShieldPi on 2026-10-27 and rated Moderate (CVSS 5.3). Affected versions are everything before 2026.10.28, which is the patched version. On PyPI, 2026.10.28 was uploaded on 2026-10-28 at 21:06 UTC, and it was still the newest release when we checked again on 2026-11-03 (UTC).

It is not the first advisory about keeping this server inside a repository. The repository's advisory list also has GHSA-j22h-9j4x-23w5, "Missing path validation when using --repository flag in mcp-server-git", published on 2025-12-17 with 2025.12.18 as the patched version, and GHSA-vjqx-cfc4-9h6v, "Path traversal in git_add allows staging files outside repository boundaries in mcp-server-git", published on 2026-02-25 with 2026.1.14 as the patched version. Each fix closed one way out of the boundary. That history is the practical reason to know which version your launch command runs, which is where this article ends up.

The mechanism rests on an ordinary Git feature. Git's repository layout documentation explains that instead of a .git directory "you can have a plain text file .git at the root of your working tree, containing gitdir: to point at the real directory that has the repository." Submodules and worktrees use this every day.

The server checked the path it was given, not the repository Git ended up opening. In the report's words, the path check "canonicalize[s] only the caller-supplied repo_path and check[s] that it is inside allowed_repository", and then the repository is opened, at which point "GitPython resolves that redirection later. There is no check after opening the repository to ensure that the repository actually selected by Git is the allowed one."

The reporter confirmed two effects through the real MCP interface: reading committed content from the outside repository, and creating a branch in it. "This bypasses the server's claimed repository boundary and permits unauthorized read and modification with the MCP process's privileges." The report is also explicit about what it does not claim: it "does not claim arbitrary host-file write or code execution", and it is "not claimed to be an unauthenticated standalone remote exploit".

The interesting part of the report is how the nested .git file gets there. The caller has to be able to "create a nested directory and a .git file inside the allowed repository, or cause such content to be placed there", and to guess the path of another repository the process can reach.

The report names one realistic setup: "a deployment that also gives a filesystem MCP server write access to the same allowed directory." At the tested commit, the filesystem server's write_file "has no special .git exclusion."

That is a plausible configuration. Both servers live in the same reference repository, both are easy to point at the same project in Claude Code or Cursor, and each server's boundary looks fine on its own. The combination is what crossed it. An agent steered by text it read, in an issue or a file, would only need to use both tools in the right order.

The mcp-server-git README is worth rereading, because it states the boundary more precisely than most people configure it.

"Without --repository, the server reads and writes any Git repository the server process can reach." The README adds that this holds "for the tools that change a repository too", and that "--repository is the only confinement the server offers".

It also widens what the flag admits: "At startup the server walks up to the enclosing repository root", so "A path inside the repository therefore allows tool calls anywhere in that repository, not only under the subdirectory given." If you pointed --repository at packages/api hoping to keep the server out of the rest of a monorepo, it does not do that.

And it describes the fix: a tool call's repo_path must be inside the repository, "and so must the directories Git then uses for it: a repository nested inside whose .git file, commondir or core.worktree points outside is refused."

Here is the configuration the README gives for Claude Desktop, and the same command is what most people pass to claude mcp add:

{
  "command": "uvx",
  "args": ["mcp-server-git", "--repository", "path/to/git/repo"]
}

There is no version in it. uv's tools guide gives two ways to say which version you want: "To run a tool at a specific version, use command@", and "To run a tool at the latest version, use command@latest". It also notes that "the @ syntax cannot be used for anything other than an exact version." The bare name is a third choice, and the guide we read does not promise it means the newest release, which is reason enough to write one of the other two.

The filesystem server's launch commands follow the same pattern with npx -y. npm's npm exec documentation says the CLI "utilizes its internal package cache when using the package name specified", and describes --prefer-online as the flag that "Forces staleness checks for packages, making the cli look for updates immediately even if the package is already in the cache."

To see what Claude Code launches, claude mcp list shows each configured server and its status, and claude mcp get <name> shows the details of one, according to the MCP documentation. The command and arguments live in .mcp.json for project scope and in ~/.claude.json for local and user scope.

Three days before the Git advisory, the same repository published GHSA-4gxg-m5jx-9j57 for server-filesystem. On a case-insensitive file system, such as the macOS and Windows defaults, versions 2026.8.31, 1.0.0 and 2.0.0 could create files outside the allowed directories through a symlink spelled with different letter case. The detail that matters for pinning is one line: "It is a regression: 2026.7.10 refuses these requests." The fix is 2.0.1.

The npm registry's release times make the trade-off concrete:

How the version is chosen What it got from July to October 2026
Exact pin to 2026.7.10 Never had this regression, and never got any later change either (whether it is affected by the other advisories published that day is a separate question)
Always the newest release Picked up the regression on 2026-08-31 and the fix on 2026-10-25 (2.0.1)
Exact pin to 2.0.1 Fixed, and stays there until someone edits the command

Neither column is free. An exact pin keeps out a bad release, including a malicious one pushed with a stolen token, but it also keeps out the next security fix until a person moves it. Floating gets fixes as soon as they ship and regressions just as fast. What we would avoid is the bare name, where the version is decided by a cache you did not choose.

claude mcp get <name> for your Git server and look at its arguments. If it says mcp-server-git with no version, you do not know whether you are on 2026.10.28.uvx mcp-server-git@2026.10.28 --repository /path/to/repo. For always-newest, uvx mcp-server-git@latest .... With claude mcp add, the command goes after --, as in claude mcp add git -- uvx mcp-server-git@2026.10.28 --repository /path/to/repo. server-filesystem.--repository. We read the boundary flags of MCP servers as promises from one server. This advisory is a reminder that they are promises about one server's own checks, and that the agent holding several tools at once can combine them in ways no single server sees.

Do you pin your MCP server versions in .mcp.json, float on @latest, or leave the bare name? If you have a way to get told when a pinned server has a fix waiting, describe it in the comments below.

── more in #ai-agents 4 stories · sorted by recency
── more on @mcp-server-git 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-nested-git-file-le…] indexed:0 read:7min 2026-11-04 · —