CVE-2026-48710 — BadHost: How a Single Starlette Flaw Put Millions of AI Agents at Risk


Executive Summary

CVE-2026-48710 — dubbed “BadHost” — is a Host header validation flaw in Starlette, the lightweight ASGI framework that underpins FastAPI and most of the Python AI tooling ecosystem. Discovered by researchers at X41 D-Sec during an OSTIF-sponsored security audit of vLLM, the vulnerability lets an unauthenticated attacker bypass path-based authentication by injecting a single delimiter character (/, ?, #) into the HTTP Host header.

The GitHub Security Advisory rates this at CVSS 6.5 (Medium), while X41 scores it 7.0 (CVSS 4.0, High). In real-world AI deployments — where MCP servers, inference gateways, and agent control planes sit behind fragile path-based middleware — the impact is critical. Affected software includes Starlette < 1.0.1, which means FastAPI, vLLM, LiteLLM, Text Generation Inference, OpenAI-compatible proxies, MCP servers, and agent frameworks including Google ADK, CrewAI, Langflow, and Dify.

Ars Technica — Millions of AI agents imperiled GitHub Security Advisory GHSA-86qp-5c8j-p5mr

CVE Details

Field Value
CVE ID CVE-2026-48710
Common Name BadHost
CVSS Score 6.5 (GitHub, CVSS 3.1) / 7.0 (X41, CVSS 4.0)
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
CWE CWE-444 (Inconsistent Interpretation of HTTP Requests)
Affected Component Starlette ASGI framework
Affected Versions All Starlette versions before 1.0.1
Fixed Version Starlette 1.0.1 (released 21 May 2026)
Published May 21, 2026 (GitHub) / May 26, 2026 (NVD)
Public PoC Available through X41 D-Sec disclosure
Discoverer X41 D-Sec (OSTIF-sponsored audit of vLLM)

Plain-English CVSS Breakdown

  • Attack Vector (Network): Exploitable remotely over the internet — no physical or local network access needed.
  • Attack Complexity (Low): A single malformed HTTP request is sufficient. No chained exploits or race conditions required.
  • Privileges Required (None): No authentication, no valid session, no prior access needed.
  • User Interaction (None): The attack does not require the victim to click a link or open a file.
  • Scope (Unchanged): The affected component remains Starlette’s request handling — though the downstream impact (what sits behind the bypassed auth) can be severe.
  • Confidentiality (High): Bypassed authentication can expose credentials, PII, and internal data.
  • Integrity (Low): Read/write access depends on what the bypassed endpoint controls.
  • Availability (None): The vulnerability is about authorization bypass, not denial of service.

CVE-2026-48710 — NVD (pending enrichment) Tenable — CVE-2026-48710

Affected Systems

The blast radius is enormous because Starlette sits beneath FastAPI and is downloaded approximately 325 million times per week.

Directly Affected Frameworks

Software Relationship Risk
Starlette (all versions < 1.0.1) The vulnerable component itself Critical
FastAPI (any version with Starlette < 1.0.1) Built on Starlette; FastAPI’s class subclasses Starlette High
vLLM Where the bug was discovered (OSTIF audit) Critical
LiteLLM OpenAI-compatible inference proxy using FastAPI High
Text Generation Inference (Hugging Face) Inference serving with Starlette dependency High

Agent and Application Frameworks

Framework Notes
MCP servers (Model Context Protocol) Network-deployed MCP servers using FastAPI are directly exposed
Google ADK Agent Development Kit running on Starlette
CrewAI Multi-agent orchestration platform
Langflow Visual AI workflow builder
Dify LLM application development platform
OpenAI-compatible proxies Custom proxies wrapping LLM APIs in Starlette apps

What X41 Found Behind Vulnerable Servers

X41’s scan catalogued the real-world assets exposed through this bug:

  • Clinical trial databases
  • Live PII and KYB (Know Your Business) pipelines
  • SSH access to industrial devices through bastions
  • Full mailbox read/send/delete access
  • S3 export buckets
  • Security asset inventories

Every one of these is a credential that an MCP server or agent tool keeps to do its job — sitting one Host-header bypass away from the network.

EMQ — Millions of Agents at Risk InfoQ — BadHost Vulnerability

Root Cause

The vulnerability lives in how Starlette reconstructs request.url from the HTTP Host header. In a normal HTTP/1.1 request:

GET /admin/settings HTTP/1.1
Host: api.example.com

The request target is /admin/settings and the Host header is api.example.com. These are separate pieces of data per RFC 9112. Starlette’s router uses the actual request path to dispatch endpoints, but convenience properties like request.url.path are reconstructed by combining the Host header with the path.

The flaw: Starlette before 1.0.1 did not validate the Host header before using it to reconstruct request.url. An attacker can inject URI delimiter characters (/, ?, #) into the Host header. When Starlette passes scheme:// + Host + path to Python’s urlsplit() parser, these delimiters shift the boundary between authority and path components.

The ASGI Layer Gap

The ASGI specification provides scope["path"] as the authoritative decoded path. The router dispatches against this value. But request.url.path was built by URL reconstruction. When the Host header contained delimiter characters, request.url.path could differ from scope["path"] — exactly the desynchronization that allows auth bypass.

Consider a simplified vulnerable middleware pattern:

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse

class AdminPathGate(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        if request.url.path.startswith("/admin"):
            return JSONResponse({"error": "Forbidden"}, status_code=403)
        return await call_next(request)

This looks reasonable. But with a malformed Host header, request.url.path may not start with /admin even though the router dispatches the request to /admin/settings. The middleware sees a safe path; the router serves a protected one.

Starlette 1.0.1 Release Notes RFC 9112 — HTTP/1.1 Message Syntax

Exploit Analysis

Attack Surface

Any Starlette or FastAPI application with path-based authorization middleware that reads request.url or request.url.path is exploitable. Common patterns include:

Pattern Where It Appears
request.url.path.startswith("/admin") Admin panel protection
request.url.path in ALLOWLIST Path allowlists
request.url for redirect construction Open redirect primitives
request.url for tenant routing Multi-tenant SaaS
request.url for callback/SSRF validation Webhook callbacks
request.url for CORS origin checks Cross-origin validation

Exploit Mechanics

The attacker crafts a request where the Host header contains a delimiter character that shifts the path boundary. The canonical example from the GitHub advisory:

GET /admin/panel HTTP/1.1
Host: example.com/?x=

When Starlette constructs request.url, it builds http://example.com/?x=/admin/panel. Python’s urlsplit() parses this as:

  • Authority: example.com
  • Path: / (from the ? delimiter)
  • Query: x=/admin/panel

So request.url.path is / — not /admin/panel. The middleware that checks request.url.path.startswith("/admin") sees safe. The router dispatches /admin/panel. The attacker gets a 200 instead of a 403.

Wormability Assessment

This bug does not self-propagate. It is an authentication bypass, not remote code execution. However, in AI agent infrastructure:

  1. An attacker scans for vulnerable FastAPI endpoints
  2. Sends malformed Host header to bypass auth on an MCP server
  3. Gains access to stored credentials (S3 keys, DB passwords, API tokens)
  4. Uses those credentials for lateral movement and data exfiltration

The wormability is low for spreading, but high for targeted exploitation chains because the bypass is reliable and scriptable.

X41 D-Sec — BadHost Disclosure Penligent — CVE-2026-48710 Deep Dive

Proof of Concept

X41 D-Sec published a proof-of-concept demonstrating the bypass. The pattern is straightforward:

Vulnerable setup: A Starlette app with middleware that blocks /admin paths by checking request.url.path.

Normal request:

GET /admin/panel HTTP/1.1
Host: legitimate.example.com

→ Middleware sees /admin/panel → Returns 403 Forbidden

Exploit request:

GET /admin/panel HTTP/1.1
Host: legitimate.example.com/?x=

→ Middleware sees / (reconstructed) → Passes through → Router dispatches /admin/panel → Returns 200 OK with admin data

A minimal Python probe for internal authorized testing:

import socket

def test_badhost(host: str, port: int, path: str):
    """Test if a service is vulnerable to BadHost.
    Only run against systems you own or are authorized to test."""

    # Control request
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, port))
    sock.sendall(f"GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n".encode())
    control_response = sock.recv(4096)
    sock.close()

    # Malformed Host request
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((host, port))
    sock.sendall(f"GET {path} HTTP/1.1\r\nHost: {host}/?x=\r\nConnection: close\r\n\r\n".encode())
    test_response = sock.recv(4096)
    sock.close()

    return control_response, test_response

The key comparison: a 403 on the control and a 200 (or different status) on the test with a malformed Host indicates a bypass.

BadHost Scanner — badhost.org OSV — Starlette CVE-2026-48710

Detection

Version-Based Detection

The simplest check is the Starlette package version:

python -c "import starlette; print(starlette.__version__)"
# If < 1.0.1, the framework is unpatched

For pip-managed environments:

pip show starlette | grep Version
# Or for comprehensive dependency inspection:
pip list | grep -i starlette

Code-Level Detection

Search for vulnerable patterns in your codebase:

# Dangerous: auth decisions from request.url
grep -rn "request\.url\.path" --include="*.py" .
grep -rn "request\.url" --include="*.py" . | grep -E "startswith|in |==|allow|deny|block"

# Vulnerable middleware patterns
grep -rn "BaseHTTPMiddleware" --include="*.py" .
grep -rn "@app\.middleware" --include="*.py" .

Log-Based Detection

Look for:

  • HTTP requests with unusual Host headers containing /, ?, #, or @
  • Successive requests showing path mismatch between Host and request target
  • Requests to protected paths (e.g., /admin/) from anonymous IPs that return 200 instead of expected 403
  • Web server access logs showing consistent 200 responses for paths normally behind auth

Network Detection

  • Monitor for Host headers containing characters outside the allowed RFC 3986 authority character set
  • Alert on HTTP requests where the Host header value does not match expected patterns (e.g., contains / or ?)
  • WAF rules: add signature for Host: */* or Host: *?* patterns

Belgium CCB Advisory — Starlette Vulnerability

Mitigation

Immediate Patching (Primary)

Upgrade Starlette to version 1.0.1 or later:

pip install --upgrade "starlette>=1.0.1"
# If you use FastAPI:
pip install --upgrade "fastapi" "starlette>=1.0.1"

For Poetry:

poetry add "starlette>=1.0.1"

For uv:

uv add "starlette>=1.0.1"

Verify the upgrade:

python -c "from packaging.version import Version; import starlette; assert Version(starlette.__version__) >= Version('1.0.1'), 'PATCH MISSING'"

Code-Level Remediation

  1. Use ASGI scope path instead of reconstructed URL:

    # Vulnerable
    if request.url.path.startswith("/admin"):
    # Safe
    path = request.scope.get("path", "")
    if path.startswith("/admin"):
  2. Move authorization to route-level dependencies (FastAPI):

    from fastapi import Depends, FastAPI
    
    app = FastAPI()
    
    async def require_admin():
        # Your auth logic here
        pass
    
    @app.get("/admin/settings", dependencies=[Depends(require_admin)])
    async def admin_settings():
        return {"data": "sensitive"}
  3. Replace path-string checks with endpoint-level guards: Instead of middleware that checks URL paths, use decorators, dependency injection, or route guards that bind to specific endpoints.

Network-Level Mitigations

  1. Reverse proxy Host validation: Place a reverse proxy (NGINX, HAProxy, Cloudflare) in front of Starlette apps that validates and sanitizes the Host header before forwarding.

    NGINX example:

    server {
        listen 443 ssl;
        server_name api.example.com;
    
        if ($http_host !~ ^(api\.example\.com)$) {
            return 400;
        }
    
        location / {
            proxy_pass http://backend;
            proxy_set_header Host $http_host;
        }
    }
  2. WAF rules: Deploy WAF rules to block Host headers containing /, ?, #, @, or space characters.

  3. Remove public HTTP listeners: For MCP servers and agent tools, remove direct HTTP listeners and place behind a gateway that validates headers.

Inventory Checklist

  • Audit all Starlette and FastAPI deployments — runtime version must be ≥ 1.0.1
  • Scan for middleware using request.url or request.url.path in security decisions
  • Check transitive dependencies (FastAPI pins Starlette versions)
  • Test with a malformed Host header against staging environments
  • Replace all path-based auth with route-level or endpoint-level authorization
  • Add Host header validation at the reverse proxy layer
  • Review MCP servers and agent frameworks for exposed HTTP listeners

Timeline

Date Event
Before May 2026 X41 D-Sec discovers the bug during OSTIF-sponsored vLLM audit
May 21, 2026 GitHub Security Advisory published — Starlette 1.0.1 released with fix
May 21, 2026 X41 publishes disclosure with proof-of-concept and detection tooling
May 26, 2026 Ars Technica breaks the story: “Millions of AI agents imperiled”
May 26, 2026 NVD publishes CVE-2026-48710 record
May 27, 2026 Tenable, Red Hat, and other vendors publish advisories
May 28, 2026 Belgium CCB (CERT) publishes national advisory
June 1, 2026 InfoQ publishes technical analysis
June 4, 2026 EMQ publishes architectural implications analysis for brokered transport
June 27, 2026 This analysis published

Architectural Lessons: Why One Bug Reached Millions

The BadHost vulnerability is noteworthy not just for what it is, but for what it reveals about current AI infrastructure deployment patterns. The reason a single library bug reached millions of agents is architectural: every MCP server, every agent harness, every inference proxy is deployed as its own HTTP server running the same ASGI library.

Each network-reachable service stores long-lived credentials to the systems it bridges. Each runs the same vulnerable ingress code. A flaw in that shared library is a fleet-wide authorization bypass — one Host-header bug against a pattern repeated millions of times.

The Brokered Transport Alternative

As the EMQ analysis highlights, moving from HTTP-native to broker-based transport (e.g., MCP-over-MQTT or A2A-over-MQTT) eliminates the entire class of bug:

  • No inbound HTTP listener on each agent → nothing to send a forged Host header to
  • No URL reconstruction → no path desynchronization
  • Centralized transport authorization (mTLS, ACLs) replaces per-service middleware

This does not solve every security problem — the broker becomes a high-value target, and application authorization must remain rigorous — but it removes the architectural single point of failure that made BadHost a fleet-wide event.

EMQ — Broker-Based Transport Security Analysis MCP-over-MQTT Specification

Sources

Cross-links automatically generated from None.