October 9, 2026
The Question Mark That Broke Authentication Across Half the Python AI Stack
A single character in an HTTP header was enough to bypass authentication on vLLM, LiteLLM, FastAPI, and more than 400,000 other projectsβ¦

By Aditya Inamdar
12 min read
A single character in an HTTP header was enough to bypass authentication on vLLM, LiteLLM, FastAPI, and more than 400,000 other projects. The bug was trivial to exploit, invisible to most scanners, and sat inside the most downloaded package nobody thinks about.
Click here to access this for free: https://inamdaraditya.medium.com/the-question-mark-that-broke-authentication-across-half-the-python-ai-stack-1c64d42a3d26?sk=d49be65f6f6bb165a1650b77396d5309
On a Friday afternoon in late May 2026, a security researcher at X41 D-Sec was auditing vLLM, the most popular open source inference server for large language models, when he noticed something strange. A request to a protected endpoint returned a 200 instead of a 403. The only difference between the two requests was a single question mark appended to the Host header.
He sent another request with a slash. Same result. Then a hash symbol. Same result.
The vulnerability he had just stumbled into, now tracked as CVE-2026β48710 and codenamed BadHost, sat inside Starlette, the lightweight ASGI framework that underpins FastAPI, vLLM, LiteLLM, Text Generation Inference, most OpenAI compatible proxies, MCP servers, and agent harnesses across the Python AI ecosystem. Starlette receives over 325 million downloads per week and is the routing core of an estimated 400,000 dependent projects. A single character injected into the HTTP Host header was enough to bypass path based authentication on any of them.
What followed was one of the most consequential open source security disclosures of 2026, a patch that took weeks to propagate through container images and virtual environments, and a hard lesson about the fragility of path based security middleware.
Table of Contents
- What Starlette Actually Does, and Why This Bug Matters
- The Root Cause: How a Host Header Poisons
request.url - The Exploit: One Character, No Credentials Required
- The Blast Radius: Everywhere FastAPI Runs
- What Attackers Could Reach
- The CVSS Dispute: Why the Official Score Understated the Threat
- The Chain: How BadHost Combined With LiteLLM for Remote Code Execution
- The Fix: What Changed in Starlette 1.0.1
- What to Do If You Cannot Patch Tonight
- The Patch Gap: Why Systems Stayed Vulnerable for Months
- The Architectural Lesson: Stop Using Paths for Authorization
- References
1. What Starlette Actually Does, and Why This Bug Matters
Starlette is a lightweight ASGI framework and toolkit for building asynchronous web services in Python. It handles routing, middleware, request parsing, and response generation. It is not a household name, but it is everywhere.
FastAPI is built on top of Starlette. vLLM, the dominant open source inference server, uses Starlette for its OpenAI compatible API. LiteLLM, the proxy layer for routing requests across model providers, uses it. Text Generation Inference (TGI), Hugging Face's inference server, uses it. MCP servers, agent harnesses, evaluation dashboards, and model management UIs across the entire Python AI stack use it.
When Starlette has a security flaw, every one of those projects has a security flaw. And because Starlette is a transitive dependency in most of them, many teams did not know they were affected until the advisory was published.
Project What it is Starlette usage
-------------------------- -------------------------------------- ----------------
FastAPI Async web framework for APIs Core dependency
vLLM LLM inference server API layer
LiteLLM LLM proxy and router API layer
Text Generation Inference HuggingFace inference server API layer
MCP servers Model Context Protocol servers Routing layer
Agent harnesses AI agent frameworks API layer
OpenAI shim proxies Drop in OpenAI API replacements API layerProject What it is Starlette usage
-------------------------- -------------------------------------- ----------------
FastAPI Async web framework for APIs Core dependency
vLLM LLM inference server API layer
LiteLLM LLM proxy and router API layer
Text Generation Inference HuggingFace inference server API layer
MCP servers Model Context Protocol servers Routing layer
Agent harnesses AI agent frameworks API layer
OpenAI shim proxies Drop in OpenAI API replacements API layer2. The Root Cause: How a Host Header Poisons request.url
The bug is a classic case of inconsistent parsing. Starlette handles HTTP requests through the ASGI specification, which provides two separate sources of truth for the request path.
# The raw ASGI path, set by the server, cannot be altered by the client
scope["path"] # e.g., "/admin"
# The reconstructed URL, built from the Host header and the raw path
request.url.path # e.g., "/abc" (poisoned)# The raw ASGI path, set by the server, cannot be altered by the client
scope["path"] # e.g., "/admin"
# The reconstructed URL, built from the Host header and the raw path
request.url.path # e.g., "/abc" (poisoned)When a request arrives, the ASGI server (Uvicorn, Hypercorn, Granian, etc.) populates scope["path"] with the raw path from the HTTP request line. This value comes directly from the wire and cannot be influenced by the client beyond sending a different request.
Starlette also reconstructs a full URL object from the request, accessible as request.url. Before version 1.0.1, it did this by concatenating the scheme (http or https), the value of the Host header, and the raw path:
# Vulnerable reconstruction (Starlette < 1.0.1)
url_string = f"{scheme}://{host_header}{raw_path}"
request.url = URL(url_string)# Vulnerable reconstruction (Starlette < 1.0.1)
url_string = f"{scheme}://{host_header}{raw_path}"
request.url = URL(url_string)The problem is that the Host header was never validated against the grammar defined in RFC 9112 Β§3.2 and RFC 3986 Β§3.2.2. According to those RFCs, a valid Host header contains only a hostname and an optional port number. Characters like /, ?, and # are not allowed.
If an attacker includes one of those characters in the Host header, the URL reconstruction produces a string with different path boundaries:
# Normal request
Host: example.com
GET /admin HTTP/1.1
# Reconstructed URL: http://example.com/admin
# request.url.path = "/admin"
# Malicious request
Host: example.com/abc?bar=
GET /admin HTTP/1.1
# Reconstructed URL: http://example.com/abc?bar=/admin
# request.url.path = "/abc"# Normal request
Host: example.com
GET /admin HTTP/1.1
# Reconstructed URL: http://example.com/admin
# request.url.path = "/admin"
# Malicious request
Host: example.com/abc?bar=
GET /admin HTTP/1.1
# Reconstructed URL: http://example.com/abc?bar=/admin
# request.url.path = "/abc"The router still dispatches the request based on scope["path"], which is /admin. The endpoint for /admin executes. But any middleware or endpoint logic that reads request.url.path for security decisions sees /abc, a path that appears to be public or authorized.
This is the vulnerability in one sentence: the router and the security middleware are reading from different sources of truth.
Component Source of truth Value
--------------------- --------------------- ---------
Router scope["path"] /admin
Security middleware request.url.path /abc
Result Router executes Middleware allowsComponent Source of truth Value
--------------------- --------------------- ---------
Router scope["path"] /admin
Security middleware request.url.path /abc
Result Router executes Middleware allows3. The Exploit: One Character, No Credentials Required
The exploit is trivial. The OSTIF advisory includes a minimal proof of concept:
# Blocked: normal Host header
curl -i -H 'Host: foo' http://target/admin
# HTTP/1.1 403 Forbidden
# Served: malformed Host header with a question mark
curl -i -H 'Host: foo?' http://target/admin
# HTTP/1.1 200 OK# Blocked: normal Host header
curl -i -H 'Host: foo' http://target/admin
# HTTP/1.1 403 Forbidden
# Served: malformed Host header with a question mark
curl -i -H 'Host: foo?' http://target/admin
# HTTP/1.1 200 OKThe only difference between the two requests is the ? appended to the Host header. No credentials, no authentication token, no session cookie. Just a single character in a header that most HTTP clients allow you to set freely.
The attack works against any application that meets three conditions:
Condition 1 Built on Starlette < 1.0.1
Condition 2 Uses path based middleware for authentication
Condition 3 Middleware reads request.url.path instead of scope["path"]Condition 1 Built on Starlette < 1.0.1
Condition 2 Uses path based middleware for authentication
Condition 3 Middleware reads request.url.path instead of scope["path"]Condition 3 is the key. If the middleware reads the raw ASGI scope path, the attack fails. If it reads request.url.path, the attack succeeds.
4. The Blast Radius: Everywhere FastAPI Runs
The affected projects are not obscure. They are the backbone of the Python AI stack.
The Centre for Cybersecurity Belgium warned that "MCP servers store credentials for each external system, effectively making them valuable for threat actors to breach" and that "credentials to third party accounts could be exploited in supply chain attacks". The affected surface includes FastAPI, vLLM, LiteLLM, text generation inference projects, most OpenAI shim proxies, MCP servers, agent harnesses, evaluation dashboards, and model management UIs.
Project What it does Exposure
--------------------------- ---------------------------------------- ---------
FastAPI Async web framework Core routing
vLLM LLM inference server API endpoints
LiteLLM LLM proxy and router Auth layer
Text Generation Inference HuggingFace inference server API layer
OpenAI shim proxies Drop in OpenAI replacements Auth layer
MCP servers Model Context Protocol servers Credentials
Agent harnesses AI agent frameworks Tool access
Eval dashboards Model evaluation UIs Admin accessProject What it does Exposure
--------------------------- ---------------------------------------- ---------
FastAPI Async web framework Core routing
vLLM LLM inference server API endpoints
LiteLLM LLM proxy and router Auth layer
Text Generation Inference HuggingFace inference server API layer
OpenAI shim proxies Drop in OpenAI replacements Auth layer
MCP servers Model Context Protocol servers Credentials
Agent harnesses AI agent frameworks Tool access
Eval dashboards Model evaluation UIs Admin accessThe MCP servers are the highest value target. Model Context Protocol servers store credentials for external systems: email accounts, calendar APIs, database connections, cloud provider tokens. An attacker who bypasses authentication on an MCP server does not just access the server. They access everything the server is connected to.
5. What Attackers Could Reach
X41 D-Sec researcher Markus Vervier ran a scan using the team's detection tool and published the categories of data that were exposed on live, unpatched systems.
Industry sector Exposed data
---------------------- ----------------------------------------------
Biopharma AI Clinical trial databases, M&A data, SSRF
Identity Verification Face analysis, KYB, live PII, internal codebase
IoT / Industrial SSH to devices via bastion, remote code execution
Email / SaaS Full mailbox read/send/delete, S3 export, webhooks
HR / Recruitment Candidate PII, hiring pipeline data
CMS / Marketing Subscriber lists, mass email campaigns
Document Management Read, upload, modify scanned documents
Cloud Monitoring AWS topology, distributed traces, metric queries
Cybersecurity Asset inventory, live Nuclei scanner access
Personal Health Nutrition logs, expenses, subscriptionsIndustry sector Exposed data
---------------------- ----------------------------------------------
Biopharma AI Clinical trial databases, M&A data, SSRF
Identity Verification Face analysis, KYB, live PII, internal codebase
IoT / Industrial SSH to devices via bastion, remote code execution
Email / SaaS Full mailbox read/send/delete, S3 export, webhooks
HR / Recruitment Candidate PII, hiring pipeline data
CMS / Marketing Subscriber lists, mass email campaigns
Document Management Read, upload, modify scanned documents
Cloud Monitoring AWS topology, distributed traces, metric queries
Cybersecurity Asset inventory, live Nuclei scanner access
Personal Health Nutrition logs, expenses, subscriptionsThe scan results are not theoretical. These are live systems, exposed to the internet, with authentication bypassed by a single character in a header that most security tools do not flag.
6. The CVSS Dispute: Why the Official Score Understated the Threat
The official CVSS v3.1 score for CVE-2026β48710 is 6.5, which is rated Moderate. The researchers who discovered the bug disagreed. The CCB advisory lists the score as 6.5 with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N, but multiple security organizations argued that this rating understated the real world impact.
Source Rating Notes
------------------------ ---------------- ----------------------------------
NVD / CVSS v3.1 6.5 (Moderate) AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
Secwest 7.0 (High) Materially understates downstream
OSTIF "Critical" Reaches entire Python AI stackSource Rating Notes
------------------------ ---------------- ----------------------------------
NVD / CVSS v3.1 6.5 (Moderate) AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
Secwest 7.0 (High) Materially understates downstream
OSTIF "Critical" Reaches entire Python AI stackOSTIF stated that the official rating "severely understates the severity of the bug downstream". Secwest claimed that the classification "materially understates" the threat, noting that at the time of disclosure, biopharma AI data, identity verification data, IoT and industrial data, emails, SaaS data, and more were all exposed.
The CVSS calculator assumes the vulnerability is isolated to a single Starlette application. But Starlette is a transitive dependency. The score does not account for the fact that the bug propagates through FastAPI, vLLM, LiteLLM, MCP servers, and hundreds of thousands of other projects. The downstream impact is what makes this a critical vulnerability, not the impact on Starlette itself.
7. The Chain: How BadHost Combined With LiteLLM for Remote Code Execution
BadHost was serious on its own. But it became catastrophic when chained with a separate vulnerability in LiteLLM.
LiteLLM had a command injection flaw tracked as CVE-2026β42271 with a CVSS score of 8.7. The vulnerability allowed any authenticated user to run arbitrary commands on the host through two MCP server test endpoints. On its own, the flaw required authentication. That requirement made it less urgent.
Horizon3.ai researchers demonstrated that chaining CVE-2026β42271 with CVE-2026β48710 removed the authentication requirement entirely. The BadHost bypass made the LiteLLM flaw reachable by an unauthenticated attacker. The combined CVSS score of the chain was 10.0, the maximum severity.
CVE CVSS Requires auth? Combined effect
------------------- ------ --------------- ---------------------
CVE-2026-42271 8.7 Yes Command injection
CVE-2026-48710 6.5 No Auth bypass
Combined chain 10.0 No Unauthenticated RCECVE CVSS Requires auth? Combined effect
------------------- ------ --------------- ---------------------
CVE-2026-42271 8.7 Yes Command injection
CVE-2026-48710 6.5 No Auth bypass
Combined chain 10.0 No Unauthenticated RCECISA added CVE-2026β42271 to its Known Exploited Vulnerabilities catalog on June 8, 2026, citing evidence of active exploitation. CVE-2026β48710 itself was later added to the KEV catalog on September 2, 2026. Both were confirmed exploited in the wild.
A third related vulnerability, CVE-2026β48746, was issued for vLLM's OpenAI compatible API server. From version 0.3.0 until 0.22.0, the AuthenticationMiddleware could be bypassed using the same Host header manipulation, allowing attackers to use the API without providing the configured VLLM_API_KEY. The vulnerability had a CVSS score of 9.1.
8. The Fix: What Changed in Starlette 1.0.1
Starlette 1.0.1 was released on May 22, 2026. The fix is a single behavioral change: validate the Host header before using it to reconstruct request.url.
# Patched behavior (Starlette >= 1.0.1)
# 1. Validate Host header against RFC 9112 Β§3.2 / RFC 3986 Β§3.2.2
# 2. If valid, use it to reconstruct request.url
# 3. If invalid, fall back to scope["server"] for the host value# Patched behavior (Starlette >= 1.0.1)
# 1. Validate Host header against RFC 9112 Β§3.2 / RFC 3986 Β§3.2.2
# 2. If valid, use it to reconstruct request.url
# 3. If invalid, fall back to scope["server"] for the host valueThe patch changes nothing about the routing logic. The router still uses scope["path"] for dispatching. The endpoint still executes. But the reconstructed request.url.path now matches the real path, so middleware that reads request.url.path sees the correct value.
The upgrade is a drop in replacement. There is no API change, no configuration change, and no behavioral change for applications that were not affected.
pip install --upgrade "starlette>=1.0.1"pip install --upgrade "starlette>=1.0.1"The fix is also available in distribution packages. Debian 12 received starlette 0.26.1-1+deb12u1, Debian 13 received starlette 0.46.1-3+deb13u2, and Fedora 43 received updated python-starlette packages.
For vLLM, the fix landed in version 0.22.0. For LiteLLM, the fix landed in version 1.84.0. Every affected project had to release its own patch to bump the Starlette dependency.
9. What to Do If You Cannot Patch Tonight
If you cannot upgrade immediately, two mitigations reduce the risk.
Mitigation 1: Reject malformed Host headers at the proxy. Configure your reverse proxy, load balancer, or WAF to reject Host headers containing /, ?, or #. Nginx, Caddy, and Cloudflare can all do this. The CCB recommends deploying "a reverse proxy in front of your ASGI server to validate and normalise the Host header before forwarding, effectively neutralising the attack path".
# Nginx example
if ($host ~* "[/?#]") {
return 400;
}# Nginx example
if ($host ~* "[/?#]") {
return 400;
}Mitigation 2: Stop using request.url.path for security decisions. Use the raw ASGI scope path instead.
# Vulnerable
if request.url.path.startswith("/admin"):
# check authorization
# Safe
if request.scope["path"].startswith("/admin"):
# check authorization# Vulnerable
if request.url.path.startswith("/admin"):
# check authorization
# Safe
if request.scope["path"].startswith("/admin"):
# check authorizationThe CCB recommends avoiding path based authentication middleware entirely and tying authentication to the endpoint rather than the path used to reach it: "Avoid path based authentication middleware. Instead, authentication should be tied to the endpoint itself rather than the path used to reach it". That is the correct architectural fix, and it applies beyond this specific CVE.
A third mitigation is to restrict network exposure of affected services. The DevGuard advisory notes that "the bypass is blocked by any upstream layer that validates or normalizes Host, such as a CDN or WAF, a reverse proxy with server_name allowlists, or a host based load balancer".
10. The Patch Gap: Why Systems Stayed Vulnerable for Months
Starlette 1.0.1 was released on May 22, 2026. By August 2026, OSTIF was still warning about "slow uptake of updated versions" and "discovery of more vulnerable live services". By September, safeguard.sh reported "Host Header Bug Confirmed Exploited Despite Moderate CVSS" and noted that CVE-2026β48710 had been added to CISA's KEV catalog on September 2, 2026.
The patch gap is a known problem in open source security. A vulnerability is patched, an advisory is published, and then nothing happens. Teams do not upgrade transitive dependencies. Container images are not rebuilt. The vulnerable version persists in production long after the fix is available.
BadHost was particularly bad for three reasons.
It was a transitive dependency. Most affected teams did not have Starlette in their requirements.txt. They had FastAPI, or vLLM, or LiteLLM. They did not know Starlette was in the dependency tree until the advisory told them.
The CVSS was Moderate. A 6.5 score does not trigger the same urgency as a 9.8. Teams that use severity scores to prioritize patching deprioritized this one.
It was easy to overlook in a diff. The patch is a single line in a URL parsing function. If you were tracking your dependencies manually, you might not have noticed that Starlette 1.0.0 to 1.0.1 was a security critical update.
OSTIF described the situation as a "responsibility gap" where the maintainer patched the bug, but the ecosystem did not upgrade. The fix exists. It is not deployed.
11. The Architectural Lesson: Stop Using Paths for Authorization
BadHost is a specific bug in a specific library, but it is also a symptom of a broader design pattern that has been fragile for years. Path based authorization is fragile because paths are not the only representation of a request. Every HTTP framework has multiple ways to represent a request path: the raw path, the parsed URL path, the normalized path, the forwarded path, the rewritten path. If the security check reads one and the router reads another, there is a gap.
Representation Source Can be influenced?
------------------------- -------------------------- --------------------
scope["path"] ASGI server, from wire No
request.url.path Starlette, from Host header Yes (pre-1.0.1)
X-Forwarded-Path Proxy headers Yes
request.scope["root_path"] ASGI server, from config NoRepresentation Source Can be influenced?
------------------------- -------------------------- --------------------
scope["path"] ASGI server, from wire No
request.url.path Starlette, from Host header Yes (pre-1.0.1)
X-Forwarded-Path Proxy headers Yes
request.scope["root_path"] ASGI server, from config NoThe correct pattern is to make security decisions on values that the client cannot influence. The raw ASGI scope path is one such value. The reconstructed URL path is not.
This lesson generalizes. Every framework that reconstructs a URL from headers and then uses that reconstructed URL for security is vulnerable to the same class of bug. The fix in Starlette 1.0.1 is correct, but the architectural fix is to stop relying on request.url.path for anything security sensitive.
12. References
-
OSTIF, "Disclosing the BADHOST Vulnerability in Starlette," ostif.org, May 26, 2026. https://ostif.org/disclosing-the-badhost-vulnerability-in-starlette/
-
X41 D-Sec, "X41β2026β002: Starlette Host Header Path Authorization Bypass," x41-dsec.de, May 2026. https://x41-dsec.de/lab/advisories/x41-2026-002-starlette/
-
Centre for Cybersecurity Belgium, "Warning: Vulnerability in Starlette framework and related frameworks like FastAPI exposes millions of servers to authentication bypass," ccb.belgium.be, May 28, 2026. https://ccb.belgium.be/de/advisories/warning-vulnerability-starlette-framework-and-related-frameworks-fastapi-exposes
-
Mallory, "BadHost: Starlette Host Header Path Authorization Bypass (CVE-2026β48710)," mallory.ai, May 26, 2026. https://mallory.ai/vulnerabilities/CVE-2026-48710
-
VulDB, "CVE-2026β48710 in starlette," vuldb.com, May 27, 2026. https://vuldb.com/cve/CVE-2026-48710
-
The Hacker News, "LiteLLM Flaw CVE-2026β42271 Exploited in the Wild, Chains to Unauthenticated RCE," thehackernews.com, June 9, 2026. https://thehackernews.com/2026/06/litellm-flaw-cve-2026-42271-exploited.html
-
VulDB, "CVE-2026β48746 in vLLM," vuldb.com, June 23, 2026. https://vuldb.com/cve/CVE-2026-48746
-
DevGuard Vulnerability Database, "GHSA-4xpc-pv4p-pm3w," docs.devguard.org, June 17, 2026. https://docs.devguard.org/vulnerability-database/GHSA-4xpc-pv4p-pm3w/
-
Korben, "BadHost: One character and your AI agent switches sides," korben.info, May 28, 2026. https://korben.info/en/badhost-one-character-ai-agent-starlette-vulnerability.html
-
Yahoo Tech, "Worrying open source security issue 'BadHost' could affect millions of AI agents," tech.yahoo.com, May 27, 2026. https://tech.yahoo.com/cybersecurity/articles/worrying-open-source-security-issue-160500962.html
-
GitHub Advisory Database, "GHSA-86qp-5c8j-p5mr: Starlette Host Header Path Authorization Bypass." https://github.com/Kludex/starlette/security/advisories/GHSA-86qp-5c8j-p5mr
-
Bhanunamikaze, "BadHost CVE-2026β48710 Exploit: Detection scanner," GitHub, May 29, 2026. https://github.com/Bhanunamikaze/BadHost-CVE-2026-48710-Exploit
-
Debian Security Tracker, "CVE-2026β48710," security-tracker.debian.org. https://security-tracker.debian.org/tracker/CVE-2026-48710
-
Safeguard.sh, "Starlette CVE-2026β48710: Host Header Bug Confirmed Exploited Despite Moderate CVSS," safeguard.sh, September 16, 2026.
-
Open Source For You, "Starlette Host Header Flaw Threatens 400,000+ Dependent GitHub Projects," opensourceforu.com, May 29, 2026.
-
IONIX, "CVE-2026β48710 Authentication Bypass Starlette (Python ASGI Framework) prior to version 1.0.1," ionix.io, June 12, 2026.
-
CERT.UG, "vLLM OpenAI Compatible API Server Authentication Bypass Allows Unauthenticated AI Inference Access (CVE-2026β48746)," cert.ug, July 13, 2026.
-
Horizon3.ai, "CVE-2026β42271 Chained With CVE-2026β48710," horizon3.ai, June 2026. https://horizon3.ai/attack-research/vulnerabilities/cve-2026-42271-chained-with-cve-2026-48710/
-
CISA, "CISA Adds Two Known Exploited Vulnerabilities Catalog," cisa.gov, June 8, 2026. https://www.cisa.gov/news-events/alerts/2026/06/08/cisa-adds-two-known-exploited-vulnerabilities-catalog
-
badhost.org, "BadHost Scanner." https://badhost.org/
This article is based on the official Starlette security advisory, the X41 D-Sec technical analysis, the OSTIF disclosure, the Centre for Cybersecurity Belgium advisory, and independent reporting current as of October 2026. If your application uses Starlette, FastAPI, vLLM, LiteLLM, or any of the other affected projects, verify that you are running Starlette 1.0.1 or later. The scanner at badhost.org can check individual endpoints.