{"record":{"id":"d44249779160ca56","repo":"modelcontextprotocol/servers","slug":"repository-path-repo-path-is-outside-the-allow","errorCode":null,"errorMessage":"Repository path '{repo_path}' is outside the allowed repository '{allowed_repository}'","messagePattern":"Repository path '(.+?)' is outside the allowed repository '(.+?)'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/git/src/mcp_server_git/server.py","lineNumber":268,"sourceCode":"    return \"\".join(output)\n\ndef validate_repo_path(repo_path: Path, allowed_repository: Path | None) -> None:\n    \"\"\"Validate that repo_path is within the allowed repository path.\"\"\"\n    if allowed_repository is None:\n        return  # No restriction configured\n\n    # Resolve both paths to handle symlinks and relative paths\n    try:\n        resolved_repo = repo_path.resolve()\n        resolved_allowed = allowed_repository.resolve()\n    except (OSError, RuntimeError):\n        raise ValueError(f\"Invalid path: {repo_path}\")\n\n    # Check if repo_path is the same as or a subdirectory of allowed_repository\n    try:\n        resolved_repo.relative_to(resolved_allowed)\n    except ValueError:\n        raise ValueError(\n            f\"Repository path '{repo_path}' is outside the allowed repository '{allowed_repository}'\"\n        )\n\n\ndef git_branch(repo: git.Repo, branch_type: str, contains: str | None = None, not_contains: str | None = None) -> str:\n    # Defense in depth: reject values starting with '-' to prevent flag injection\n    if contains and contains.startswith(\"-\"):\n        raise BadName(f\"Invalid contains value: '{contains}' - cannot start with '-'\")\n    if not_contains and not_contains.startswith(\"-\"):\n        raise BadName(f\"Invalid not_contains value: '{not_contains}' - cannot start with '-'\")\n\n    match contains:\n        case None:\n            contains_sha = (None,)\n        case _:\n            contains_sha = (\"--contains\", contains)\n\n    match not_contains:","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/modelcontextprotocol/servers/blob/76d64c822f5125032f89eb71dbdb94e42b434821/src/git/src/mcp_server_git/server.py#L250-L286","documentation":"Thrown by validate_repo_path (server.py:252-270) when the client's 'repo_path' argument resolves to a location that is neither the same as nor a subdirectory of the repository the server was started with via --repository. It is a security guard: the server holds a single allowed root (plus MCP Roots) and refuses to open git repos outside it to block path traversal. Both paths are resolved (symlinks expanded) before Path.relative_to tests containment.","triggerScenarios":"A tool call whose 'repo_path' is a sibling or parent of the configured root, an absolute path to a different repo, or a path whose symlink target escapes the allowed root. Also fires when --repository points at a dir but the client passes the enclosing workspace (or vice versa).","commonSituations":"Server started with 'mcp-server-git --repository /home/me/proj' while the editor/agent sends '/home/me/other-repo' or the workspace parent '/home/me'. Symlinked HOME dirs. Multi-repo workspaces where only one repo was registered (use Roots instead).","solutions":["Start the server with --repository pointing at the exact git working tree the client will request.","Call the list_repositories tool first and pass only a path it advertises (the root or a subdir).","If you need more than one repo, register each via MCP Roots rather than a single --repository.","Resolve symlinks in the client path before sending, and ensure the target stays under the allowed root.","If using a launcher/IDE integration, confirm it forwards the same repo path it used to start the server."],"exampleFix":"// before\n//   server: mcp-server-git --repository /home/me/code\n//   client call: {\"repo_path\": \"/home/me/other-repo\"}  -> error\n// after\n//   server: mcp-server-git --repository /home/me\n//   (or add /home/me/other-repo as an MCP Root)","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef assert_repo_allowed(repo_path: str, allowed_roots: list[str]) -> None:\n    p = Path(repo_path).resolve()\n    for root in allowed_roots:\n        r = Path(root).resolve()\n        if p == r or r in p.parents:\n            return\n    raise ValueError(f\"{repo_path} not under any allowed root {allowed_roots}\")\n\n# call after fetching advertised repos via the list_repositories tool","typeGuard":"from pathlib import Path\n\ndef is_allowed_repo(repo_path: str, allowed_roots: list[str]) -> bool:\n    p = Path(repo_path).resolve()\n    return any(\n        p == Path(r).resolve() or Path(r).resolve() in p.parents\n        for r in allowed_roots\n    )","tryCatchPattern":null,"preventionTips":["Start the server with --repository equal to the exact path the client will send.","Use MCP Roots (not multiple --repository) for multi-repo access.","Resolve symlinks client-side before comparing paths.","Drive the client's repo_path from list_repositories output, never hardcode it."],"tags":["git","security","path-traversal","python","config"],"backgroundTag":null,"analyzedSha":"76d64c822f5125032f89eb71dbdb94e42b434821","analyzedAt":"2026-08-12T10:02:41.718Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}