Skip to content

Host-validation hardening: no startup log of effective state, and unparsable entries silently deny all #117

Description

@plusky

Three related gaps from #104's review, all fail-closed but operator-hostile: (1) nothing logs at startup whether Host validation ended up on or off, or with what list — a typo'd MCP_ALLOWED_HOSTS leaves validation silently off; (2) entries rmcp cannot parse as an authority (*, http://a.example, a;b, encoded commas, zero-width unicode) enable validation with nothing matchable — deny-all with only rmcp's per-request warn as signal; * (an operator writing 'any') is the plausible brick; (3) whitespace splitting manufactures authorities from typos ('a b.example' → two hosts, one single-label — meaningful in a k8s namespace). One startup info line naming the resolved list plus a startup rejection or warning for unparsable entries closes all three. allowed_origins additionally has no knob at all. Found during #104's review.

All three probes re-verified on main at 5e1611d (post-#104/#105, 2026-08-18): resolved_allowed_hosts still splits on commas and whitespace and drops only empty entries; http_server_config guards only the all-empty case and hands a non-empty list straight to with_allowed_hosts, where rmcp 3.1.2 drops unparsable entries via filter_map(parse_allowed_authority) at match time — deny-all, with only the per-request "rejected request with disallowed Host header" warn as signal. Startup logs the API-key custody mode but nothing about Host validation, and no allowed_origins knob exists.

Acceptance criteria

  • Startup emits one info line stating whether Host validation is on or off and, when on, the resolved list verbatim — a typo'd MCP_ALLOWED_HOSTS becomes visible without sending a request (hosts only, never key material — I12).
  • An entry rmcp cannot parse as an authority (*, a scheme-carrying URL, a;b, encoded commas, zero-width unicode) is refused at startup, or at minimum warned about by name at startup — never a silent deny-all discovered one 403 at a time.
  • The a b.example typo cannot silently become two authorities: either whitespace splitting goes, or a single entry that splits into several is called out in the startup line.
  • allowed_origins gets a knob with the same shape as MCP_ALLOWED_HOSTS, or DESIGN.md records why it deliberately has none.
  • Empty/unset keeps meaning "validation off" (MCP_ALLOWED_HOSTS= reads as unset) — the on/off contract itself is unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsecurityGuard, key custody, or disclosure surface

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions