BadHost: One Character Bypasses Host-Based Security Across Most of the Python AI Stack
CVE-2026-48710 /
GHSA-86qp-5c8j-p5mr /
X41-2026-002
A single character injected into the HTTP Host header bypasses path-based authorization in Starlette, the routing core of FastAPI. Through FastAPI, this primitive (now tracked as CVE-2026-48710 and branded BadHost by the discoverers) reaches a large segment of the Python AI tooling ecosystem: vLLM (where the bug was discovered), LiteLLM, Text Generation Inference, most OpenAI-shim proxies, MCP servers, agent harnesses, eval dashboards, and model-management UIs. The exploit primitive is one character. The fix is Starlette 1.0.1, quietly released with a CVSS 6.5 Moderate score that materially understates the downstream impact. The discoverers themselves characterize the bug as critical.
A free online scanner for CVE-2026-48710 is available at badhost.org, run jointly by X41 D-Sec, Persistent Security Industries, and Bintech.
At a glance
| CVE | CVE-2026-48710 (BadHost) |
| Downstream severity | Critical (per the discoverers) |
| Upstream severity, as scored | Moderate (CVSS 6.5) — understated |
| Exploitability | Trivial. One malformed header. |
| Auth required | None |
| Automatable | Yes. Suitable for mass scanning. |
| Discovered in | A source code audit of vLLM by X41 D-Sec |
| Online scanner | badhost.org |
| Patch | Starlette 1.0.1 (quietly released) |
| Affected | starlette >= 0.8.3, < 1.0.1 and everything that depends on it |
Known-impacted downstream stacks: FastAPI (the dominant downstream consumer of Starlette and the foundation of most modern Python web and AI services), LiteLLM proxy, vLLM (the project the bug was discovered against), Text Generation Inference and related wrappers, most "OpenAI-API-shim" projects fronting local model runners, a large number of MCP servers, agent harnesses, eval dashboards, model registries, and internal admin panels built on FastAPI.
The bug
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 the RFC 9112 / RFC 3986 grammar before reconstruction. A Host header containing /, ?, or # shifts the path, query, and fragment boundaries during re-parse, so request.url.path no longer matches the path the ASGI server actually received and routed against.
The router dispatches on the real wire path. Middleware sees the poisoned, re-parsed path. Any path-based security decision made in middleware can be bypassed while the underlying route still executes.
Minimal proof of concept:
curl -i -H 'Host: foo' http://target/admin # 403, blocked
curl -i -H 'Host: foo?' http://target/admin # 200, served
The exploit primitive is one character.
Severity: scored low, lands hard
The upstream Starlette advisory was published with a CVSS score of 6.5 (Moderate), characterizing the issue strictly at the library layer as a path string mismatch. The patch shipped quietly, without an accompanying ecosystem-wide warning. That framing materially understates the downstream impact. The discoverers, who actually examined what the primitive does to consumers of the library, characterize CVE-2026-48710 as critical.
This is a primitive, not an outcome. What it costs the ecosystem is determined by what downstream consumers do with request.url. X41's analysis found multiple popular open source projects whose middleware gates security-relevant decisions on request.url, with demonstrated chains from this single-character primitive to authentication bypass, SSRF, and remote code execution.
Why this matters to the LLM and AI ecosystem
FastAPI is built on Starlette, and FastAPI is the base layer for the bulk of the Python AI plumbing deployed today. The transitive blast radius is not "a Python web framework"; it is most of the model-serving, gateway, proxy, eval, agent, and MCP-server infrastructure that has been stood up in the last two years.
Four points worth keeping in mind:
- The bug was found in vLLM. X41 D-Sec discovered it during a source code audit of vLLM sponsored by OSTIF.org, not while looking at Starlette. The path from "Starlette quirk" to "LLM-serving primitive" is not theoretical; it is the discovery path.
- Lab-style deployments expose the bug. These services are routinely deployed direct-to-
uvicorn(or hypercorn, daphne, granian) on internal networks, lab subnets, workstations, and bench hardware. The reverse-proxy mitigation that protects production websites is frequently absent in research, eval, and dev deployments. - The endpoints being protected are high-value: admin routes, model management, key issuance, prompt and tool configuration, finetune submission, dataset upload, and shell-adjacent agent tooling.
- The bar for automation is on the floor. One character in a request header is drop-in compatible with any existing internet scanning toolchain and with any agentic or automated reconnaissance pipeline.
Impact
Where Starlette-based middleware (including FastAPI middleware) enforces authorization or routing restrictions using request.url or request.url.path, the following should be assumed reachable by an unauthenticated remote attacker:
- Bypass of path-prefix authentication (
/admin,/v1/models,/internal,/metrics,/shutdown, tool-execution endpoints) - Bypass of tenant or workspace scoping enforced in middleware
- Smuggling of requests into endpoints intended to be reachable only from authenticated sessions or internal hops
- SSRF against cloud metadata services and internal hosts where the gated endpoint performs outbound fetches
- RCE where the gated endpoint exposes tool execution, plugin loading, arbitrary model loading from URL, file upload, or code-eval style functionality
For LLM gateways specifically, the admin and key-management surface of services like LiteLLM, and the model and runtime control surface of services like vLLM, must be treated as exposed if the deployment is direct-to-ASGI.
Exposure check
The fastest way to test a reachable endpoint is the BadHost online scanner: badhost.org. For systematic review, you are likely exposed if any of the following are true:
- You run any FastAPI or Starlette application directly on
uvicorn,hypercorn,daphne, orgranianwithout an HTTP/1.1-compliant reverse proxy in front. - You run LiteLLM, vLLM, or similar LLM proxies and servers as the directly-reachable HTTP endpoint.
- You terminate HTTP/3 or QUIC at a frontend whose Host validation behavior you have not verified.
- Your application reads
request.url.pathin any authentication, authorization, audit, rate-limiting, or routing-decision code. - Any internal-only service is reachable from a workstation, VPN subnet, lab network, or other "we trust this network" segment.
Mitigation
Primary
Upgrade Starlette to 1.0.1 or later. Rebuild and redeploy every container, virtualenv, and bundled artifact that pins or vendors Starlette. Bundled installs are common in LLM tooling; pip list on the host is not enough. Audit images.
Secondary (defense in depth)
- Replace
request.urlandrequest.url.pathwithrequest.scope["path"]in every middleware, dependency, and decorator that makes security decisions. Grep the codebase. This bug class will recur; reading the un-reconstructed value is the durable fix. - Place a reverse proxy that rejects malformed
Hostheaders in front of every ASGI-served application. nginx, Apache httpd, and Cloudflare reject the PoC by default. Verify your specific config. - For HTTP/3-terminated frontends, test
Hostheader handling explicitly with the X41 PoC before relying on the proxy as a mitigation.
Detection and scanning
Three options, in order of effort:
- Online scanner. The BadHost project, run jointly by X41 D-Sec, Persistent Security Industries, and Bintech, offers a free remote scanner for any reachable HTTP endpoint at badhost.org. Suitable for quick triage of a few services.
- Source code scanning. X41 has published a scanner, Semgrep rules, and CodeQL queries at github.com/x41sec/poc/tree/master/starlette-host-header. Run these across any Python codebase that touches Starlette or FastAPI to find affected middleware patterns. The repository also includes a runtime PoC for confirming exposure on a deployed instance.
- Log review. Cheap signals worth running retroactively:
- Any access log entry where the
Hostheader contains any of:/,?,#,\,@. Legitimate clients do not emit these characters in Host. - Any divergence between the dispatched route and the
request.url.pathyour application recorded for that request. Any non-equal pair is the exploit signature. - For LiteLLM / vLLM specifically: requests to administrative or model-control endpoints whose audit trail records an unexpected path string for the same dispatched handler.
- Any access log entry where the
Timeline
| 2026-01-27 | Issue identified during X41 source code audit of vLLM |
| 2026-02-04 | Starlette vendor notified, PoC supplied |
| 2026-02-05 | Vendor acknowledged |
| 2026-03-01 | Patch proposed by vendor |
| 2026-05-21 | Patch released (Starlette 1.0.1, scored CVSS 6.5 Moderate) |
| 2026-05-22 | Public disclosure, CVE-2026-48710 assigned, badhost.org launched |
Note: end-to-end the vendor was pushed for roughly four months before shipping, with the proposed patch sitting for nearly three of those months between proposal and release. The fix was released quietly, scored CVSS 6.5 Moderate at the library layer, with no ecosystem-wide notice corresponding to the actual downstream blast radius. Public disclosure and patch availability landed on the same day, so operators should assume zero exploit development lead time.
References
- CVE-2026-48710 (BadHost), online scanner — badhost.org
- GHSA-86qp-5c8j-p5mr — github.com/Kludex/starlette
- X41-2026-002 — x41-dsec.de
- X41 scanner, Semgrep, CodeQL — github.com/x41sec/poc/tree/master/starlette-host-header
- RFC 9112 §3.2, RFC 3986 §3.2.2, CWE-436

