BadHost Breaks Into FastAPI and vLLM With a Single Character

Want to learn ethical hacking? I built a complete course. Have a look!
Learn penetration testing, web exploitation, network security, and the hacker mindset:
→ Master ethical hacking hands-on
Hacking is not a hobby but a way of life!
BadHost is one character in an HTTP header that bypasses authentication on FastAPI, vLLM, LiteLLM, and the Python MCP SDK. They all run on Starlette. Starlette has more than 400,000 dependent projects on GitHub. The bug is in Starlette.
It is tracked as CVE-2026-48710, disclosed on May 22. Starlette is the framework that sits underneath FastAPI and handles the basic plumbing of web requests: routing, middleware, everything that happens before your code runs. Through FastAPI it reaches vLLM, LiteLLM, Text Generation Inference, most OpenAI-compatible proxy servers, MCP servers, agent frameworks, and model management dashboards.
Understanding the bug means understanding one thing Starlette does with every request. When a browser or API client connects to a server, it sends a Host header. That header just says which domain the request is for. Starlette takes that value and uses it to build the full URL, by gluing the scheme, the host, and the path together into one string and reading the path back out of the result. So if the Host header says example.com and the request path is /admin, Starlette builds http://example.com/admin, reads /admin as the path, and passes it to middleware. Middleware uses that path to decide what to block and what to allow.
The problem is that Starlette never checks whether the Host header actually contains a valid hostname. An attacker sends whatever they want. Starlette trusts it.
Inject a ? character into the Host header. Starlette concatenates http://foo? with /admin and gets http://foo?/admin. When it parses that string, the ? shifts the boundary between path and query string. The path becomes empty. The query string becomes /admin. Middleware reads request.url.path, sees an empty string, decides there is nothing to block, and passes the request through. Meanwhile the router, which reads the path directly from the raw ASGI scope and never touches the reconstructed URL, still dispatches the request to /admin. The endpoint runs. The authentication never triggered.
| |
This exploit is fully automatable. One character in a request header works with any scanner already running on the internet, no special tooling needed. That makes it suitable for mass scanning, not just targeted attacks.
The same trick works with / and # in the Host header, not just ?. Each one shifts the URL boundaries in a slightly different way, but the result is the same. Middleware sees something harmless. The router still sends the request where it was always going.
Researchers JJ, Yassine El Baaj, and Markus Vervier at X41 D-Sec were doing a sponsored source code audit of vLLM for OSTIF when they noticed this pattern. They were not looking at Starlette. They were not looking at authentication. They were auditing an LLM inference server and stumbled into a framework-level bug that reaches everything built on that framework.
The timeline of how it got from discovery to public:
- → January 27, 2026: bug found during the vLLM audit
- → February 4: vendor contacted with proof of concept
- → March 1: patch proposed by the vendor
- → May 21: patch released, nearly three months after it was ready
- → May 22: public disclosure, CVE assigned, scanner live
Vendor notification and public disclosure landed on the same day. Operators had zero lead time to patch before the exploit was public.
The official CVSS score came in at 6.5, Moderate. Starlette’s own advisory looked at the bug as a library-level issue and scored it there. X41 scores it 7.0 and calls the downstream impact critical. OSTIF published a disclosure saying the official rating severely understates what it means for the ecosystem built on top of Starlette. That gap matters, because plenty of teams use CVSS numbers to decide how urgently to patch. A 6.5 gets scheduled. A critical gets fixed tonight.
Part of what makes this difficult to capture in a score is that the vulnerability is structural. It does not sit in one file. It spans three separate layers. ASGI servers pass the raw Host header to Starlette without checking it. Starlette trusts that value and uses it to build the URL. The developers writing middleware assume that the path Starlette hands them is the real path, because why would it not be. Each component behaves correctly on its own. The vulnerability only exists in the interaction between them. X41 went through popular open-source projects and found real attack chains built on this one primitive. Authentication bypass, server-side request forgery, and in some cases remote code execution. BadHost is not just a theoretical path in.
What becomes reachable when middleware is bypassed:
- → Admin routes and model management panels
- → API key issuance endpoints
- →
/metricsand/shutdownsurfaces - → Tool execution and agent tooling endpoints
- → Finetune submission and dataset upload
- → Anything sitting behind a path prefix that middleware was supposed to block
Internet-wide scanning has already found vulnerable deployments across AI infrastructure, research environments, and LLM serving setups that were assumed to be internal-only. Google ADK-Python, Ray Serve, and BentoML are also in the affected list.
There is one more layer to this that most coverage has not touched. MCP, the Model Context Protocol that Anthropic released to let AI assistants connect to external services, uses the Python MCP SDK. That SDK is built on Starlette. MCP servers store credentials: OAuth tokens, database connections, email access, API keys. One compromised MCP server does not just give an attacker access to one service. It gives access to everything that AI assistant was connected to. The Python MCP SDK is directly affected.
BadHost is not an isolated incident. More than 40 CVEs against MCP implementations have been filed in 2026 alone. The pattern is consistent: AI infrastructure built fast, security assumptions inherited from upstream libraries, nobody checking whether those assumptions actually held.
The audit that found this bug was sponsored by OSTIF, which is funded through Alpha-Omega, a security initiative backed by Anthropic, AWS, GitHub, Google, Google DeepMind, Microsoft, and OpenAI. In March 2026, those same companies committed $12.5 million to Alpha-Omega and the Open Source Security Foundation to improve open source security. The vulnerability that exposed their own AI infrastructure was found with their own security money. Anthropic’s own AI security project, Project Glasswing, found over 10,000 vulnerabilities in open source code, and missed this one. The reason is structural: a bug that only exists in the interaction between three separate layers is not the kind of thing an automated scanner finds by looking at one codebase at a time.
The deployments most at risk are the ones that look the least dangerous: research setups, local LLM runners, eval environments, and internal tools. These run direct to uvicorn, hypercorn, daphne, or granian without a reverse proxy in front, because they were never designed to face the internet. In practice they often do anyway, through exposed ports, shared VPN segments, and internal networks where being on the same network is treated as being safe.
The basic PoC above works with curl because curl passes the ? character through without normalizing it. For more complex injections, like inserting a full path segment into the Host header, standard HTTP clients do normalize the value and block the test. The badhost.org scanner uses raw TCP sockets for that reason, bypassing client-side normalization entirely.
The fix is Starlette 1.0.1, released 21 May 2026. That version checks the Host header before using it to build the URL. If the value contains characters that do not belong in a hostname, Starlette ignores it and falls back to the actual server address instead. Upgrading means upgrading the package in every environment where it runs, including Docker images. Running pip list on the host is not enough if Starlette is pinned inside a container image that was built before the patch. Check the images.
Beyond upgrading, there is a permanent fix at the code level. Replace request.url.path with request.scope["path"] in any middleware that makes security decisions based on the request path. The scope path comes directly from the ASGI server, not from a reconstructed URL, and it is the same value the router uses. Using it in security middleware closes the gap regardless of what the Host header contains.
| |
One important clarification: if you use FastAPI’s built-in Depends() for authentication, you are not affected by this. That system works from route matching, not from request.url.path. The risk is specifically in custom BaseHTTPMiddleware or raw ASGI middleware where security decisions are made based on the request path. If that describes your setup, patch and switch to request.scope["path"].
If patching is not immediate, a properly configured reverse proxy provides mitigation. Nginx and Apache reject malformed Host headers by default. One caveat: if your setup uses HTTP/3 or QUIC as the transport layer between the client and the proxy, the way the Host header gets handled is different and not all implementations treat it the same.
Test it explicitly with the PoC before relying on the proxy as a mitigation. And it only works if the proxy is actually in front of the application and the application is not also trusting X-Forwarded-Host values from untrusted sources.
For log review, look for access log entries where the Host header contains /, ?, #, \, or @. Legitimate clients never send those characters in a Host header. Any entry with one of those characters is a sign someone tested or used this path against that server.
- → Test online: badhost.org
- → Patch:
pip install "starlette>=1.0.1" - → Affected versions: Starlette 0.8.3 through 1.0.0
- → GitHub advisory: GHSA-86qp-5c8j-p5mr
- → X41 advisory: X41-2026-002
If you want to understand how attackers think about HTTP request manipulation, authentication bypass, and real CVE exploitation from the ground up, my ethical hacking course covers all of it:
Hacking is not a hobby but a way of life. 🎯
Sources: X41-2026-002 | GHSA-86qp-5c8j-p5mr
→ Stay updated!
Get the latest posts in your inbox every week. Ethical hacking, security news, tutorials, and everything that catches my attention. If that sounds useful, drop your email below.