Skip to content

Add Origin header validation for defense-in-depth in Streamable HTTP server #822

Description

@DaleSeo

Background

The Streamable HTTP server transport currently validates only the Host header for DNS rebinding protection (added in #764, shipped in v1.4.0, tracked in #815). The TypeScript, Python, Go, and Java SDK security advisories for the same class of vulnerability all validate bothHostandOrigin, and the MCP transport security note calls out both headers.

Host-only validation is sufficient to block the specific DNS rebinding attack Jonathan Leitschuh reported (the browser cannot forge the Host header after rebinding). However, Origin validation adds defense-in-depth against a separate misconfiguration class: an operator who binds to 0.0.0.0, disables the host allowlist, and serves permissive CORS (Access-Control-Allow-Origin: *). In that setup, a public malicious page could make same-host cross-site requests that pass the Host check but originate from an untrusted origin.

This is not part of the #815 advisory because it is a separate hardening, not a fix for the reported DNS rebinding issue.

Proposal

Extend StreamableHttpServerConfig with an allowed_origins: Vec<String> field, paralleling allowed_hosts:

  • Default: empty → Origin header is not required or validated (preserves current behavior for non-browser clients like the CLI and stdio-bridge test suite that don't send Origin).
  • When non-empty: requests carrying an Origin header must match the allowlist, or are rejected with HTTP 403. Requests without an Origin header are allowed (non-browser clients).
  • Builder methods: with_allowed_origins(&[&str]), disable_allowed_origins().
  • Reuse the parse_allowed_authority / host_is_allowed pattern from tower.rs:195-210.

Acceptance Criteria

  • validate_dns_rebinding_headers checks Origin when allowed_origins is non-empty.
  • Opt-in: default config does not break existing non-browser clients.
  • New regression tests in crates/rmcp/tests/test_custom_headers.rs:
    • Origin on allowlist → 200
    • Origin not on allowlist → 403
    • Missing Origin with non-empty allowlist → 200 (non-browser clients)
    • Missing Origin with require_origin = true (if we add that knob) → 403
  • Docs: update the StreamableHttpServerConfig rustdoc to describe the recommended production configuration (allowlist both Host and Origin when serving browser clients).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-securitySecurity-related changes

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions