The Exploit
An unauthenticated attacker only needs network access to send a crafted Host header.
curl -v -H "Host: foo/bar" http://TARGET/admin
The request is routed to the actual path /admin, but Starlette rebuilds request.url from the malformed Host header. If an app or middleware uses request.url.path for access control, the response can return guarded content while the URL-derived path is wrong.
What the Patch Did
Before:
if host_header is not None:
url = f"{scheme}://{host_header}{path}"
After:
_HOST_RE = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\])(?::[0-9]+)?$", re.IGNORECASE)
if host_header is not None and _HOST_RE.fullmatch(host_header):
url = f"{scheme}://{host_header}{path}"
The patch added strict Host header validation using a compiled regex against RFC-style host grammar before rebuilding request.url. Invalid headers are ignored instead of being interpolated directly into the reconstructed URL.
Root Cause
This is an input validation flaw (CWE-20) in URL construction: attacker-controlled scope["headers"] "host" was concatenated into f"{scheme}://{host_header}{path}" without checking whether host_header was a valid authority. The malformed Host header therefore crossed the trust boundary into the reconstructed request.url, producing a URL path inconsistent with the actual routing path in scope["path"]. Middleware or endpoints that used request.url.path instead of the raw ASGI scope path could be bypassed.
Why It Works
The load-bearing change is _HOST_RE.fullmatch(host_header). Without that line, the vulnerable string interpolation still accepts Host: foo/bar and produces http://foo/bar/admin, so request.url.path becomes /bar/admin instead of /admin. The added regex check is the actual defense; the import and constant exist only to compile the pattern once and make the validation readable. The intent is: if Host is syntactically invalid, ignore it and fall back to the ASGI server tuple rather than trusting attacker-controlled header content.
Hardening Checklist
- Validate
Hostagainst RFC 9112 / RFC 3986 authority syntax before reconstructing request URLs. - Never use reconstructed
request.urlfor routing or access-control decisions; prefer raw ASGIscope["path"]/scope["raw_path"]. - If
Hostis rejected, fall back toscope["server"]or an application-configured canonical host instead of string concatenating the invalid header. - Use a compiled regex or
urllib.parseparser for authority validation rather than manual string concatenation. - Treat
Hostheader values as untrusted input in any middleware that enforces security based on URL semantics.
References
- https://nvd.nist.gov/vuln/detail/CVE-2026-48710