BadHost CVE-2026-48710 — Starlette HTTP Host Header Authentication Bypass Affecting FastAPI/AI Infrastructure (MCP, vLLM, LiteLLM) — Threadlinqs Intelligence
As of 2026-05-30, BadHost CVE-2026-48710 — Starlette HTTP Host Header Authentication Bypass Affecting FastAPI/AI Infrastructure (MCP, vLLM, LiteLLM) is a critical-severity vulnerability threat, tracked by Threadlinqs Intelligence with 9 detection rules (Splunk SPL, Microsoft KQL, Sigma) and 18 indicators of compromise.
Threat ID: TL-2026-0606 · Severity: CRITICAL · CVSS: 6.5 · Status: ACTIVE · Category: VULNERABILITY
BadHost (CVE-2026-48710) is a critical authentication bypass vulnerability in Starlette versions prior to 1.0.1, the ASGI framework underlying FastAPI. The flaw stems from unsafe concatenation of the
## Overview
BadHost (CVE-2026-48710, X41-2026-002, GHSA-86qp-5c8j-p5mr, PYSEC-2026-161) is an HTTP Host header injection vulnerability in Starlette, the ASGI web framework that powers FastAPI and most modern Python AI/agent serving infrastructure. The flaw allows an unauthenticated remote attacker to bypass path-based authentication middleware by injecting characters such as `/`, `?`, `#`, `@`, `\`, or whitespace into the HTTP Host header. The vulnerability affects every Starlette release prior to 1.0.1 (operationally relevant from 0.8.3 onward) and is fixed in 1.0.1, released 2026-05-21.
## Root Cause
Starlette reconstructs `request.url` by concatenating the HTTP `Host` request header with the ASGI `scope["path"]` and re-parsing the result through Python''s `urlsplit()` — without validating the Host value against RFC 9110 §7.2 / RFC 9112 §3.2 / RFC 3986 §3.2.2 grammar (`uri-host [ ":" port ]`). The vulnerable logic lives in `starlette/datastructures.py` (the `URL` class), consumed by `Request.url` in `starlette/requests.py`.
Conceptually, the vulnerable assembly behaves like:
```
url = f"{scheme}://{host_header}{scope[''path'']}"
parsed = urlsplit(url)
# parsed.path is now derived from host_header + scope[''path''], not from scope[''path''] alone
```
The critical consequence is a **parser disagreement** between two consumers of the same request:
1. The ASGI server / Starlette router dispatches against the raw wire path (`scope["path"]`).
2. Any middleware that inspects `request.url.path` sees a re-parsed path that can be shifted by attacker-controlled characters in the Host header.
Authorization built on `request.url.path` therefore evaluates a different string than the route actually executed. Python''s `urlsplit()` is lenient and accepts characters that are illegal in a valid RFC host, producing the cleavage.
## Exploit Primitive
The canonical proof of concept:
```
GET /admin HTTP/1.1
Host: example.com/health?x=
```
Reconstructed URL becomes `http://example.com/health?x=/admin`. After re-parse:
- `request.url.path` = `/health` (passes any allowlist for unauthenticated paths)
- ASGI routed path = `/admin` (still dispatched to the protected handler)
- Middleware permits the request; the protected handler executes and returns 200 OK
A single trailing `?`, `/`, or `#` appended to the Host header is sufficient. Standard HTTP clients (browsers, `requests`, `urllib`) normalize Host and will not deliver the malformed value — exploitation requires raw sockets, low-level libraries, or `curl` with an explicit `-H 'Host: foo?'` override.
## Why MCP Servers Are Disproportionately Exposed
The Model Context Protocol (MCP) specification mandates two unauthenticated OAuth discovery endpoints on every spec-compliant MCP server:
- `/.well-known/oauth-authorization-server`
- `/.well-known/oauth-protected-resource`
These paths are guaranteed unauthenticated by design. An attacker therefore has a pre-built skeleton key for bypassing path-based middleware:
```
GET /mcp/tools/execute HTTP/1.1
Host: target.example.com/.well-known/oauth-authorization-server?x=
```
Middleware evaluates the OAuth discovery path (allowlisted) while the router dispatches `/mcp/tools/execute`. The attacker invokes MCP tools, exfiltrates API keys, and accesses internal tooling — all without credentials. Persistent Security Industries (Nemesis) operates a public scanner (`mcp-scan.nemesis.services`) that probes `/mcp`, `/sse`, `/messages`, and the `.well-known` OAuth endpoints to identify exposed MCP instances.
## Downstream Blast Radius
Because Starlette is a transitive dependency of much of the Python AI/agent serving ecosystem, the vulnerability impacts:
- **FastAPI** — directly built on Starlette. All apps using `BaseHTTPMiddleware` with `request.url.path` auth checks are affected. Route-level `Depends()` / `Security()` dependencies are NOT affected.
- **vLLM** — high-performance LLM inference server. The bug was originally discovered here during
Weaknesses (CWE)
CWE-444, CWE-436, CWE-287
Target sectors: technology, ai-infrastructure, saas, financial-services, healthcare, government, mlops, agent-developers, model-hosting-providers
Target regions: Global, North America, Europe, Asia-Pacific
Detections & IOCs
As of 2026-08-17, this threat has 9 detection rule(s) across Splunk SPL, Microsoft KQL and Sigma, and 18 indicator(s) of compromise. Detection query text and full IOC values are available to authenticated users and programmatically via the Threadlinqs MCP server (Purple tier). View plans.
VULNERABILITY, CRITICAL, threat intelligence, cybersecurity, CVE-2026-48710, T1595, T1592, T1587, T1588, T1190, T1059, T1211, T1685, T1552, T1528