Compare Host and Origin case-insensitively (#3437) - #3456
Conversation
Host names are case-insensitive (RFC 9110 Section 4.2.3) and WHATWG-URL clients (fetch, undici, browsers, mcp-remote) lowercase the URL host before sending. TransportSecurityMiddleware compared the Host and Origin headers against allowed_hosts / allowed_origins case-sensitively, so an uppercase allowlist entry -- the default when deriving it from %COMPUTERNAME% on Windows -- rejected every fetch-based client with 421. Normalize both sides to lowercase in _validate_host and _validate_origin (exact match and the :* wildcard-port loop); keep the original value in the log line. Origin scheme+host are likewise case-insensitive (RFC 6454 Section 4). Assisted by an AI coding tool; reviewed and tested by me.
|
This PR has been closed automatically. This repo only keeps pull requests open when they come from a maintainer, or from a contributor a maintainer has assigned to the linked issue, and you aren't currently assigned to #3437. If a maintainer assigns you to #3437, this PR reopens on its own and there's nothing more you need to do here. Assignment is a maintainer call based on capacity; comments that only ask to be assigned don't factor in. What does help is engaging on the issue itself by confirming the repro, explaining why it matters for your use case, or describing the approach you'd take. You're welcome to keep pushing commits here (just avoid force-pushing, since GitHub can't reopen a rewritten branch), but that on its own won't get the PR reviewed or the issue assigned, and realistically most auto-closed PRs stay closed. There's no need to open a new PR either way. CONTRIBUTING.md has the full reasoning, but in short:
Maintainers: reopen, remove |
Fixes #3437
Problem
TransportSecurityMiddleware._validate_host()and_validate_origin()comparethe incoming
Host/Originheader againstallowed_hosts/allowed_originscase-sensitively — both the exact-match check and the
:*wildcard-port loop.Host names are case-insensitive (RFC 9110 §4.2.3), and every WHATWG-URL based
client (
fetch, undici, browsers, and thereforemcp-remote) lowercases the URLhost before sending. On Windows this is the default path into the bug:
%COMPUTERNAME%is uppercase, so derivingallowed_hostsfrom the machine nameyields
MYHOST:*, the client sendshost: myhost:8000, and the server returns421 to every request while configured exactly as intended.
Fix
Normalize both sides to lowercase in
_validate_hostand_validate_origin(exact match and the wildcard-port loop). The original header value is kept in
the
logger.warningline for observability. Origin scheme + host are likewisecase-insensitive (RFC 6454 §4); an origin has no path component so lowercasing
the whole string is safe.
Tests
tests/server/test_transport_security.py:test_validate_request_checks_host_then_origin(uppercase request vs lowercase allowlist, host + origin, exact + wildcard).
test_validate_host_case_insensitive_with_uppercase_allowlist— theWindows scenario:
allowed_hosts=["MYHOST:*"]acceptsmyhost:8000andMYHOST:8000, still rejectsother:8000.Local:
uv run pytest tests/server/test_transport_security.py tests/server/test_sse_security.py tests/server/test_streamable_http_security.py→ 71 passed.
ruff check,ruff format --check,pyrightclean.Notes
main(v2). Happy to open the equivalent against the v1 branch.are owned by me.