fix(mcp): omit credential-bearing paths from failure logs

This commit is contained in:
Joshua Valluru 2026-09-11 07:27:10 -07:00
parent 0ee9e1e448
commit 5b13dfcc59
4 changed files with 18 additions and 6 deletions

View file

@ -410,7 +410,7 @@ def _safe_text(value: str, limit: int = _BODY_PREVIEW_CHARS) -> str:
def safe_upstream_url(url: httpx.URL) -> str:
return _safe_text(str(url.copy_with(username="", password="", query=None, fragment=None)))
return _safe_text(str(url.copy_with(username="", password="", path="/", query=None, fragment=None)))
def _sensitive_field(key: str) -> bool:

View file

@ -518,7 +518,7 @@ async def test_token_exchange_failure_diagnostics(mode, monkeypatch, caplog):
assert not caplog.text
elif mode in {"timeout", "connect"}:
assert isinstance(result, TokenEndpointUnreachable)
assert "POST https://idp/token failed" in caplog.text
assert "POST https://idp/ failed" in caplog.text
else:
assert "POST https://idp/token -> HTTP" in caplog.text
assert "POST https://idp/ -> HTTP" in caplog.text
assert {"denied":"denied", "invalid":"invalid response", "missing":"no access token"}[mode] in caplog.text

View file

@ -233,7 +233,7 @@ class TestDescribeUpstreamHttpFailure:
)
described = describe_upstream_http_failure(exc)
assert described is not None
assert "POST https://upstream.example/apis/mcp -> HTTP 500" in described
assert "POST https://upstream.example/ -> HTTP 500" in described
assert '{"method":"initialize"' in described
assert 'response body: {"error":"boom"}' in described
@ -545,3 +545,15 @@ async def test_streamed_error_redacts_reflected_credentials_before_capture():
assert detail is not None and "invalid_client" in detail and "Rejected" in detail
assert secret not in detail and "REDACTED" in detail
assert await response.aread() == raw
@pytest.mark.parametrize("path", ["/credential-path-value/mcp", "/oauth/credential-path-value/token"])
def test_failure_diagnostics_omit_credential_bearing_url_paths(path):
request = httpx.Request("POST", "https://upstream.example" + path)
response = httpx.Response(401, request=request, json={"error": "access_denied"})
error = httpx.HTTPStatusError("denied", request=request, response=response)
diagnostic = describe_upstream_http_failure(error)
assert diagnostic is not None
assert "credential-path-value" not in diagnostic
assert "POST https://upstream.example/ -> HTTP 401" in diagnostic
assert "access_denied" in diagnostic

View file

@ -456,7 +456,7 @@ async def test_fetch_tools_logs_upstream_request_details_on_500(caplog):
with pytest.raises(MCPServerListError):
await manager._fetch_tools_with_timeout(mock_client, "sample_docs")
assert "POST https://upstream/apis/mcp -> HTTP 500" in caplog.text
assert "POST https://upstream/ -> HTTP 500" in caplog.text
assert '"method":"initialize"' in caplog.text
assert "upstream-token-0123456789" not in caplog.text
@ -473,5 +473,5 @@ async def test_client_creation_failure_logs_sanitized_exchange(monkeypatch, capl
with caplog.at_level(logging.WARNING, logger="LiteLLM"):
with pytest.raises(MCPServerListError):
await manager._get_tools_from_server(server)
assert "POST https://upstream/mcp -> HTTP 500" in caplog.text
assert "POST https://upstream/ -> HTTP 500" in caplog.text
assert "missing_scope" in caplog.text and "query-secret" not in caplog.text