this is cross-tenant data leakage — not just privilege escalation within one tenant's boundary.
The Downstream Stack: vLLM, LiteLLM, Google ADK, and Ray Serve

BadHost lives in Starlette, but the endpoints that matter most belong to Starlette's dependents. The vulnerability was found during an OSTIF-commissioned audit of vLLM that began in January 2026 — and while the flaw is upstream, vLLM's /v1/models, /metrics, and model-management endpoints sit directly in the blast zone. LiteLLM's billing gates and rate-limiting middleware, Google ADK-Python's agent serving layer, Ray Serve's deployment management API, and BentoML's HTTP interface all inherit the same exposure . Each framework carries a different risk profile depending on whether the deployment context is production, evaluation, or local development.
| Framework | Highest-Risk Endpoint Paths | Reverse Proxy in Production | Reverse Proxy in Dev / Eval |
|---|---|---|---|
| vLLM | /v1/models, /metrics, /shutdown, /v1/generate |
Often (Kubernetes ingress, cloud LB) | Rarely — research clusters run uvicorn directly |
| LiteLLM proxy | /v1/*, /health, billing middleware paths, rate-limit exclusion lists |
Sometimes (cloud-hosted tier) | Rarely — local proxy mode starts without a proxy layer |
| Google ADK-Python | Agent serving endpoints, OAuth discovery routes | Cloud-managed deployments | No — ADK quickstart runs uvicorn directly |
| Ray Serve | /v2/models, /dashboard, deployment management API |
Yes (Ray cluster head proxy) | No — local clusters expose dashboard and API directly |
| BentoML | /predict, /docs, /metrics, runner APIs |
Production-only (cloud serving) | No — bentoml serve starts uvicorn directly |
| MCP servers (FastAPI-based) | /.well-known/*, /tools/*, /resources/*, /oauth/authorize |
Rarely in any environment | No — quickstarts bind directly to 0.0.0.0 |
vLLM's /metrics endpoint exposes Prometheus-format inference metrics including queue depth, token throughput, and GPU utilization. That data is useful for capacity planning — and equally informative for an attacker characterizing a deployment before targeting it. The /shutdown and model-reload endpoints move the impact from information disclosure into service disruption. A BadHost bypass that makes authorization middleware read /health while routing executes /shutdown is not a theoretical edge case — it is precisely the attack this vulnerability enables .
LiteLLM's billing and rate-limiting middleware warrants separate attention. LiteLLM is widely deployed as a gateway enforcing per-key spend caps, per-model rate limits, and budget enforcement for teams sharing an OpenAI-compatible interface. Most of that enforcement is implemented as path-based middleware — which is exactly what BadHost bypasses. A crafted request can route to a billing-controlled endpoint while the billing middleware evaluates an unmetered path, effectively removing spend-cap enforcement for the duration of the session .
The Reverse Proxy Shield — and Where It Breaks Down

The most actionable operational fact about BadHost is that a correctly configured reverse proxy completely neutralizes the attack. nginx, Caddy, Traefik, AWS ALB, and Cloudflare all validate incoming HTTP headers before forwarding to the application layer. The characters that make BadHost injection work — /, ?, # — are invalid in DNS hostnames per RFC 1123 and are rejected by standard proxy header parsers before the malformed Host header ever reaches Starlette . Well-hardened production deployments are largely shielded. That is the good news.
"The characters that enable BadHost injection are illegal in RFC 1123 hostnames — every correctly configured proxy rejects them before they reach application code. The real exposure surface is the gap between where production deployments end and where AI research, development, and evaluation actually runs." — Community technical analysis, Hacker News BadHost disclosure thread, 2026-05
The breakdown points are predictable and common across AI infrastructure. Local development servers started with uvicorn app:main --reload --host 0.0.0.0 have no proxy layer. Docker Compose stacks that expose the application port directly to the host without a proxy service in the compose file have no proxy layer. CI evaluation runners that spin up a model server for automated testing have no proxy layer. Jupyter-adjacent MCP setups, where a Starlette tool server starts in a post-start hook or notebook cell, have no proxy layer. In all of these environments, a developer who has not explicitly added Host-header validation is running a BadHost-vulnerable service on a network-reachable interface, as CSO Online's coverage of the disclosure details .
Adding a proxy in dev is low overhead. A minimal Caddyfile that terminates connections and proxies to uvicorn adds complete protection — Caddy rejects malformed Host headers as part of standard HTTP/1.1 parsing:
localhost {
reverse_proxy localhost:8000
}
In Docker Compose, a single nginx service block achieves the same protection for the full stack. The operational overhead is a few minutes of configuration; the defense-in-depth value is independent of application code correctness. Even if a future Starlette release regresses on Host validation, a proxy layer provides a rejection point that does not depend on the application getting it right.
Mitigation: Patch, Audit, and Defense-in-Depth
The immediate step is pinning Starlette to >=1.0.1 explicitly in every project that runs Starlette or a Starlette-based framework. This is not automatic: FastAPI, vLLM, LiteLLM, and Google ADK-Python each carry their own transitive dependency pins, and a transitive upgrade does not propagate to the host project unless the host project declares the constraint explicitly :
# requirements.txt
starlette>=1.0.1
# pyproject.toml
[project]
dependencies = [
"starlette>=1.0.1",
"fastapi>=0.115.0",
]
After patching, audit every middleware and dependency that reads request.url or request.url.path for security-control purposes. The safe replacement is request.scope["path"], which Starlette's router sets from the parsed HTTP request line before any URL reconstruction occurs. It is never derived from the Host header and is unaffected by BadHost in any Starlette version :
# Vulnerable — reads the Host-reconstructed URL path
async def auth_middleware(request: Request, call_next):
if request.url.path.startswith("/admin"):
verify_token(request)
return await call_next(request)
# Safe — reads the router-set scope path
async def auth_middleware(request: Request, call_next):
if request.scope["path"].startswith("/admin"):
verify_token(request)
return await call_next(request)
If upgrading Starlette is temporarily blocked by a dependency conflict, add an entry-point middleware that rejects any Host header containing /, ?, or # before routing or authentication logic runs:
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import PlainTextResponse
import re
_INVALID_HOST_RE = re.compile(r'[/?#]')
class BadHostGuard(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
host = request.headers.get("host", "")
if _INVALID_HOST_RE.search(host):
return PlainTextResponse("Bad Request", status_code=400)
return await call_next(request)
Finally, add a reverse proxy to every environment where uvicorn is network-reachable — development, staging, and CI evaluation included. A free open-source scanner to identify affected deployments is available at badhost.org.
Frequently Asked Questions

Does upgrading to Starlette 1.0.1 fully fix BadHost?
Starlette 1.0.1 fixes the root cause: the Host header is validated against RFC 9112 §3.2 before URL reconstruction, so path injection via a malformed Host value no longer works. However, upgrading alone may not close every gap. If custom middleware copies request.url.path to a local variable early in the request cycle — before Starlette's validation runs — that code is still consuming an unsafe value. The complete fix is patch and audit together: upgrade Starlette, then review every middleware and dependency that reads request.url or request.url.path for access-control purposes, and replace those reads with request.scope["path"].
If I'm running behind nginx or Cloudflare, do I still need to upgrade?
Reverse proxies block the attack in practice: nginx, Cloudflare, Caddy, Traefik, and AWS ALB all reject malformed Host headers before they reach Starlette, because the injected characters (/, ?, #) are illegal in DNS hostnames and caught by standard HTTP parsers. But the root cause in Starlette remains unpatched, and proxy configurations can drift — internal routing, developer bypasses, health-check paths that skip the proxy, or any future misconfiguration re-expose the application. Upgrade Starlette regardless. The patch carries negligible risk, defense in depth applies, and leaving a confirmed authorization bypass unpatched because you trust your proxy configuration creates an unnecessary dependency on that configuration remaining correct forever.
Which endpoint paths are highest risk under BadHost?
Any endpoint protected by middleware that reads request.url.path is in scope. Concretely: admin routes (/admin, /dashboard), inference management endpoints (/v1/models, model load and reload APIs), internal metrics (/metrics), shutdown or restart endpoints (/shutdown), billing gates and spend-cap enforcement in LiteLLM-style proxies, CSRF protection middleware, per-tenant scoping checks in multi-tenant platforms, and rate-limiting middleware using path-based exclusion lists. The general pattern: if a route is whitelisted or excluded from a security check based on a path string derived from request.url.path, that check is bypassable under BadHost.
How do I check if my MCP server is affected?
Run pip show starlette in the environment where the server runs. If the version is below 1.0.1 and any authentication or access-control logic reads request.url or request.url.path, you are affected. Also confirm whether a reverse proxy sits between your network interface and uvicorn: if you started the server with uvicorn app:main --host 0.0.0.0 and there is no nginx, Caddy, Traefik, or cloud load balancer in front of it, the attack is reachable from any host with a route to that port. The badhost.org scanner automates this identification across a fleet of services.
What is request.scope['path'] and why is it safer than request.url.path?
scope["path"] is populated by Starlette's router directly from the parsed HTTP request line — the GET /protected HTTP/1.1 line — before any URL reconstruction occurs. It reflects the path the router actually dispatched to and is never derived from the Host header. request.url.path, by contrast, is reconstructed from the Host header value combined with a path suffix — exactly where BadHost injects its fake segment. Using scope["path"] in authorization middleware means the path you evaluate always matches the path that executed, regardless of what the Host header contains.
Patch First, Then Audit
BadHost is a contained, fixable vulnerability with a clear patch, a safe API replacement, and a low-overhead defense-in-depth layer. The remediation order is unambiguous: pin Starlette to >=1.0.1, audit middleware for request.url.path reads, and add a proxy in front of every uvicorn process that is network-reachable. None of these steps require significant engineering investment. Production deployments behind hardened proxies face limited near-term risk; developer and evaluation environments running uvicorn directly on routable interfaces are immediately exposed.
The broader signal is worth carrying forward. AI-agent infrastructure is being assembled from components designed for conventional web services, then deployed in environments that skip the hardening assumptions those components relied on. Starlette was built expecting a proxy-mediated deployment model; MCP servers, research LLM stacks, and CI model evaluators have systematically abandoned that assumption. BadHost illustrates what happens when framework security models meet deployment patterns that break their implicit preconditions. The community observation from the Hacker News thread — that LLM-assisted code review tooling failed to catch this cross-component logic flaw — points to a gap in how the AI infrastructure community is currently approaching static analysis for security-critical services .
Concretely: run pip show starlette across every service in your stack, confirm the explicit pin in your dependency file, grep auth middleware for request.url, and add a proxy to any environment that currently exposes uvicorn directly. The scanner at badhost.org can accelerate that audit across a larger fleet.
Last updated: 2026-05-28. Reviewed against the CVE-2026-48710 NVD entry, the OSTIF disclosure post, the X41 D-Sec advisory X41-2026-002, and the Starlette 1.0.1 changelog at time of publication.

