fix(mcp): reject unsafe HTTP URLs before probe

This commit is contained in:
Stellar鱼
2026-06-08 16:03:57 +08:00
committed by Xubin Ren
parent 6e6470daa0
commit ed0aeb1ea9
3 changed files with 119 additions and 8 deletions
+27 -1
View File
@@ -21,6 +21,7 @@ from nanobot.bus.events import (
RUNTIME_CONTROL_MCP_RELOAD,
InboundMessage,
)
from nanobot.security.network import validate_url_target
# Transient connection errors that warrant a single retry.
# These typically happen when an MCP server restarts or a network
@@ -87,12 +88,23 @@ async def _probe_http_url(url: str, timeout: float = 3.0) -> bool:
timeout=timeout,
)
writer.close()
await writer.wait_closed()
with suppress(OSError, asyncio.TimeoutError):
await asyncio.wait_for(writer.wait_closed(), timeout=0.2)
return True
except (OSError, asyncio.TimeoutError):
return False
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})",
request=request,
)
def _windows_command_basename(command: str) -> str:
"""Return the lowercase basename for a Windows command or path."""
return command.replace("\\", "/").rsplit("/", maxsplit=1)[-1].lower()
@@ -595,6 +607,18 @@ async def connect_mcp_servers(
await server_stack.aclose()
return name, None
if transport_type in {"sse", "streamableHttp"}:
ok, error = validate_url_target(cfg.url)
if not ok:
logger.warning(
"MCP server '{}': blocked unsafe URL {} ({})",
name,
cfg.url,
error,
)
await server_stack.aclose()
return name, None
if transport_type == "stdio":
command, args, env = _normalize_windows_stdio_command(
cfg.command,
@@ -626,6 +650,7 @@ async def connect_mcp_servers(
}
return httpx.AsyncClient(
headers=merged_headers or None,
event_hooks={"request": [_validate_mcp_request_url]},
follow_redirects=True,
timeout=timeout,
auth=auth,
@@ -643,6 +668,7 @@ async def connect_mcp_servers(
http_client = await server_stack.enter_async_context(
httpx.AsyncClient(
headers=cfg.headers or None,
event_hooks={"request": [_validate_mcp_request_url]},
follow_redirects=True,
timeout=None,
)