Skip to content
Journal

Cybersecurity · Application Security

CVE-2026-48710: Starlette's BadHost Bug, Explained

BadHost sat quietly patched since May. In September, CISA flagged active exploitation. If you run FastAPI, vLLM, or any Starlette app, here's what to check.

Abhishek Gupta

Abhishek Gupta

6 min read

Title card for CVE-2026-48710 showing a malformed Host header shifting Starlette's parsed request path past a security check

Sponsored

Share

CVE-2026-48710 is a Host-header parsing bug in Starlette that lets a single malformed character turn a 403 into a 200 on a protected endpoint, no password, no session, nothing but a crafted header. It was patched quietly in May. It’s back in the news because CISA added it to the Known Exploited Vulnerabilities catalog on September 2, evidence that someone is now exploiting it directly in the wild, not just as a chained step in one specific attack. If you run FastAPI, vLLM, or anything built on Starlette, this is worth five minutes of your afternoon.

Where Starlette gets it wrong

Every ASGI app built on Starlette needs to know, for each request, what URL was requested. Starlette builds that by taking the raw Host header the client sent and concatenating it with the request path: roughly f"{scheme}://{host_header}{path}". That’s a reasonable-looking approach, right up until you ask what happens when the Host header itself contains characters that shouldn’t be there.

RFC 9112 and RFC 3986 both specify what a valid Host header looks like, and a /, ?, or # isn’t part of it. Starlette didn’t check for that before this patch. So if a client sends a Host header containing one of those characters, the concatenation shifts. request.url.path, the attribute a lot of security middleware reads to decide “is this request allowed to reach this route,” can come back with a different value than the path the router actually used to dispatch the request. You get two different answers to “what path is this” from the same request, one used for the security check, one used for the actual routing decision, and an attacker who understands the mismatch gets to pick which one lies.

X41 D-Sec’s demonstration was blunt: one invalid character in the Host header, and a protected admin endpoint that should return 403 returns 200. No authentication step was bypassed in the traditional sense. The security check just checked the wrong string. CSO Online’s writeup has the full disclosure history if you want the original research writeup rather than the CISA follow-up.

Why the severity score is genuinely disputed

Starlette’s maintainer rated this 6.5, moderate. X41 D-Sec, who found it, scored it 7.0 and pushed back publicly, arguing the number undersells what the bug enables once you look past the isolated case. Both numbers describe the same root cause; they disagree on how much weight to give the fact that the actual impact depends entirely on what the application built on top of Starlette does with request.url.path. An app that never uses it for security decisions is unaffected in practice. An app that gates an admin panel, an internal API, or an MCP tool endpoint on it is looking at a clean authentication bypass, and researchers have demonstrated chains from there into SSRF and, in LiteLLM’s specific case, full remote code execution when paired with a separate command-injection bug.

That’s the useful takeaway from the scoring dispute, not who’s right: a parsing bug this far down the stack inherits its real-world severity from every application built on top of it, and CVSS is bad at scoring a bug whose blast radius is “depends what 400,000-plus downstream projects do with one attribute.”

Why this affects more than LiteLLM

Starlette is the ASGI toolkit underneath FastAPI, and one layer further down the stack, underneath a big share of the Python AI-serving world: vLLM, most MCP servers, and a long list of LLM gateways and OpenAI-compatible proxies. X41 D-Sec found this bug while doing an OSTIF-sponsored security audit of vLLM, not while looking at Starlette itself, which is a useful reminder of how deep this dependency runs. If your team runs an LLM gateway in front of production traffic or serves models with vLLM, Starlette is very likely somewhere in that stack whether or not it appears in your own requirements.txt directly.

That breadth is also why this is worth treating as its own issue rather than a footnote to the LiteLLM story it first became famous through. LiteLLM’s exploit chain got the headlines because researchers demonstrated full unauthenticated RCE with it, but BadHost as CISA logged it in September is a standalone, independently exploited vulnerability in any Starlette-based service, patched or not, that happens to check request.url.path for anything security-relevant.

