Skip to content

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

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

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

Conversation

@corinagum
Copy link
Copy Markdown
Collaborator

Summary

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

#14 — MCP server auth

New opt-in requireAuth hook on McpPluginOptions:

```ts
new McpPlugin({
requireAuth: (req) => req.headers.authorization === 'Bearer ...'
})
```

Hook is called once per inbound MCP request; false/throw returns 401 and short-circuits. When unset, all requests are accepted and a one-time startup warning fires when the SSE transport comes up. Path-filtered to /mcp only; the activity route (/api/messages) and other plugins are unaffected.

#15 — MCP client SSRF

New url-validation.ts gate, wired into makeMcpClient before transport build. Two new fields on McpClientPluginParams:

  • allowPrivateNetwork?: boolean (default false) — block RFC1918, loopback, link-local, IPv6 unique-local when false
  • validateUrl?: (url: URL) => boolean | Promise<boolean> — when set, fully replaces the default scheme + host-network checks

Default policy: scheme ∈ {http, https}; if not allowPrivateNetwork, DNS resolve and reject private addresses. IP literals short-circuit DNS.

Behavior change to be aware of

MCP client calls that previously pointed at localhost / on-prem MCP servers will now fail with UrlValidationError unless allowPrivateNetwork: 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, SSE handshake on correct bearer
  • Activity path: bot responded to a devtools chat message with REQUIRE_AUTH=1 active simultaneously
  • Startup warning fires when requireAuth is unset; silent when set

Note on DNS rebinding

Default private-network filter rejects at registration time but does not prevent DNS rebinding (a public hostname that resolves to a private IP at connection time). 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