Starlette's BadHost Is on CISA KEV. FastAPI Apps Inherit It

CVE-2026-48710 tricks request.url via a malformed Host header. CISA listed it September 2. What to patch, what to log, what pip will not fix.

CISA added CVE-2026-48710 to the Known Exploited Vulnerabilities catalog on September 2. The name on the advisory is Starlette. The name in your requirements file is probably FastAPI. The federal civilian deadline under BOD 26-04 is September 16. That date does not bind a European company. A KEV listing is still the public signal that someone is using this in the wild.

The bug is not new. X41 D-Sec found it in January during a vLLM audit that OSTIF ran with Alpha-Omega money. Starlette 1.0.1 already fixed it before OSTIF wrote the long version on May 26. What is new is the catalog, the summer of LiteLLM chaining, and the number of Python web processes that still ship a Starlette older than 1.0.1 inside a Docker image nobody has rebuilt since spring.

We have a FastAPI production checklist and an ASGI server comparison. Those assume request.url is telling the truth. This week it might not be.

What BadHost actually desynchronizes

A Starlette app has two views of a request. The router matches the path that arrived on the wire. request.url is a rebuilt object: scheme, Host header, path, query, glued together and parsed again. Versions before 1.0.1 did that rebuild without checking the Host header against RFC 9112 and RFC 3986. If Host contains a character that, in a URL, starts a query string or a fragment or a path, the rebuilt request.url.path slides. The router does not.

Daniel O’Grady’s Hard2bit writeup, published September 5, is the clearest English description I have seen. Middleware that allows unauthenticated access to everything except /admin or /v1/models reads the fake path, decides this is public, and waves the request through. The router then runs the real handler. No password. No user interaction. You have to know the target path. That is not a high bar.

CVSS is 6.5. The people who named it BadHost on badhost.org said the score undersells the ecosystem, because the flaw leaks nothing by itself. It turns off the checks that used request.url. Tenant separation, billing quotas, CSRF, prefix ACLs. Harm is whatever those checks were doing.

You are exploitable when three things coincide: Starlette before 1.0.1; some middleware, dependency, or decorator that makes a security decision from request.url or request.url.path; and no reverse proxy dropping a malformed Host. OSTIF says nginx, Apache httpd, and Cloudflare do that in default configuration. HTTP/3 and QUIC front ends need their own test. If you pointed uvicorn, hypercorn, daphne, or granian at the internet, you skipped that layer.

The durable code fix, even after you upgrade, is to read request.scope["path"] for security decisions. That is the unreconstructed path. OSTIF treats it as the version that survives the next parser bug.

Why a 6.5 made KEV

KEV is about exploitation, not elegance. Horizon3 showed in June that BadHost turns LiteLLM’s authenticated command injection, CVE-2026-42271, into unauthenticated remote code execution. The chain is a 10. CISA put the LiteLLM CVE on KEV on June 9. Wiz watched honeypots eat miner installs through LiteLLM’s MCP connection-test routes. Microsoft Threat Intelligence published three AI-infrastructure intrusions at the end of August, one of them a LiteLLM gateway, and Wiz dropped 90 days of honeypot telemetry a day later.

The LiteLLM bug is a subprocess spawned when the proxy tests an MCP server over stdio. Alone, you need proxy credentials. In front of BadHost, you do not. Wiz describes a fake MCP config whose command downloads a miner, then a valid MCP handshake so the test is logged as success. In the same sessions, attackers pulled the proxy’s master credential out of loaded Python module state. Microsoft’s reconstructed intrusion started by reading process environment, then persistence via SSH keys and cron.

That is why CISA bothered with a 6.5 on September 2, in the same batch as SonicWall SMA, Sangoma, Artifactory, Kestra, and a second LiteLLM CVE (CVE-2026-59822). Federal agencies have until September 16.

Hard2bit is careful on a point that matters for FastAPI people: neither Microsoft nor Wiz has yet documented BadHost against ordinary FastAPI apps outside the AI gateway pile. The surface is much larger. The flaw is the same. Watch for scanner noise this week. Assume the AI-gateway chain is real now.

FastAPI, images, and why pip on the host lies

You probably never typed pip install starlette. FastAPI depends on it. So do LiteLLM, vLLM, a pile of OpenAI-compatible proxies, MCP servers, agent runtimes, and model UIs. OSTIF’s affected-software list is basically “Python that speaks HTTP in front of a model.” Wiz cites a State of AI in the Cloud figure that 90 percent of cloud environments run some self-hosted AI software. Even if that number is marketing-adjacent, the inventory problem is not. Hardly anyone has Starlette in the asset register.

GitLab’s advisory database tied fifteen Red Hat RHSA notices to this one CVE, one per product that bundled the library. If your scanner only looks at the base AMI, you will miss the copy inside the LiteLLM image the data team started with docker compose on a high port.

