Skip to content

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

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

Security hardening: MCP server auth + client URL validation#448
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). .NET half of a 3-SDK PR set (TS + PY are separate PRs on the same branch name).

#14 — MCP server auth

New McpPluginOptions class with an opt-in RequireAuth delegate. New overload on the existing extension method:

```csharp
builder.AddTeamsMcp(options =>
{
options.RequireAuth = ctx =>
Task.FromResult(ctx.Request.Headers.Authorization.ToString() == "Bearer ...");
});
```

Delegate signature: Func<HttpContext, Task<bool>>. false/throw → 401 via an ASP.NET Core middleware path-filtered to /mcp. Parameterless .AddTeamsMcp() overload is unchanged (backward compatible). When RequireAuth is unset, a one-time startup warning fires.

#15 — MCP client SSRF

New UrlValidation.cs gate, called in both FetchToolsFromServer and CallMcpTool before transport creation. Two new fields on McpClientPluginParams:

  • AllowPrivateNetwork: bool = false — block RFC1918, loopback, link-local, IPv6 unique-local / site-local
  • ValidateUrl: Func<Uri, Task<bool>>? — when set, fully replaces the default scheme + host-network checks

Default policy: scheme ∈ {http, https}; if not AllowPrivateNetwork, resolve host via Dns.GetHostAddressesAsync and reject private addresses. IP literals short-circuit DNS. Internal HostResolver seam for testability.

Behavior change to be aware of

MCP client calls that previously pointed at localhost / on-prem MCP servers will now fail with UrlValidationException 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/wrong auth, 400 ("Mcp-Session-Id header is required") on correct bearer (past middleware)
  • Activity path: DevTools chat message accepted (201 on /v3/conversations/devtools/activities); /devtools UI unaffected (200)
  • 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. 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