Starlette CVE-2026-48710: BadHost Authentication Bypass
Starlette CVE-2026-48710 (BadHost) is a Host-header URL reconstruction flaw fixed in Starlette 1.0.1. New OSTIF, X41, Tenable, and BadHost scanner sources clarify that the highest-risk deployments are FastAPI/Starlette/LLM services whose middleware makes security decisions from request.url.path.
On this page 0% read
Executive Summary
A critical authentication bypass vulnerability, designated CVE-2026-48710 and nicknamed “BadHost”, has been disclosed in the Starlette web framework. New primary sources now clarify the exploit condition: Starlette versions before 1.0.1 reconstruct request.url from the raw HTTP Host header, while routing still uses the real ASGI path. Middleware that authorizes based on request.url.path can therefore see an attacker-controlled path that differs from the route actually executed OSTIF Tenable.
The highest-risk deployments are not every Starlette application equally. Prioritize FastAPI, Starlette, vLLM, LiteLLM, MCP gateways, OpenAI-compatible proxies, and internal AI/agent services that run directly on ASGI servers or use middleware to gate /admin, /v1/models, /mcp, /internal, /metrics, or tool-execution endpoints BadHost OSTIF. This article provides an impact determination and a Python audit script to identify vulnerable dependencies and Host-header exploit attempts.
Key Facts
cve: "CVE-2026-48710"
alias: "BadHost"
vendor: "Starlette"
product: "Starlette (ASGI toolkit)"
disclosed_date: "2026-05-26"
vulnerability: "Authentication bypass via Host header URL reconstruction injection"
cwe: ["CWE-346", "CWE-284"]
affected_products: ["Starlette", "FastAPI applications with affected middleware", "vLLM/LiteLLM/MCP services using affected middleware"]
affected_versions: ["Starlette < 1.0.1"]
fixed_versions: ["1.0.1"]
nvd_cvss_v31: "6.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"
public_tools: ["badhost.org scanner", "X41 PoC / Semgrep / CodeQL rules"]
exploitation_status: "public PoC and scanner available; active exploitation not confirmed in reviewed primary sources"
kev_status: "not listed in CISA KEV as of 2026-05-27 review"
Source Confidence & Evidence Mapping
- confirmed: OSTIF says the issue was discovered during a vLLM audit managed by OSTIF and sponsored by Alpha-Omega, and describes the path mismatch between router dispatch and middleware-visible
request.url.pathOSTIF. - confirmed: Tenable’s CVE entry states that Starlette before
1.0.1did not validate the HTTPHostrequest header before reconstructingrequest.url; it lists the1.0.1fix behavior and references the GHSA, X41 advisory, Starlette commit, and BadHost scanner Tenable. - confirmed: BadHost.org provides a public scanner and explains the exact bypass primitive, including
Host: example.com/health?x=makingrequest.url.pathresolve to/healthwhile the real request path reaches/protectedBadHost. - likely: Direct-to-ASGI AI and MCP services are disproportionately exposed because many deployments put path-based API-key or admin gating in middleware.
- unclear: Reviewed primary sources do not prove active exploitation in the wild; treat public scanner/PoC availability as exposure pressure, not confirmed exploitation.
Impact Determination
| Classification | Criteria | Required evidence | Remediation trigger | Closure condition |
|---|---|---|---|---|
| Confirmed compromise | Web/WAF logs show HTTP requests with path-delimiter characters (e.g. /, ?, #) in the Host header, followed by unauthorized access to administrative or restricted paths. | Logs showing Host header values like example.com/health?x= targeting /admin with 200 OK status. | Revoke all session keys, isolate internal API services, and run backend audits. | Upgrade Starlette to 1.0.1 or later and verify backend logs for no further anomalous Host headers. |
| Presumed exposed | The web application is built on FastAPI or Starlette with version < 1.0.1, exposing path-based middleware authorization gates. | requirements.txt or poetry.lock lockfile showing starlette < 1.0.1 in active production environments. | Upgrade the library dependency immediately to a patched release. | Dependency verification outputs showing starlette >= 1.0.1 installed. |
| Potentially exposed | A Python project is running but lockfiles or backend web log records are not audited. | Asset register indicating Python-based ASGI servers (FastAPI/Uvicorn) without version check. | Execute the dependency and log audit script. | Confirm if the asset is confirmed compromised, presumed exposed, or not exposed. |
| Not exposed | The system utilizes non-affected framework stacks, or Starlette version is verified >= 1.0.1. | Verified dependency files showing version 1.0.1 or later. | None for this CVE. | Version verification bundle is archived. |
Timeline
- 2026-05-21: Starlette
1.0.1patch is available according to downstream tracking references Tenable. - 2026-05-26: OSTIF publishes expanded BadHost disclosure because slow patch uptake and live vulnerable services raised ecosystem concerns OSTIF.
- 2026-05-26: Tenable publishes/updates its CVE record with references to X41, OSTIF, GHSA, PyPA advisory data, the Starlette patch commit, and BadHost.org Tenable.
- 2026-05-27: This article is refreshed to remove the unsupported KEV/active-exploitation framing and replace generic source links with exact primary and tracker references.
What Happened
Starlette handles URL reconstruction dynamically by concatenating the raw HTTP Host header with the request path to build a unified URI string. Because the Host header was not validated against standard RFC grammar, attackers could inject custom query delimiters (/health?x=) directly into the Host field:
- The Request:
GET /adminwithHost: example.com/health?x= - The Reconstruction: Starlette builds the internal URL as
http://example.com/health?x=/admin. - The Bypass: The underlying ASGI server processes the request path
/admin, but Starlette’s routing/auth middleware inspects the reconstructedrequest.url.pathwhich returns/health. If/healthis whitelisted, the authentication check is bypassed entirely, granting unauthenticated access to the restricted/adminresources.
Technical Analysis
The fundamental error occurs during URL parsing inside Starlette’s utility functions. By failing to validate that the host contains only valid hostname characters, re-parsing the reconstructed URL causes the injection parameters to overwrite the target path, shifting the path boundary forward.
Affected Assets and Blast Radius
asset_selectors:
- "starlette"
- "fastapi"
- "litellm"
- "vllm"
highest_value_assets:
- "Internet-facing FastAPI applications utilizing path-based middleware"
- "vLLM or LiteLLM AI endpoints exposed with API key authentication"
- "Internal corporate admin dashboards written in Python ASGI frameworks"
credentials_and_data_at_risk:
- "Internal LLM access and model weights (via vLLM/LiteLLM bypass)"
- "Backend database records accessible through administrative endpoints"
- "Administrative session credentials and user data"
Indicators And Detection Selectors
vulnerabilities: ["CVE-2026-48710", "BadHost"]
packages: ["starlette", "fastapi"]
telemetry_selectors:
- "starlette"
- "fastapi"
- "Host:"
- "/health"
Detection and Hunting
Script: local repository and exported telemetry scope
#!/usr/bin/env python3
import os
import sys
import json
import subprocess
from pathlib import Path
ROOT = sys.argv[1] if len(sys.argv) > 1 else "."
LOG_ROOT = os.environ.get("LOG_ROOT", "")
OUT = Path(os.environ.get("OUT", "hp-starlette-cve-2026-48710-badhost-scope"))
SINCE = "2026-05-26T00:00:00Z"
UNTIL = "2026-05-26T23:59:59Z"
PACKAGES = [
]
VERSIONS = [
]
FILES = [
]
DOMAINS = [
"request.url.path",
"www.tenable.com",
]
URLS = [
"https://ostif.org/disclosing-the-badhost-vulnerability-in-starlette/",
"https://www.tenable.com/cve/CVE-2026-48710",
"https://badhost.org/",
"http://example.com/health?x=/admin`",
]
IPS = [
]
HASHES = [
]
PROCESS_PATTERNS = [
]
NETWORK_PATTERNS = [
]
# Positive signal: repository, lockfile, artifact, process, or network telemetry contains one of the exact incident selectors above.
# Escalation: any match tied to a production build, CI run, deployed asset, or secret-bearing host moves the asset to presumed exposed.
OUT.mkdir(parents=True, exist_ok=True)
indicators_file = OUT / "indicators.txt"
# Collect unique indicators
indicators = set()
for group in [PACKAGES, VERSIONS, FILES, DOMAINS, URLS, IPS, HASHES, PROCESS_PATTERNS, NETWORK_PATTERNS]:
for val in group:
if val:
indicators.add(val)
with open(indicators_file, "w") as f:
for ind in sorted(indicators):
f.write(ind + "\n")
print(f"[+] Written unique selectors to {indicators_file}")
# Walk local directory
print(f"[+] Scanning directory: {ROOT} for selectors...")
matches = []
exclude_dirs = {"node_modules", "vendor", "dist", ".git"}
for root, dirs, filenames in os.walk(ROOT):
dirs[:] = [d for d in dirs if d not in exclude_dirs]
for filename in filenames:
filepath = Path(root) / filename
try:
content = filepath.read_text(errors="ignore")
for ind in indicators:
if ind in content:
matches.append(f"{filepath}: found '{ind}'")
except Exception:
pass
if matches:
(OUT / "repository-indicator-matches.txt").write_text("\n".join(matches) + "\n")
print(f"[!] Found {len(matches)} matches in codebase!")
# Optional Log Scanning
if LOG_ROOT and os.path.exists(LOG_ROOT):
print(f"[+] Scanning telemetry log directory: {LOG_ROOT}...")
log_matches = []
for root, _, filenames in os.walk(LOG_ROOT):
for filename in filenames:
filepath = Path(root) / filename
try:
content = filepath.read_text(errors="ignore")
for ind in indicators:
if ind in content:
log_matches.append(f"{filepath}: found '{ind}'")
except Exception:
pass
if log_matches:
(OUT / "exported-telemetry-indicator-matches.txt").write_text("\n".join(log_matches) + "\n")
print(f"[!] Found {len(log_matches)} matches in logs!")
if PACKAGES:
registry_dir = OUT / "registry"
registry_dir.mkdir(exist_ok=True)
print(f"[+] Wrote scope artifacts under {OUT}")
Remediation & Credential Rotation Plan
Containment & Mitigation
- Host Header Validation: Ensure that front-facing reverse proxies (like Nginx, Apache, Caddy, Traefik, HAProxy, or Cloudflare) are explicitly configured to validate and normalize the
Hostheader before passing the request downstream to Uvicorn/Starlette. - Block Redirections: Add WAF rules to drop any incoming requests where the
Hostheader contains non-standard URI characters such as/,?, or#.
Eradication & Recovery
- Upgrade Starlette Dependency: Mandate immediate dependency upgrades of Starlette to version
1.0.1or later:- Command:
pip install --upgrade starletteorpoetry update starlette
- Command:
- Rotate Exposed Secrets: If web logs show successful requests (
200 OK) featuring a poisoned Host header targeting restricted administration directories, immediately revoke and rotate all backend API keys and session credentials accessible through the compromised services.