VYPR
Medium severity6.5NVD Advisory· Published May 26, 2026

CVE-2026-48710

CVE-2026-48710

Description

Starlette is a lightweight ASGI framework/toolkit. Prior to version 1.0.1, the HTTP Host request header was not validated before being used to reconstruct request.url. Because the routing algorithm relies on the raw HTTP path while request.url is rebuilt from the Host header, a malformed header could make request.url.path differ from the path that was actually requested. Middleware and endpoints that apply security restrictions based on request.url (rather than the raw scope path) could therefore be bypassed. Users should upgrade to a version greater than or equal to version 1.0.1, which validates the Host header against the grammar of RFC 9112 §3.2 / RFC 3986 §3.2.2 when constructing request.url and falls back to scope["server"] for malformed values.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Starlette before 1.0.1 does not validate the HTTP Host header, allowing path injection that bypasses security checks relying on request.url.

Vulnerability

Starlette versions 0.8.3 through 1.0.0 (prior to 1.0.1) reconstruct request.url by concatenating the HTTP Host header value with the request path and re-parsing the result. The Host header is not validated against RFC 9112 §3.2 / RFC 3986 §3.2.2 grammar, allowing injection of characters such as /, ?, #, and @ into the host field. This causes request.url.path to differ from the actual path used for routing, which is based on the raw HTTP request line. The vulnerability affects all applications using Starlette, including FastAPI, vLLM, LiteLLM, and many other Python AI tooling projects [1][2][3].

Exploitation

An attacker can send a crafted HTTP request with a malformed Host header containing path components. For example, a request GET /foo HTTP/1.1 with Host: example.com/abc?x= results in Starlette constructing request.url as http://example.com/abc?x=/foo. No authentication or special network position is required; the attack is trivially automatable. The routing algorithm still uses the original path /foo, but any middleware or endpoint that applies security restrictions based on request.url (e.g., path-based authorization) will see the injected path /abc?x=/foo instead [3].

Impact

Successful exploitation allows an attacker to bypass security checks that depend on the reconstructed URL, such as authentication or authorization middleware that validates the path from request.url. This can lead to unauthorized access to protected endpoints, including administrative interfaces, model management UIs, and other sensitive functionality. The discoverers characterize the downstream impact as critical, affecting a large portion of the Python AI ecosystem [1][2].

Mitigation

Users should upgrade to Starlette version 1.0.1 or later, which validates the Host header against the grammar of RFC 9112 §3.2 / RFC 3986 §3.2.2 when constructing request.url and falls back to scope["server"] for malformed values [4]. The fix was released quietly; no workaround is available for unpatched versions. The vulnerability is not currently listed on the CISA Known Exploited Vulnerabilities (KEV) catalog [1].

AI Insight generated on May 26, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

1
764dab0dcfb9