Fixing it

pip show starlette

Anything below 1.0.1 needs an upgrade:

pip install --upgrade "starlette>=1.0.1"

If your framework pins its own Starlette version, check that dependency directly rather than assuming your last pip install --upgrade fastapi pulled a fixed release along with it:

pip show fastapi | grep -i requires
pip list | grep -i starlette

A reverse proxy in front of your service, nginx, Apache, most managed load balancers, typically rejects malformed Host headers before they reach your app, which closes the most direct external path. That’s a mitigation, not a fix. Any internal service-to-service call that bypasses the edge proxy, any Starlette instance reachable directly, and any deployment where “well-formed Host header” was never actually enforced at the network edge is still exposed until the package itself is patched.

The part worth remembering after you’ve patched

This bug was fixed in May. It became urgent again in September because CISA’s KEV listing is evidence of independent, ongoing exploitation, not a re-announcement of the same May finding. That gap, four months between “patch available” and “we have to write about this because it’s actively being hit,” is the more durable lesson than the specific header-parsing detail. A dependency five layers down your stack getting patched doesn’t mean the org running your production traffic actually pulled it, and “we’re not directly on Starlette” isn’t the same claim as “nothing in our stack is.” If you haven’t checked your pip list for starlette in the last few months, whatever wraps your API, this is a reasonable prompt to do it today rather than after the next KEV alert names something you’re running. Auditing exactly which framework versions sit under a production stack is exactly the kind of gap our engineering team checks for before it becomes someone else’s incident report.

Frequently asked questions

What is CVE-2026-48710, in plain terms?
It's a bug in how Starlette parses the Host header on incoming requests. Starlette reconstructs the full request URL by joining the Host header directly with the request path, without first checking that the Host header is well-formed. If an attacker puts an invalid character, like a slash, question mark, or hash, into the Host header, that reconstruction shifts where Starlette thinks the path starts and ends, and code that checks request.url.path for security decisions can end up checking the wrong thing.
Am I affected if I use FastAPI?
Yes, if your Starlette version is anywhere from 0.8.3 up to and including 1.0.0. FastAPI depends directly on Starlette, so this isn't a separate bug you might have avoided by not using Starlette's lower-level API yourself; it's in the request-parsing layer FastAPI is built on top of. Check your installed version with `pip show starlette` and confirm it's 1.0.1 or later.
Does a reverse proxy in front of my app protect me?
Often, yes, in part. Nginx and most production reverse proxies reject requests with malformed Host headers before they ever reach your Python process, which closes off the most direct exploitation path. It's not a substitute for patching, though: internal services, service-to-service calls that skip the edge proxy, and any deployment where Starlette is reachable directly are still exposed regardless of what sits in front of your public-facing traffic.
Why is this suddenly relevant again if it was patched in May?
Because a patch existing and a codebase actually running it are different facts. CISA added CVE-2026-48710 to its Known Exploited Vulnerabilities catalog on September 2, 2026, based on evidence of independent active exploitation, separate from the specific LiteLLM chain it was first known for. That's the signal that attackers are now targeting the bug directly across the wider Starlette ecosystem, not just theorizing about it, four months after the quiet May fix most teams never revisited.
What does this have to do with vLLM and LLM gateways specifically?
X41 D-Sec found this bug while auditing vLLM under an OSTIF-sponsored security review, not while looking at Starlette directly. vLLM, LiteLLM, and most Python-based MCP servers and LLM gateways sit on top of Starlette for their HTTP layer, which means a parsing bug this low in the stack reaches an unusually wide set of AI infrastructure at once. It's also the bug that, chained with a separate LiteLLM command-injection flaw, produced full unauthenticated remote code execution in that specific case, covered separately.

Sources

Sponsored

Sponsored

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored