BadHost — Starlette host-header auth bypass blasts FastAPI, vLLM, LiteLLM, MCP servers (CVE-2026-48710)
TL;DR
CVE-2026-48710 ("BadHost") — Starlette < 1.0.1 builds request.url from the raw HTTP Host header without validation. A single character (/, ?, or #) in Host shifts the path/query/fragment boundaries when the URL is re-parsed, so middleware that authorizes on request.url.path sees a different path than the ASGI router actually dispatched. Any auth middleware reading request.url.path fails open. Starlette ships ~325M downloads/week and sits under FastAPI, vLLM, LiteLLM, Text Generation Inference, OpenAI-compatible proxies, the Python MCP SDK, and most AI-agent dashboards — one bug, one character, an enormous blast radius. X41 D-Sec found it during an OSTIF-sponsored vLLM audit; coordinated disclosure 2026-05-22; patched in Starlette 1.0.1 (2026-05-21).
What happened
Starlette reconstructs request.url by concatenating the HTTP Host header with the request path and re-parsing the result. The Host value is not validated against RFC 9112 § 3.2 / RFC 3986 § 3.2.2 before reconstruction. A malicious client sends a Host header that contains an HTTP-meaningful character — /, ?, or # — and the re-parser splits the URL in a different place than the ASGI server did.
Result: request.url.path returns one string, request.scope["path"] (the actual routed path) returns another. Auth middleware that decides on request.url.path runs its check against the wrong value. The router still dispatches to the protected endpoint.
The minimal PoC published by X41:
GET /admin HTTP/1.1
Host: foo?
…returns 200 OK against a server whose middleware blocks /admin via request.url.path. One character. No credentials.
The bug was found by JJ, Yassine El Baaj, and Markus Vervier at X41 D-Sec during a sponsored source-code audit of vLLM for OSTIF.org funded by the Alpha-Omega Project. Independent reports from ehhthing and Nicolas Lamoureux corroborated. Coordinated disclosure was published 2026-05-22 (badhost.org, OSTIF, X41 advisory X41-2026-002, Starlette GHSA-86qp-5c8j-p5mr), one day after the upstream fix shipped — so operators had effectively zero lead time before the technique was public.
Why this is bigger than one CVE
Starlette is the ASGI foundation under most modern Python AI infrastructure:
| Tool | What it does | Why it inherits BadHost |
|---|---|---|
| FastAPI | The dominant Python web/AI API framework | Built directly on Starlette; any Depends() auth on path is exposed |
| vLLM | Open-source LLM inference server | Public /generate etc. behind path-based auth |
| LiteLLM | OpenAI-compatible proxy → 100+ models | Admin endpoints, master-key gated |
| Text Generation Inference | Hugging Face's inference server | Same |
| MCP Python SDK / FastMCP | Server-side MCP | Path-based tool dispatch |
| OpenAI-compatible proxies | LM Studio, Ollama wrappers, custom shims | Same |
| Agent harnesses / eval dashboards | LangChain Server, OpenHands, etc. | Anything using request.url.path for routing/auth |
Starlette has ~325 M weekly PyPI downloads and 400 K+ dependent GitHub projects — possibly the broadest-surface Python security event of 2026.
"Two parsers, one string" — a recurring class
BadHost is the third recent disclosure where two parts of one stack disagree on how to interpret a string, and the security check runs on the wrong interpretation. Sibling shapes already in this repo:
- Argv-smuggling — Claude Code
eagerParseCliFlagdeeplink RCE: the pre-parser and the main argv parser disagree on which token is a flag value. - Allowlist vs resolver — Claude Code SOCKS5 null-byte bypass: the matcher sees
attacker.com\x00.google.com, the OS truncates at\x00and dialsattacker.com. - Auth-middleware vs ASGI router (BadHost, this advisory): middleware reads
request.url.path(rebuilt fromHost), router readsscope["path"](raw ASGI path), they diverge.
The general lesson: never base a security decision on a reconstructed value when a canonical value is available right next to it. In Starlette, the canonical value is request.scope["path"]. In a CLI, it's the post-parse argv structure. In a network allowlist, it's the bytes the OS will actually pass to connect().
Am I affected?
# 1) Is Starlette < 1.0.1 anywhere in your deps?
pip show starlette 2>/dev/null | grep -E '^(Name|Version):'
pip list 2>/dev/null | grep -i starlette
# Locked versions, all envs:
grep -RniE 'starlette[<=>!~ ]+[0-9]' requirements*.txt pyproject.toml poetry.lock pdm.lock uv.lock 2>/dev/null
# 2) Does any middleware in your code make security decisions from
# request.url, request.url.path, or str(request.url)?
# Each match is a candidate gap.
grep -RnE 'request\.url(\.path)?|str\(request\.url\)' . 2>/dev/null
# 3) Live probe (against your OWN server) — does it serve a protected path
# when Host is "foo?" ?
curl -sk -H 'Host: foo?' -o /dev/null -w '%{http_code}\n' https://your-host/admin
# 200 → vulnerable. 4xx/5xx → likely fine.
# Or use the official scanner:
# https://badhost.org/ (X41 + Persistent Security Industries + Bintech)
IOCs / identifiers
| Type | Value |
|---|---|
| CVE | CVE-2026-48710 |
| GHSA | GHSA-86qp-5c8j-p5mr |
| PYSEC | PYSEC-2026-161 |
| X41 advisory | X41-2026-002 |
| Affected | starlette >= 0.8.3, <= 1.0.0 |
| Fixed | starlette 1.0.1 (released 2026-05-21) |
| Disclosed | 2026-05-22 |
| Trigger payload | Host: foo? (or any /, ?, # in Host) |
| Researcher group | JJ, Yassine El Baaj, Markus Vervier (X41 D-Sec) — OSTIF / Alpha-Omega vLLM audit |
| Official CVSS | 6.5 (Moderate). X41 calls it critical given downstream blast radius. |
If you are affected
-
Upgrade Starlette to ≥ 1.0.1 in every environment that runs FastAPI / vLLM / LiteLLM / MCP server / any ASGI app:
bash pip install --upgrade 'starlette>=1.0.1' # then, in each downstream: pip install --upgrade fastapi vllm litellm 'mcp>=0' text-generationPin the floor inrequirements.txt/pyproject.toml. -
In middleware, replace
request.url.pathwithrequest.scope["path"]anywhere a security decision is made. The scope path comes directly from the ASGI server, not from a reconstructed URL, and it's the value the router actually dispatched. This is the structural fix; the Starlette patch is defense-in-depth on top of it. -
Treat any pre-patch deployment as potentially probed. The scanner at
badhost.orgwas public from disclosure day; assume external scans hit you. Review access logs for unusualHostheaders (Host:containing/,?,#, or other URI-unsafe characters) across 2026-05-21 → your patch date. -
Rotate the credentials that lived behind the path-auth-gated endpoints if you cannot rule out unauthorized access — particularly LLM API keys, MCP server tokens, admin/master keys on LiteLLM, model-management UIs, and any secrets retrievable from now-exposed admin pages.
-
Audit downstream MCP servers separately. A Python MCP server using FastAPI/Starlette and serving over HTTP is reachable from any caller that can hit its
Host; the canonical local-bind defense (the historical "MCP runs on stdio") does not apply once you've exposed an HTTP transport.
Prevention
→ prevention/agent-sandboxing.md
→ prevention/credential-hygiene.md
→ Don't authorize on reconstructed strings. When the framework hands you both a canonical and a derived value, the canonical one is the security boundary. In Starlette: request.scope["path"], not request.url.path. In any auth allowlist: the post-canonicalization form.
→ Validate the Host header at the edge. Put a reverse proxy or ASGI middleware that enforces RFC-grammar Host values before your auth middleware runs.
→ MCP-over-HTTP needs the same network posture as any other public API — auth on every endpoint, allowlists by IP, not "MCP is local-only." (See advisories/2026-05-mcp-stdio-systemic-rce.md for the broader systemic MCP-exposure class.)
Sources
- badhost.org — BadHost: CVE-2026-48710 (X41 D-Sec, Persistent Security Industries, Bintech) — canonical writeup + free scanner.
- OSTIF — Disclosing the BADHOST Vulnerability in Starlette — coordinated-disclosure narrative; vLLM-audit context.
- X41 D-Sec advisory X41-2026-002 — Request Host Header not Validated in Starlette — researcher advisory; technical detail and PoC.
- GitHub Security Advisory — GHSA-86qp-5c8j-p5mr (Kludex/starlette) — upstream advisory + fix.
- Starlette release notes — 1.0.1 — patch details (
Hostvalidated against RFC 9112 / 3986; fall back toscope["server"]). - NVD — CVE-2026-48710 — official CVE entry (CVSS 6.5).
- FastAPI Discussion #15593 — Does GHSA-86qp-5c8j-p5mr affect FastAPI installations using Starlette ≤ 1.0.0? — confirms FastAPI inherits the bug.
- CSO Online — FastAPI-based AI tools exposed to authentication bypass by flaw in Starlette framework — downstream-impact framing.
- Cybersecurity News — Attackers Can Exploit BadHost to Access Sensitive AI Agent Server Endpoints — corroborating timeline.
- GBHackers — BadHost Vulnerability Exposes Sensitive AI Agent Server Endpoints to Attackers — corroboration.
- Cyber Kendra — BadHost (CVE-2026-48710): One Rogue Header Line Unlocks Your Entire AI Stack — corroboration.
- Hacker News thread — BadHost – CVE-2026-48710: Starlette Host-Header Auth Bypass — discussion + reproductions.
- Firethering — A Critical Bug in a 325M-Download Package Put Millions of AI Agents at Risk — scale.
- GIGAZINE — Vulnerability in Starlette Endangers Millions of AI Agents — mainstream coverage.
- HackingPassion — BadHost Breaks Into FastAPI and vLLM With a Single Character — PoC walkthrough.
- AI Weekly — Starlette BadHost flaw breaks AI agent auth — AI-industry coverage.
- Tenable — CVE-2026-48710 — CVE catalog.
- ITdaily — 'BadHost' vulnerability threatens millions of AI agents and MCP servers — MCP angle.