Skip to content

Security: MCP hosts lack a Host-header allowlist (DNS rebinding) - Darling + Lite #1648

Description

@erikdarlingdata

Found by the security review in the 2026-07 maintenance pass (#1643).

The defect

DarlingMcpHostService.cs:512-541 installs bearer-token and CIDR gates only when networkMode is true, and neither mode gets a Host-header check.

Compare DarlingWebHostService.cs:391-408, where the guard runs unconditionally in both modes. The web host got this in #1576 with the rationale written into the code:

The loopback surface is tokenless, so a browser ON the host that loads attacker content could be rebound to 127.0.0.1:5153 and read/write the whole surface same-origin.

That reasoning applies identically to the MCP host at :5152 in its default loopback configuration — and the MCP surface is no longer read-only: DarlingMcpCustomViewTools and DarlingMcpServerAdminTools (registered at :490 and :501) add view CRUD plus add_servers/remove_server, and DarlingMcpAlertTools adds alert-config writes.

The application/json content type would normally force a CORS preflight, but under a rebind the browser treats the request as same-origin, so no preflight applies — which is exactly what the Host check defends against. ModelContextProtocol.AspNetCore 1.4.0 does not add this itself; per the MCP spec it is the application's responsibility.

Failure scenario

An operator browsing from the Darling host loads an attacker page. The attacker's domain re-resolves to 127.0.0.1 and their JavaScript POSTs to the MCP endpoint, reading the monitoring store (query text, plans, server inventory) and calling the write tools. No token is needed, because loopback mode installs no auth middleware at all.

Fix

Lift DarlingWebHostService.IsAllowedHost into DarlingHostBinding and install it as the first middleware in the MCP host too, in both modes — mirroring DarlingWebHostService.cs:399-408.

Lite parity

Lite/Mcp/McpHostService.cs:45, :103 has the same shape. Lite is loopback-only, single-user, no service-account privilege, so the severity there is informational — but the guard should move into shared code and cover both apps in the same change, per the standing Lite/Darling parity rule.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions