Repository navigation
fix(git): redact query values and the fragment in git_remote URLs - #5533
Conversation
redact_url only redacted a URL's userinfo, so a token in a query parameter (?private_token=...) or the fragment was returned as is, short of the tool's "credentials are shown as <redacted>" contract. Every query value and a non-empty fragment are now redacted too, for any scheme:// URL but file://, whose ? and # belong to a file name. A token in a path segment cannot be told from a repository name, so the tool description and README now say path segments are kept. Closes #5508 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
|
| Tools | Prompts | Resources | Resource templates | |
|---|---|---|---|---|
| Base | 14 | 2 | 0 | 0 |
| Head | 14 | 2 | 0 | 0 |
tools
--- base/tools.json
+++ target/tools.json
- tools[git_remote].description: "Lists the repository's remotes with their fetch and push URLs, like `git remote -v`. Credentials emb..."
+ tools[git_remote].description: "Lists the repository's remotes with their fetch and push URLs, like `git remote -v`. Credentials emb..."Updated by this run for 0bd4cf90ad004fbcaaa1e2646e1f84e02a4cee13.
There was a problem hiding this comment.
🟡 Changes recommended
The URL redactor still leaks credential content in pathless URLs whose query or fragment contains @, requiring conservative redaction and regression tests for these cases.
1 open issue
What changed in this PR
This PR addresses #5508 by extending the Git server’s git_remote URL redaction beyond userinfo to query values and fragments.
Changes:
- Redacts query values, bare query parameters, and non-empty fragments, including inside remote-helper URLs.
- Preserves path segments and
file://filename characters, with documented exceptions. - Adds regression tests and updates the tool-description snapshot.
| File | Description |
|---|---|
src/git/tests/test_remote.py |
Adds URL edge cases and a protocol-level redaction test. |
src/git/tests/snapshots/list_tools.json |
Updates the expected git_remote description. |
src/git/src/mcp_server_git/server.py |
Implements query and fragment redaction and revises tool wording. |
src/git/README.md |
Documents expanded redaction and its boundaries. |
Review effort: Balanced
The userinfo runs to the last '@' before the first '/', so in a URL like https://host?t=a@SECRET it swallowed the query delimiter and the part of the credential after the '@' was shown as the host. A '?' or '#' there may be in a password or may start a query or fragment, so no split of it is safe: such a URL is now shown as scheme://<redacted>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
Copilot round 1: one finding, fixed in 0bd4cf9.
|
|
| Tools | Prompts | Resources | Resource templates | |
|---|---|---|---|---|
| Base | 22 | 4 | 8 | 5 |
| Head | 22 | 4 | 8 | 5 |
Updated by this run for 0bd4cf90ad004fbcaaa1e2646e1f84e02a4cee13.
|
Copilot round 2: clean (no open issues; the round-1 finding is shown as resolved). The review loop has converged. |

Closes #5508
Description
git_remotepromised that credentials embedded in a URL are shown as<redacted>, butredact_urlonly redacted the URL's userinfo. A token in a query parameter or the fragment came back as configured.redact_urlnow also redacts, for everyscheme://URL (including the inner URL of a<transport>::<url>remote-helper address):?private_token=<redacted>&ref=<redacted>). No list of credential-like names is complete, so none is kept; the names stay so the URL still reads as what it is. A parameter with no=(?<token>) is redacted whole.#<redacted>). The fragment starts at the first#, which also ends the query.Two things are deliberately kept as configured, and the tool description and README now say so:
https://host/token/SECRET/r.git): a token there cannot be told apart from a repository name, which is the boundary the issue allows for.file://URLs'?and#, which are characters of a local file name, not a query or fragment. Their userinfo is still redacted as before.A URL where a
?or#comes before the userinfo's@is shown asscheme://<redacted>. The userinfo runs to the last@before the first/, so inhttps://host?t=a@SECRETthe?might start a query holding an@, or might be in a password (https://user:p?ss@host.example, the edge case the reporter found while probing the prototype on the issue). No split of it is safe, so nothing after the scheme is shown (found in Copilot round 1).Server Details
gitgit_remoteoutput (redact_url), the tool's description (and itslist_tools.jsonsnapshot),src/git/README.mdMotivation and Context
See #5508. Reported by @reservoir-cmyk. Thanks also to @SalyyS1, whose prototype on the issue explored the same space; this PR is an independent implementation that redacts every query value rather than filtering by parameter name.
How Has This Been Tested?
Unit and protocol tests (
src/git/tests/test_remote.py): 23 newredact_urlcases (query values, bare and empty parameters, empty query and fragment,#before?, userinfo + query + fragment,@inside a query value, SSH,?/#before the userinfo's@, with and without a path, a remote-helper URL, a path-segment token,file://), plus a test that drivesgit_remotethrough the MCP client with the issue's two examples. Full git suite: 365 passed;server.pycoverage 100% lines, 99.4% branches.npm run local:gateexit 0.Inspector CLI 2.12.0, both eras, before (
origin/v2/main) and after (this branch). The repository hasoriginfetchinghttps://git.example.com/g/r.git?private_token=SECRET1, pushinghttps://git.example.com/g/r.git#SECRET2, andpathtokathttps://git.example.com/token/SECRET3/r.git:Before,
legacy(2025-11-25) andmodern(2026-07-28,_metacarriesio.modelcontextprotocol/serverInfo), identical:After,
legacyandmodern, identical:LLM client: Claude Code 2.1.322, headless, with only this branch's server configured (
--strict-mcp-config). Prompt: "Use the git server's git_remote tool on the repository at<repo>and reply with exactly the text it returned, verbatim." It calledgit_remoteand returned the "After" listing above. Claude Code does not show which protocol era it negotiated, so the run cannot be labelled with one.Breaking Changes
None. No configuration changes;
git_remoteoutput redacts more.Types of changes
Checklist
npm run changeset) if this changes what a TypeScript server publishes (not applicable: a Python server)🤖 Generated with Claude Code