fix(mcp): redact credentials from URLs before logging
MCP server URLs can carry secrets in userinfo (`https://user:token@host/sse`) or a query string (`?token=...`). A few connect/validate paths logged the raw `cfg.url` / `request.url`, so those secrets could land in log files that are often shared or aggregated. Add a small `_redact_url()` helper that keeps only scheme/host/port/path and use it at the four sites that log a server or request URL. Logging only; no other behavior changes.
This commit is contained in:
committed by
Xubin Ren
parent
1873e948c3
commit
780093d037
@@ -166,12 +166,28 @@ async def _probe_http_url(url: str, timeout: float = 3.0) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _redact_url(url: str) -> str:
|
||||
"""Strip credentials and query/fragment before logging an MCP URL.
|
||||
|
||||
Server URLs may embed secrets (``https://user:token@host/sse`` or a
|
||||
``?token=`` query); only scheme, host, port, and path are safe to log.
|
||||
"""
|
||||
try:
|
||||
parts = urllib.parse.urlsplit(url)
|
||||
netloc = parts.hostname or ""
|
||||
if parts.port:
|
||||
netloc = f"{netloc}:{parts.port}"
|
||||
return urllib.parse.urlunsplit((parts.scheme, netloc, parts.path, "", ""))
|
||||
except Exception:
|
||||
return "<redacted-url>"
|
||||
|
||||
|
||||
async def _validate_mcp_request_url(request: httpx.Request) -> None:
|
||||
"""Validate each outgoing MCP HTTP request, including redirect targets."""
|
||||
ok, error = validate_url_target(str(request.url))
|
||||
if not ok:
|
||||
raise httpx.RequestError(
|
||||
f"Blocked unsafe MCP URL {request.url} ({error})",
|
||||
f"Blocked unsafe MCP URL {_redact_url(str(request.url))} ({error})",
|
||||
request=request,
|
||||
)
|
||||
|
||||
@@ -774,7 +790,7 @@ async def connect_mcp_servers(
|
||||
logger.warning(
|
||||
"MCP server '{}': blocked unsafe URL {} ({})",
|
||||
name,
|
||||
cfg.url,
|
||||
_redact_url(cfg.url),
|
||||
error,
|
||||
)
|
||||
await server_stack.aclose()
|
||||
@@ -795,7 +811,7 @@ async def connect_mcp_servers(
|
||||
read, write = await server_stack.enter_async_context(stdio_client(params))
|
||||
elif transport_type == "sse":
|
||||
if not await _probe_http_url(cfg.url):
|
||||
logger.warning("MCP server '{}': {} unreachable, skipping", name, cfg.url)
|
||||
logger.warning("MCP server '{}': {} unreachable, skipping", name, _redact_url(cfg.url))
|
||||
await server_stack.aclose()
|
||||
return name, None
|
||||
|
||||
@@ -822,7 +838,7 @@ async def connect_mcp_servers(
|
||||
)
|
||||
elif transport_type == "streamableHttp":
|
||||
if not await _probe_http_url(cfg.url):
|
||||
logger.warning("MCP server '{}': {} unreachable, skipping", name, cfg.url)
|
||||
logger.warning("MCP server '{}': {} unreachable, skipping", name, _redact_url(cfg.url))
|
||||
await server_stack.aclose()
|
||||
return name, None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user