Skip to content

Security hardening: MCP server auth + client URL validation#403

Draft
corinagum wants to merge 1 commit intomainfrom
cg/mcp-auth-hardening
Draft

Security hardening: MCP server auth + client URL validation#403
corinagum wants to merge 1 commit intomainfrom
cg/mcp-auth-hardening

Conversation

@corinagum
Copy link
Copy Markdown
Contributor

Summary

Addresses security scan findings #14 (MCP server mounted without auth) and #15 (MCP client accepts arbitrary URLs). Python half of a 3-SDK PR set (TS + C# are separate PRs on the same branch name).

#14 — MCP server auth

New opt-in require_auth parameter on McpServerPlugin:

```python
McpServerPlugin(
name="my-mcp",
require_auth=lambda req: req.headers.get("authorization") == "Bearer ...",
)
```

Hook accepts sync or async (Request) -> bool. False/raise → 401 via a thin ASGI middleware wrapping the mounted FastMCP app; lifespan events pass through untouched. When unset, all requests are accepted and a one-time startup warning fires when the server mounts.

#15 — MCP client SSRF

New url_validation.py gate, wired into _fetch_tools_from_server and _call_mcp_tool before transport creation. Two new fields on McpClientPluginParams:

  • allow_private_network: bool = False — block RFC1918, loopback, link-local, IPv6 unique-local
  • validate_url: Optional[Callable[[str], Union[bool, Awaitable[bool]]]] — when set, fully replaces the default scheme + host-network checks

Default policy: scheme ∈ {http, https}; if not allow_private_network, resolve host via loop.getaddrinfo and reject private addresses. IP literals short-circuit DNS. Uses Python's ipaddress stdlib for classification.

Behavior change to be aware of

MCP client calls that previously pointed at localhost / on-prem MCP servers will now fail with UrlValidationError unless allow_private_network=True is set on the call's params. Set the flag for intentional on-prem deployments.

Design docs

  • design/mcp-server-auth-options.md
  • design/mcp-client-ssrf-options.md

E2E verified

  • Middleware gating: /mcp returns 401 without auth, 400 (FastMCP "Missing session ID") on correct bearer (past middleware)
  • Activity path: POST /api/messages routed to the handler (skip_auth toggle used for the synthetic request); middleware did not intercept
  • Startup warning fires when require_auth is unset; silent when set

Note on DNS rebinding

Default private-network filter rejects at registration time but does not prevent DNS rebinding. Known residual risk; call out if follow-up work is wanted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant