Capability Laundering: The Series So Far
This is the third case in an ongoing series documenting capability laundering in MCP ecosystems.
Capability laundering is when an agent calls one tool, but gets the effect of a different capability via side effects. It occurs when all three conditions are met:
- The tool’s contract does not cover its effects — the implementation can produce effects beyond what the tool claims to do.
- Inputs can steer those effects — arguments can influence which effect happens and what gets modified.
- Controls gate tool calls, not effects — approvals and policies do not model the effect being produced.
The previous two cases:
- Part 1: Memory MCP: A “memory storage” tool laundered arbitrary file-write capabilities, enabling VS Code terminal hijacking. Boundary bypassed: approval gate.
- Part 2: Git MCP (CVE-2025-68143):
git_initlaundered file-read capabilities by creating repositories in sensitive directories. Boundary bypassed: CWD boundary.
This third case bypasses the same CWD boundary, but through a different mechanism: it requires no setup, leaves no artifacts, and exploits a flaw in GitPython itself rather than just the MCP server.
This vulnerability was assigned CVE-2026-27735 and disclosed in GHSA-vjqx-cfc4-9h6v.
What Happened
Part 2 required git_init to first create a repository in a sensitive directory like ~/.ssh. That left a .git directory behind — a detectable artifact.
This vulnerability needs none of that. From any existing repository, a single git_add call with a relative path like ../../../.kube/config silently reads the file into the Git object database. The working directory stays clean. The file content lives only in Git history, invisible to ls or any file browser.
The root cause is not in the MCP server alone — it is in GitPython itself. Git CLI correctly rejects paths outside the repository. GitPython’s index.add() does not. The MCP server inherits this flaw by calling index.add() without validation.
I discovered this vulnerability and reported it to the Anthropic MCP Team. They published the fix through my PR #3164, and the advisory GHSA-vjqx-cfc4-9h6v credits the report and fix contribution.
TL;DR
- Issue:
git_addpasses user-supplied paths directly to GitPython’srepo.index.add(), which does not validate that files are within the repository. Git CLI rejects such paths; GitPython does not. - Impact: Any file readable by the process can be silently added to Git history and extracted via
git_showorgit_diff_staged. Working directory remains clean. - Root Cause: GitPython’s
_to_relative_path()only validates absolute paths. Relative paths with../bypass all checks. --repositoryflag: Ineffective. It restricts which repos can be accessed, but does not prevent path traversal withingit_add.- Advisory: GHSA-vjqx-cfc4-9h6v / CVE-2026-27735
- Class: MCP capability laundering — “stage files” tool produces “read arbitrary files” effect.
Threat Model: Capability Laundering
The Approval Gap: Agent Sandbox vs. MCP Runtime
When an AI agent connects to an MCP server, the user approves tool calls — not the underlying filesystem effects those tools produce. This creates a fundamental security gap, because the MCP server runs in a different runtime from the agent’s sandbox.

The agent’s sandbox enforces CWD restrictions on direct file operations. But MCP tool calls bridge into a different runtime — the MCP server’s process — where the agent’s CWD policy does not apply. When the user approves “allow git_add”, they approve a tool invocation, not “read ~/.kube/config into Git history”.
Consider what happens when an agent tries to read a sensitive file directly versus through MCP:
sequenceDiagram
participant A as AI Agent
participant SB as Agent Sandbox
participant U as User
participant M as MCP Git Server
participant F as ~/.kube/config
rect rgb(232, 245, 233)
Note over A,SB: Direct file read
A->>SB: read_file("~/.kube/config")
SB-->>A: DENIED: outside CWD
end
rect rgb(255, 235, 238)
Note over A,F: Same file via MCP
A->>U: Allow git_add?
U->>A: Approved
A->>M: git_add(files=["../../../.kube/config"])
Note over M: Separate runtime, no CWD check
M->>F: GitPython reads file
M-->>A: "Files staged successfully"
A->>M: git_show("HEAD")
M-->>A: Raw credential content
end
This is why capability laundering is effective: the approval mechanism gates the tool call, but the effect happens in a security domain where the agent’s restrictions do not exist.
The Generalized Pattern (Refined from Three Cases)
| Case | Tool Label | Actual Effect | Boundary Bypassed |
|---|---|---|---|
| Memory MCP | Memory storage | Arbitrary JSON file write | Approval gate |
| Git MCP (CVE-2025-68143) | Git init | File read | CWD boundary |
| Git MCP git_add (CVE-2026-27735) | Stage files | File read | CWD boundary |
Parts 2 and 3 both bypass the CWD boundary, but through different mechanisms. Part 2 moves the repository to the target; Part 3 reaches out from the repository to the target.
Technical Root Cause
The Vulnerable Code
The vulnerable git_add implementation (source):
def git_add(repo: git.Repo, files: list[str]) -> str:
if files == ["."]:
repo.git.add(".") # Uses Git CLI — safe
else:
repo.index.add(files) # Uses GitPython — vulnerable
return "Files staged successfully"
When files == ["."], the code uses repo.git.add() which calls Git CLI (safe). For all other inputs, it uses repo.index.add() which calls GitPython’s library API (vulnerable).
Why Git CLI Is Safe But GitPython Is Not
Git CLI validates that paths are within the repository:
$ cd /tmp/test_repo
$ git add ../../../.kube/config
fatal: '../../../.kube/config' is outside repository at '/tmp/test_repo'
GitPython’s index.add() does not:
repo = git.Repo("/tmp/test_repo")
repo.index.add(["../../../.kube/config"]) # Succeeds silently
The root cause is in GitPython’s _to_relative_path():
def _to_relative_path(self, path):
if not osp.isabs(path):
return path # Relative paths bypass ALL validation
Relative paths like ../../../.kube/config skip validation entirely, then get processed through _store_path() which reads the file content into the Git object database.
The --repository parameter does not help — it restricts which repos can be accessed, but the path traversal happens inside GitPython after the repository access check.
Proof of Concept
Step 1: Git CLI Correctly Rejects Path Traversal

