Skip to content

fix(security): warn on empty allowed_hosts and improve 421/403 response bodies#2694

Open
BSmick6 wants to merge 1 commit into
modelcontextprotocol:mainfrom
BSmick6:fix/transport-security-empty-allowlist-warning
Open

fix(security): warn on empty allowed_hosts and improve 421/403 response bodies#2694
BSmick6 wants to merge 1 commit into
modelcontextprotocol:mainfrom
BSmick6:fix/transport-security-empty-allowlist-warning

Conversation

@BSmick6
Copy link
Copy Markdown

@BSmick6 BSmick6 commented May 26, 2026

Summary

Fixes #2688.

When TransportSecuritySettings is constructed with enable_dns_rebinding_protection=True (the default) but allowed_hosts=[] (also the default), every incoming request is silently rejected with a bare HTTP 421 Misdirected Request. The cause is invisible without reading the SDK source.

Changes:

  • Adds a model_validator to TransportSecuritySettings that emits a logger.warning at construction time when protection is enabled but allowed_hosts is empty, pointing the user at the configuration needed
  • Includes the received header value and a TransportSecuritySettings(allowed_hosts=[...]) hint in the 421 and 403 response bodies so users can self-diagnose from the response alone
  • Removes all # pragma: no cover markers from transport_security.py by adding tests/server/test_transport_security.py — 21 direct unit tests covering all branches without subprocesses
  • Updates the 6 exact-string assertions in the existing integration tests (test_sse_security.py, test_streamable_http_security.py) to substring matches to accommodate the richer response bodies

Note: this PR overlaps in file scope with #2498 (subdomain wildcard support). There will be conflicts due to both producing tests for transport_security.py

Test plan

  • uv run --frozen pytest tests/server/test_transport_security.py — 21 new unit tests, all pass
  • uv run --frozen pytest tests/server/test_sse_security.py tests/server/test_streamable_http_security.py — existing integration tests still pass
  • Full suite: uv run --frozen pytest — 1193 passed, 98 skipped, 1 xfailed
  • uv run --frozen coverage report --include='src/mcp/server/transport_security.py' — 100% branch coverage, no pragmas remaining

🤖 Generated (partially) with Claude Code

…se bodies

When TransportSecuritySettings is constructed with DNS rebinding protection
enabled but allowed_hosts=[], every request is silently rejected with a
bare HTTP 421 — hard to diagnose without reading the SDK source.

Add a model_validator that emits a logger.warning at construction time
pointing users at allowed_hosts configuration. Also include the received
header value and a configuration hint in the 421/403 response bodies.

Removes all pragma: no cover markers from transport_security.py by adding
a direct unit test file (test_transport_security.py) that exercises all
branches without subprocesses. Updates the existing integration tests to
use substring matching now that the response bodies carry extra context.

Closes modelcontextprotocol#2688

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

TransportSecuritySettings.allowed_hosts=[] (default) silently rejects external Host headers with HTTP 421

1 participant