Threat reportVulnerabilityTL-2026-0606

BadHost CVE-2026-48710 — Starlette HTTP Host Header Authentication Bypass Affecting FastAPI/AI Infrastructure (MCP, vLLM, LiteLLM)

criticalACTIVE

BadHost CVE-2026-48710 (TL-2026-0606), also tracked as BadHost, is a critical-severity software vulnerability scored CVSS 6.5, first published 2026-05-27. It has no confirmed attribution, affects Encode Starlette, references 1 CVE (CVE-2026-48710), maps to 15 MITRE ATT&CK techniques (T1046, T1059, T1102), and is covered by 9 detection rules and 18 indicators of compromise.

CVSS
6.5/10Critical
CVEs
1Referenced vulnerabilities
Techniques
15MITRE ATT&CK
Actors
0Not attributed
Detection rules
9SPL · KQL · Sigma
IOCs
18Indicators of compromise

Key facts for TL-2026-0606

Threat ID
TL-2026-0606
Also known as
BadHost, X41-2026-002, GHSA-86qp-5c8j-p5mr, PYSEC-2026-161
Severity
CRITICAL
CVSS
6.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N)
Status
ACTIVE
Category
VULNERABILITY
First published
Last reviewed
Attribution confidence
NONE
Motivation
UNKNOWN
Target sectors
technology, ai-infrastructure, saas, financial-services, healthcare, government, mlops, agent-developers, model-hosting-providers
Target regions
Global, North America, Europe, Asia-Pacific
Detection rules
9
Indicators of compromise
18

Malware and tooling in BadHost CVE-2026-48710

Malware and tooling: mcp-scan.nemesis.services, scan_cve_2026_48710.py

How BadHost CVE-2026-48710 works

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 HTTP Host header into request.url construction, allowing attackers to inject path-like values (e.g., 'Host: example.com/health?x=') that cause request.url.path to differ from the actual routed path. Authentication middleware relying on request.url.path can be bypassed entirely. The vulnerability disproportionately impacts AI infrastructure including MCP (Model Context Protocol) servers, vLLM, LiteLLM, Ray Serve, BentoML, and Google ADK-Python. Discovered by X41 D-Sec during an OSTIF-sponsored audit funded by the Alpha-Omega Project.

## 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 the audit. - **LiteLLM** — LLM proxy gateway. `/model/info`, `/key/info` endpoints exposed; direct API key exfiltration risk. - **MCP servers** — highest risk class due to mandated unauthenticated `.well-known` paths. - **Ray Serve**, **BentoML**, **Google ADK-Python**, **Text Generation Inference**, agent harnesses, eval dashboards, model registries. - **Apache Airflow** — PR #3279 cites two concrete bypasses: `JWTAuthStaticFiles.validate_jwt_token` log-file auth bypass and Edge3 worker API auth bypass. Upgraded to Starlette 1.0.1.

## Escalation

The authentication bypass is the primitive; the impact depends on what protected functionality the application exposes. Documented escalations include:

1. Authentication bypass on path-gated middleware 2. Access to admin/management endpoints (`/admin`, `/v1/models`, `/internal`, `/metrics`, `/shutdown`) 3. SSRF via gated proxy endpoints — reaching cloud metadata services and internal networks 4. RCE when reached endpoints execute tools, load models from URLs, or evaluate code — confirmed across multiple downstream AI projects

## Fix

