From e820453fa1a372bebc3e8a83b2618af3272c74e5 Mon Sep 17 00:00:00 2001 From: Charlie Tonneslan Date: Wed, 18 Mar 2026 11:18:17 -0400 Subject: [PATCH] fix(git): validate repo_path against current roots on every tool call Previously, call_tool only validated repo_path against the CLI --repository argument. When roots changed at runtime (e.g. client removes a root), a repo that was previously reachable could still be accessed in subsequent tool calls. Now validates repo_path against the full list of allowed repos (both roots and CLI) on every tool call, ensuring root changes revoke access immediately without requiring a restart. Fixes #3613 --- src/git/src/mcp_server_git/server.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/git/src/mcp_server_git/server.py b/src/git/src/mcp_server_git/server.py index 5ce953e545..4a69076993 100644 --- a/src/git/src/mcp_server_git/server.py +++ b/src/git/src/mcp_server_git/server.py @@ -473,8 +473,21 @@ def by_commandline() -> Sequence[str]: async def call_tool(name: str, arguments: dict) -> list[TextContent]: repo_path = Path(arguments["repo_path"]) - # Validate repo_path is within allowed repository - validate_repo_path(repo_path, repository) + # Validate repo_path against both CLI repository AND current roots. + # This ensures root changes at runtime revoke access to removed repos. + allowed_repos = await list_repos() + if allowed_repos: + resolved_repo = str(repo_path.resolve()) + if not any( + resolved_repo == r or resolved_repo.startswith(r + "/") + for r in (str(Path(r).resolve()) for r in allowed_repos) + ): + raise ValueError( + f"Repository path '{repo_path}' is not within any allowed repository" + ) + elif repository is not None: + # Fallback to CLI-only validation if list_repos fails + validate_repo_path(repo_path, repository) # For all commands, we need an existing repo repo = git.Repo(repo_path)