Starlette 1.0.1 is the library fix. LiteLLM 1.83.7, out May 8, fixes the command injection and the bundled Starlette. Upgrading the package on the host does nothing when the process runs from a six-month-old image. Rebuild. Redeploy. Do not trust pip list on the bastion.

If you cannot rebuild this week: put a proxy that rejects bad Host headers in front; switch security checks to request.scope["path"]; take admin and key-management UIs off the internet. Belgium’s CCB said to bind authentication to the endpoint, not the path prefix. Microsoft’s advice for AI gateways is to treat them as Tier 0 secret stores: auth on API and UI, no shared master key, secrets not in process environment. The Microsoft intrusion started with /proc/1/environ. If your API keys live there, the rest of the conversation is rotation order.

X41 published Semgrep and CodeQL rules for the request.url.path pattern. badhost.org has a remote checker. Use both. The checker only sees what the internet sees. The rules see what your middleware does.

What to log and what to hunt

The first indicator is the Host header. Log it at the proxy. Alert on /, ?, or # inside Host. If there is no proxy, the ASGI server has to emit that log. Horizon3 lists malformed Host as an IoC. It is cheap.

On LiteLLM, Horizon3 wants eyes on /mcp-rest/test/connection and /mcp-rest/test/tools/list, routes that should be quiet. Microsoft published KQL that looks for the gateway process as parent of a shell, a Python interpreter, or curl, plus reads of /proc/1/environ, command lines that mention DATABASE_URL or LiteLLM’s model and token tables, and outbound IPs with no DNS name.

If you get one of those hits, rotate before you stop the container. Microsoft’s reconstructed attacker copied credentials first. Provider keys, virtual keys, database URL, UI passwords. Then check the provider accounts for spend spikes and keys you did not create. Then take the process down.

This is the same lesson as the Langflow CVE we wrote up on September 3: AI app security keeps collapsing into classic web bugs plus a pile of secrets in the environment. Prompt-injection workshops do not catch Host headers.

Granian, uvicorn, and the path in front of Starlette

Django 6.1’s docs now include how to run Django with Granian. granian --interface asgi project.asgi:application, from the manage.py directory, listens on 127.0.0.1:8000. Granian is in the same list Hard2bit gives for ASGI servers that will happily accept a bad Host if nothing sits in front: uvicorn, hypercorn, daphne, granian.

The deployment pattern that keeps showing up in the BadHost writeups is not “a carefully proxied FastAPI on Kubernetes.” It is a LiteLLM or vLLM container on a lab GPU, or an MCP server on a high port, reachable from the VPN. Python security baselines still say “do not expose the app server.” KEV week is when that sentence stops being a style guide.

If you run Django, you are not automatically safe. You are safe if the thing in front of Granian or uvicorn rejects a Host that is not a hostname, and if your auth does not parse a reconstructed URL. If you run FastAPI with a homegrown “skip auth when path startswith /health” middleware that uses request.url.path, you are the demo.

Patch order I would actually do on Monday: inventory images for starlette and litellm with a scanner that reads layers, not just the OS; rebuild anything below Starlette 1.0.1 or LiteLLM 1.83.7; grep for request.url.path in middleware; put nginx or Caddy in front of every ASGI port that currently faces a subnet; log Host. Then argue about Granian vs uvicorn. The runtime debate can wait until the header debate is over.

A timeline you can put in the ticket

January 2026: X41 finds the parser bug in a vLLM audit. The target was an inference server. The flaw was in the HTTP library under it. That sentence is the whole supply-chain lesson.

April 20: CVE-2026-42271, LiteLLM MCP test command injection, needs credentials.

May 8: LiteLLM 1.83.7. May 26: OSTIF’s long BadHost writeup, noting 1.0.1 was already out and the internet was still serving the old code. May 28: Belgium CCB warning.

June 1: Horizon3 publishes the unauth RCE chain. June 9: LiteLLM CVE hits KEV; Wiz honeypots show miners.

August 26–27: Microsoft on three AI infra intrusions; Wiz on 90 days of telemetry. Qilin gets named by external researchers in Wiz’s telling; Wiz does not independently confirm the attribution. The miners and the memory-read of the master key are first-hand.

September 2: Starlette’s CVE joins KEV. September 16: US federal remediation date.

If your ticket template wants “why now,” paste that list. The code fix is four months old. The catalog is four days old. Mass scanners follow catalogs. Attach the Hard2bit URL and the Starlette advisory. Reviewers close tickets faster when the dates are in the description instead of in Slack.

NIS2 and DORA shops in Europe are not under BOD 26-04. They still use KEV as a trigger. If you need a local deadline, pick “before the scanners add the check,” which is usually days, not the 16th.

CISA’s September 16 date is a federal calendar. Attackers do not use it. The catalog entry is the part that should change your week.

Spread The Article

Share this guide

Send this article to your network or keep a copy of the direct link.

X Facebook LinkedIn Reddit Telegram

Discussion

Leave a comment

No comments yet

Be the first to start the conversation.