The patch (encode/starlette PR #3279 by Marcelo Trylesinski / Kludex, commit `764dab0dcfb9033d75442d7a359645c9f94648c6`) introduces a strict allowlist regex (`_HOST_RE`) in `starlette/datastructures.py` matching only valid `[a-zA-Z0-9.-]` domain labels, IPv6 in brackets, and an optional `:port`. If the Host header fails the regex, Starlette falls back to `scope["server"]` (host+port from the ASGI scope) — identical to the no-Host-header path. Released as **Starlette 1.0.1** on 2026-05-21.

## Detection & Mitigation

- Deploy an RFC-compliant reverse proxy (nginx, Apache, Caddy, Traefik, HAProxy, Cloudflare, AWS ALB) in front of any directly-exposed Starlette/FastAPI service — these reject invalid characters in Host headers. - WAF rule: drop requests where the Host header contains `/`, `?`, `#`, `@`, `\`, or whitespace. - Replace `request.url.path` with `request.scope["path"]` in custom middleware. The ASGI scope path is not influenced by the Host header. - Prefer FastAPI route-level `Depends()` / `Security()` and Starlette `requires()` over `BaseHTTPMiddleware` path-prefix checks. - Run X41''s published Semgrep and CodeQL rules to identify unsafe `request.url.path` usage in your codebase. - Upgrade Starlette to ≥ 1.0.1 (transitively re-pin FastAPI, vLLM, LiteLLM, Ray Serve, BentoML, ADK-Python, MCP servers).

## Discovery

Identified by X41 D-Sec senior researchers (JJ, Yassine El Baaj, Markus Vervier) on 2026-01-27 during manual source review of vLLM in an OSTIF-managed audit sponsored by the Alpha-Omega Project. Privately disclosed to Starlette maintainers 2026-02-04, patched 2026-05-21, publicly disclosed 2026-05-22.

MITRE ATT&CK techniques used in TL-2026-0606

Discovery

T1046 Network Service Discovery; T1526 Cloud Service Discovery

Execution

T1059 Command and Scripting Interpreter

Command and Control

T1102 Web Service

Initial Access

T1190 Exploit Public-Facing Application

Defense Evasion

T1211 Exploitation for Stealth

Collection

T1213 Data from Information Repositories

Impact

T1496 Resource Hijacking

Credential Access

T1528 Steal Application Access Token; T1552 Unsecured Credentials

Resource Development

T1587 Develop Capabilities; T1588 Obtain Capabilities

Reconnaissance

T1592 Gather Victim Host Information; T1595 Active Scanning

defense-impairment

T1685 Disable or Modify Tools

Affected products and versions in BadHost CVE-2026-48710

  • Encode — Starlette
    Vulnerable versions: >=0.8.3,<1.0.1; PyPA range 0.1.0 through 1.0.0 inclusive
    Fixed in: 1.0.1
  • Tiangolo — FastAPI
    Vulnerable versions: All versions transitively depending on Starlette <1.0.1, where BaseHTTPMiddleware reads request.url.path
    Fixed in: Any version with starlette>=1.0.1 pinned
  • vLLM Project — vLLM
    Vulnerable versions: All releases with starlette<1.0.1
    Fixed in: Versions pinning starlette>=1.0.1
  • BerriAI — LiteLLM
    Vulnerable versions: All releases with starlette<1.0.1
    Fixed in: Versions pinning starlette>=1.0.1
  • Anthropic — MCP server reference implementations
    Vulnerable versions: All MCP server implementations on Starlette<1.0.1 — highest-risk class due to mandated unauthenticated .well-known/oauth-* endpoints
    Fixed in: Implementations upgraded to starlette>=1.0.1
  • Anyscale — Ray Serve
    Vulnerable versions: All releases with starlette<1.0.1
    Fixed in: Versions pinning starlette>=1.0.1
  • BentoML — BentoML
    Vulnerable versions: All releases with starlette<1.0.1
    Fixed in: Versions pinning starlette>=1.0.1
  • Google — ADK-Python (Agent Development Kit)
    Vulnerable versions: All releases with starlette<1.0.1
    Fixed in: Versions pinning starlette>=1.0.1
  • Hugging Face — Text Generation Inference
    Vulnerable versions: All releases with starlette<1.0.1
    Fixed in: Versions pinning starlette>=1.0.1
  • Apache Software Foundation — Airflow
    Vulnerable versions: Releases with starlette<1.0.1 — concrete bypasses in JWTAuthStaticFiles.validate_jwt_token and Edge3 worker API
    Fixed in: Releases pinning starlette>=1.0.1

Remediation for BadHost CVE-2026-48710

Patches

  • Upgrade Starlette to >= 1.0.1 (released 2026-05-21, fix PR encode/starlette#3279, commit 764dab0dcfb9033d75442d7a359645c9f94648c6)
  • Pin starlette>=1.0.1 in pyproject.toml/requirements.txt and rebuild all dependent images
  • Verify transitive upgrade for FastAPI, vLLM, LiteLLM, Ray Serve, BentoML, Google ADK-Python, MCP server implementations
  • Apache Airflow: upgrade to the version pinning Starlette>=1.0.1 (fixes JWTAuthStaticFiles and Edge3 worker bypasses)

Immediate actions

  • Deploy an RFC-compliant reverse proxy (nginx, Apache, Caddy, Traefik, HAProxy, Cloudflare, AWS ALB) in front of any directly-exposed Starlette/FastAPI/Uvicorn service to reject malformed Host headers
  • Add a WAF rule that drops requests where the Host header contains any of: '/', '?', '#', '@', '\', or whitespace
  • Scan internal estate with the X41 PoC scanner (github.com/x41sec/poc/tree/master/starlette-host-header) or badhost.org
  • Audit all custom middleware for use of request.url.path — replace with request.scope['path']

Workarounds

  • Replace request.url.path with request.scope['path'] in BaseHTTPMiddleware-derived auth middleware
  • Use FastAPI Depends()/Security() or Starlette requires() decorators for authorization (route-level, not path-prefix)
  • Front the service with nginx and reject requests via 'if ($http_host ~ "[/?#@\\ ]") { return 400; }'
  • Base authentication decisions on endpoint/route names rather than path strings

Longer-term hardening

  • Adopt route-level authorization (FastAPI Depends()/Security(), Starlette requires()) instead of path-prefix middleware
  • Continuous SCA scanning for transitive Starlette versions across all Python services
  • Treat ASGI scope as the single source of truth for routing and security decisions
  • Deploy EDR/WAF with behavioral detection for HTTP Host header anomalies and parser disagreement patterns
  • Integrate Semgrep and CodeQL rules from X41 into CI to block reintroduction of request.url.path auth checks

CVEs associated with BadHost CVE-2026-48710

CVE-2026-48710

Weaknesses (CWE) in BadHost CVE-2026-48710

CWE-444, CWE-436, CWE-287

Timeline of BadHost CVE-2026-48710

  • X41 D-Sec researcher identifies BadHost during manual source review of vLLM in an OSTIF-managed audit funded by the Alpha-Omega Project
  • X41 D-Sec privately discloses BadHost to Starlette maintainers with working proof of concept
  • Starlette maintainers acknowledge receipt of the X41 advisory and confirm the issue
  • Initial patch proposed in Starlette repository introducing strict Host header allowlist regex
  • Starlette 1.0.1 released with PR #3279 (commit 764dab0); GHSA-86qp-5c8j-p5mr published; Apache Airflow upgrades to fix JWTAuthStaticFiles and Edge3 worker bypasses
  • X41 advisory X41-2026-002 published; PYSEC-2026-161 indexed; BadHost project portal goes live; Nemesis MCP scanner online; OSTIF disclosure post published
  • NVD entry for CVE-2026-48710 published with CVSS 3.1 score 6.5 MEDIUM
  • Broad press coverage (Cybersecurity News, BeyondMachines, MLQ.ai); Threadlinqs Intelligence opens TL-2026-0606
  • As of 2026-05-29, BadHost (CVE-2026-48710) remains a live concern: patched in Starlette 1.0.1 (2026-05-21) but disclosed only days ago with public PoC scanners (X41, Nemesis MCP scanner) and a trivial one-character exploit. The vast FastAPI/vLLM/LiteLLM/MCP install base (~325M weekly downloads) is still largely unpatched; not yet in CISA KEV but actively probed.

Sources cited for BadHost CVE-2026-48710

Detection coverage for TL-2026-0606

As of 2026-05-27, Threadlinqs Intelligence publishes 9 detection rule(s) for TL-2026-0606 across Splunk SPL, Microsoft KQL and Sigma, covering 18 indicator(s) of compromise. The whole corpus is readable without an account; a free account unlocks full detection query text in Splunk SPL, Microsoft KQL and Sigma; paid tiers add raw indicator values, correlation and the MCP server. Threadlinqs MCP server · View plans.

9 detection rules (Splunk SPL, Microsoft KQL, Sigma) · Blue and above. Compare plans
18 indicators of compromise · Red and above. Compare plans

Further reading

Threadlinqs Intelligence — Real-Time Threat Detection Platform

[ 0 threats ] [ 0 det ] [ CRIT: 0 ] [ HIGH: 0 ]
// threat_feed
$ sort --newest
Showing all threats

Live intelligence console

Threat level
Fig. 01 · Threat weatherIndexing the archive…
1 square = 1 threat · click to open

Latest Threats