Ignore malformed `Host` header when constructing `request.url` (#3279)

https://github.com/kludex/starletteMarcelo TrylesinskiMay 21, 2026via nvd-ref
2 files changed · +32 8
  • starlette/datastructures.py+6 8 modified
    @@ -1,14 +1,9 @@
     from __future__ import annotations
     
    +import re
     from collections.abc import ItemsView, Iterable, Iterator, KeysView, Mapping, MutableMapping, Sequence, ValuesView
     from shlex import shlex
    -from typing import (
    -    Any,
    -    BinaryIO,
    -    NamedTuple,
    -    TypeVar,
    -    cast,
    -)
    +from typing import Any, BinaryIO, NamedTuple, TypeVar, cast
     from urllib.parse import SplitResult, parse_qsl, urlencode, urlsplit
     
     from starlette.concurrency import run_in_threadpool
    @@ -26,6 +21,9 @@ class Address(NamedTuple):
     # that is, you can't do `Mapping[str, Animal]()["fido"] = Dog()`
     _CovariantValueType = TypeVar("_CovariantValueType", covariant=True)
     
    +# Rejects Host header chars (/, ?, #, @, ...) that would let urlsplit produce a path differing from scope["path"].
    +_HOST_RE = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\])(?::[0-9]+)?$", re.IGNORECASE)
    +
     
     class URL:
         def __init__(
    @@ -48,7 +46,7 @@ def __init__(
                         host_header = value.decode("latin-1")
                         break
     
    -            if host_header is not None:
    +            if host_header is not None and _HOST_RE.fullmatch(host_header):
                     url = f"{scheme}://{host_header}{path}"
                 elif server is None:
                     url = path
    
  • tests/test_datastructures.py+26 0 modified
    @@ -159,6 +159,32 @@ def test_url_from_scope() -> None:
         assert repr(u) == "URL('http://example.com:8000/some/path?query=string')"
     
     
    +@pytest.mark.parametrize(
    +    "host",
    +    [
    +        pytest.param(b"foo/?x=", id="question-mark"),
    +        pytest.param(b"foo/#", id="hash"),
    +        pytest.param(b"foo/bar", id="slash"),
    +        pytest.param(b"user@foo", id="at-sign"),
    +        pytest.param(b"foo\\bar", id="backslash"),
    +        pytest.param(b"foo bar", id="space"),
    +    ],
    +)
    +def test_url_from_scope_with_invalid_host(host: bytes) -> None:
    +    """An invalid Host header should be ignored, falling back to the server tuple."""
    +    u = URL(
    +        scope={
    +            "scheme": "http",
    +            "server": ("example.com", 80),
    +            "path": "/admin",
    +            "query_string": b"",
    +            "headers": [(b"host", host)],
    +        }
    +    )
    +    assert u.path == "/admin"
    +    assert u.netloc == "example.com"
    +
    +
     def test_headers() -> None:
         h = Headers(raw=[(b"a", b"123"), (b"a", b"456"), (b"b", b"789")])
         assert "a" in h
    

Vulnerability mechanics

Root cause

"Missing validation of the HTTP Host header allows injection of path components into the reconstructed URL, causing request.url.path to differ from the actual request path used by the routing algorithm."

Attack vector

An attacker sends an HTTP request with a malformed Host header containing characters such as `/`, `?`, or `#` (e.g., `Host: example.com/abc?bar=`). Starlette reconstructs the URL as `f"{scheme}://{host_header}{path}"` without validating the Host header [ref_id=1]. Because routing uses the raw HTTP path from the request line while `request.url.path` is derived from the reconstructed URL, the two paths diverge. Middleware and endpoints that apply security checks based on `request.url.path` (e.g., an authentication middleware that only allows unauthenticated access to `/`) can be bypassed by making the reconstructed path appear to be a safe path while the actual routed path targets a protected resource [ref_id=1]. No authentication or special network position is required beyond the ability to send arbitrary HTTP headers.

Affected code

The vulnerability is in the `URL.__init__` method in Starlette's `url` module. The code reconstructs the URL as `f"{scheme}://{host_header}{path}"` without validating the Host header value [ref_id=1]. The patch modifies the condition at the point where `host_header` is used, adding a regex check before constructing the URL string [patch_id=2592755].

What the fix does

The patch adds a regular expression `_HOST_RE` that validates the Host header against RFC 9112 §3.2 / RFC 3986 §3.2.2 grammar, rejecting characters such as `/`, `?`, `#`, `@`, `\`, and spaces [patch_id=2592755]. The condition `if host_header is not None` is changed to `if host_header is not None and _HOST_RE.fullmatch(host_header)`, so malformed Host headers are ignored and the code falls back to `scope["server"]` for constructing the URL [patch_id=2592755]. New test cases confirm that invalid Host headers (question mark, hash, slash, at-sign, backslash, space) cause the URL to fall back to the server tuple, keeping `request.url.path` equal to the actual request path [patch_id=2592755].

Preconditions

  • networkAttacker must be able to send arbitrary HTTP requests including a crafted Host header to the Starlette application.
  • configThe application must have middleware or endpoint logic that applies security restrictions based on request.url.path rather than the raw scope path.

Reproduction

1. Install Starlette: `pip install starlette` and an ASGI server such as `pip install uvicorn`. 2. Create a file `poc.py` with the example application from the advisory: a Starlette app with an `AuthMiddleware` that permits only `/` and blocks `/admin`, plus routes for both paths [ref_id=1]. 3. Start the app: `uvicorn poc:app`. 4. Confirm the bypass: `curl -i -H 'Host: foo' localhost:8000/admin` returns `403 Forbidden`, while `curl -i -H 'Host: foo?' localhost:8000/admin` returns `200 OK` and reveals the secret [ref_id=1].

Generated on May 26, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.