$ cd /tmp/test_repo
$ git add ../../../Users/aonanguan/.kube/config
fatal: '../../../Users/aonanguan/.kube/config' is outside repository
Step 2: MCP git_add Bypasses the Boundary

{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"git_add","arguments":{"repo_path":"/tmp/test_repo","files":["../../../Users/aonanguan/.kube/config"]}}}
Response: {"text":"Files staged successfully","isError":false}
Step 3: File Added but Invisible in Working Directory

$ ls /tmp/test_repo
test.txt # Working directory looks clean
$ git ls-files
../../../Users/aonanguan/.kube/config # But kubeconfig is in the index
test.txt
No .git directory left in ~/.kube, no visible trace anywhere.
Step 4: Extract Credentials via Git History

{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"git_commit","arguments":{"repo_path":"/tmp/test_repo","message":"add config"}}}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"git_show","arguments":{"repo_path":"/tmp/test_repo","revision":"HEAD"}}}
The response contains the complete kubeconfig including AWS EKS cluster endpoint, certificate authority data, and cluster ARN.
Fix
As described in the advisory:
In
mcp-server-gitversions prior to2026.1.14, thegit_addtool did not validate that file paths provided in thefilesargument were within the repository boundaries. The tool used GitPython’srepo.index.add(), which did not enforce working-tree boundary checks for relative paths. The fix switches torepo.git.add(), which delegates to the Git CLI and properly rejects out-of-tree paths.
I reported this vulnerability and contributed the fix (commit db96050, PR #3164). Users should upgrade to 2026.1.14 or newer.
Timeline
- 2024-11-21: Vulnerability introduced (commit 100323a — switched from
repo.git.add()torepo.index.add()) - 2025-12-29: Vulnerability discovered and verified; fix contributed (commit db96050)
- 2026-02-25: GHSA-vjqx-cfc4-9h6v published with CVE-2026-27735
Conclusion
This is the third documented case of capability laundering in MCP ecosystems:
Case 1 (Memory MCP): Memory storage → Config injection → Terminal hijacking Case 2 (Git MCP, CVE-2025-68143): Git init → Credential exfiltration → Secret theft Case 3 (Git MCP git_add, CVE-2026-27735): Stage files → Path traversal → Credential exfiltration
All three follow the same pattern, but break different boundaries:
- Memory MCP: Bypassed approval gates (arbitrary JSON schema written without approval)
- Git MCP (git_init): Bypassed CWD boundary (created repos outside workspace)
- Git MCP (git_add): Bypassed CWD boundary (read files outside repo via library flaw)
The Emerging Pattern
Capability laundering is a systemic security pattern in MCP ecosystems:
- Tools are trusted by labels (“memory”, “git”)
- Implementations exceed contracts
- Effects bypass capability controls
- MCP servers run outside the agent’s sandbox — by design, not by accident
This third case adds a new dimension: library APIs are not equivalent to CLI tools. GitPython’s index.add() does not enforce the same boundaries as git add. MCP servers that wrap libraries inherit their flaws silently.
Beyond these three cases, the Git MCP Server alone revealed multiple capability laundering vulnerabilities:
- CVE-2025-68143:
git_initbypasses CWD boundary - CVE-2025-68144:
git_diff/git_checkoutargument injection enables arbitrary file writes - CVE-2025-68145: Path validation bypass when using
--repositoryflag - CVE-2026-27735 (this article):
git_addpath traversal via GitPython library flaw
All four demonstrate the same pattern: Git tools laundering capabilities beyond their documented contracts.
For the Ecosystem
Server developers: Tool contracts are security boundaries. Do not trust library APIs to enforce them — validate explicitly.
Client developers: Gate effects, not tool names. Recognize that MCP tool execution bridges into a separate runtime where the agent’s sandbox does not apply.
Security researchers: Look for capability laundering wherever tool contracts are vague and implementations are powerful. Pay special attention to mismatches between CLI tools and their library equivalents.
The fix is in 2026.1.14. But the lesson is broader: MCP needs effect-based capability accounting.
This is part of an ongoing series on capability laundering in MCP:
- Part 1: Memory MCP Server Terminal Hijacking
- Part 2: Git MCP Server Credential Exfiltration (CVE-2025-68143)
- Part 3: Git MCP Server git_add Path Traversal (this article)
References:
- Advisory: GHSA-vjqx-cfc4-9h6v
- CVE: CVE-2026-27735
- Fix: commit db96050 / PR #3164
- Introduced: commit 100323a
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory
- GitPython
_to_relative_path()source - GitPython
index.add()source
Related Git MCP Server vulnerabilities:
- CVE-2025-68143 / GHSA-5cgr-j3jf-jw3v:
git_initCWD boundary bypass - CVE-2025-68144 / GHSA-9xwc-hfwc-8w59:
git_diff/git_checkoutargument injection - CVE-2025-68145 / GHSA-j22h-9j4x-23w5:
--repositorypath validation bypass
Aonan Guan | Security Researcher | LinkedIn | GitHub | Related work in Microsoft Agentic Web Featured by The Verge
