Safeguard
Application Security•

Starlette BadHost CVE-2026-48710: A Python Library Lands in CISA KEV

A malformed Host header made Starlette's request.url.path differ from the routed path, bypassing path-based auth. Fixed in 1.0.1. CISA added it to KEV on 2 September 2026.

Safeguard Security Team
5 min read

On 2 September 2026 CISA added CVE-2026-48710, the "BadHost" bug in the Starlette ASGI framework, to its Known Exploited Vulnerabilities catalog. The due date was 16 September. CISA's entry notes it can be chained with CVE-2026-42271, a LiteLLM command injection that was already in KEV.

GitHub's advisory rates the bug 6.5 under CVSS 3.1. That number undersells it. Starlette sits under FastAPI and much of the Python LLM stack, and in the LiteLLM chain it removes the one thing standing between an attacker and command execution: the API key.

What's affected

  • Package: starlette on PyPI.
  • Vulnerable: 1.0.0 and earlier.
  • Fixed: 1.0.1, uploaded to PyPI on 21 May 2026. It requires Python 3.10 or newer.
  • Downstream: anything that bundles Starlette, including FastAPI apps. OSTIF's write up lists FastAPI, LiteLLM, vLLM, text generation inference projects, OpenAI compatible shim proxies, MCP servers, agent harnesses and model management UIs.

The bug, per GHSA-86qp-5c8j-p5mr: Starlette rebuilt request.url by joining the Host header and the path, then re-parsing the result, without validating the host. Routing used the raw path. A host containing /, ? or # moves the path boundary on re-parse. The advisory's example request:

GET /foo HTTP/1.1
Host: example.com/abc?bar=

That reconstructs to http://example.com/abc?bar=/foo. The router still runs the /foo endpoint, but any middleware reading request.url.path sees /abc. If that middleware gates /admin or /internal by path prefix, it waves the request through.

The bug was found by X41 D-Sec during an OSTIF managed audit of vLLM. Horizon3.ai later validated a chain with LiteLLM. CVE-2026-42271 lets any holder of a LiteLLM proxy key make the MCP test endpoints spawn a command. Horizon3 found that on deployments with Starlette 1.0.0 or earlier, BadHost removes the key requirement, which makes it unauthenticated RCE. Its affected range for that chain is LiteLLM 1.74.2 through 1.83.6.

How to check if you're exposed

Find every Starlette in your Python estate, including the ones pulled in by FastAPI:

pip show starlette | grep -i ^version
pipdeptree -r -p starlette          # who depends on it
grep -rn "starlette" requirements*.txt poetry.lock uv.lock 2>/dev/null

OSTIF warns that pip list on a host may miss copies bundled in containers and virtualenvs, so check images too:

docker run --rm --entrypoint python your-image:tag -c \
  "import importlib.metadata as m; print(m.version('starlette'))"

Then find code that makes security decisions from the reconstructed URL:

grep -rnE "request\.url(\.path)?" --include=*.py . | grep -vi test

Middleware that checks request.url.path against a prefix list is the classic pattern. The durable fix, per OSTIF, is to read request.scope["path"] instead.

Finally, test a live endpoint you own. If a path you normally get a 403 on returns 200 with a malformed host, you are exposed:

curl -s -o /dev/null -w '%{http_code}\n' https://app.example.com/admin
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: app.example.com/x?y=' https://app.example.com/admin

What to do now

  1. Upgrade Starlette to 1.0.1 or later in every service, then rebuild and redeploy the images. Pinning in requirements.txt does nothing for an image built last month.
  2. If you run LiteLLM, upgrade it to 1.83.7 or later as well. Horizon3 says both fixes are needed to close the chain.
  3. Services stuck on Python 3.9 can't install 1.0.1. For those, move security checks from request.url.path to request.scope["path"] now, and plan the Python upgrade.
  4. Put a reverse proxy in front of every ASGI app that rejects malformed Host headers. OSTIF says nginx, Apache httpd and Cloudflare reject the proof of concept by default, but verify your own config.
  5. On LiteLLM hosts that ran an affected chain, review logs for unusual Host values and for subprocess activity from the proxy, as Horizon3 recommends, and rotate credentials the proxy stores.

Why it matters

KEV is mostly appliances and enterprise products. A library entry is a different kind of problem, because nobody "installs Starlette" on purpose. It arrives as a transitive dependency of a framework that arrived with an AI tool. You can only patch it if your inventory goes below the top level packages.

The bug also shows how a medium CVSS score can mislead. On its own it is a path confusion. Next to an endpoint that runs commands for authenticated users, it is the missing piece of an unauthenticated RCE.

Try it yourself

Generate a requirements.txt or CycloneDX SBOM from one of your Python services and paste it into the free scan at gold.safeguard.sh/scan. It runs in your browser, the file never leaves your machine, and it handles up to 100 dependencies per scan. See whether Starlette shows up and at which version.

This demo shows how reachability analysis tells you whether your code actually hits a vulnerable path:

Click through it at your own pace, or open it full screen.

Quick check

Which Starlette version fixes BadHost?

1.0.1. It validates the Host header when building request.url and falls back to the server address for malformed values.

My app doesn't use request.url for auth. Am I safe?

Mostly, for your own code. Libraries and middleware you depend on may still read request.url.path, so upgrade anyway.

Why does BadHost matter for LiteLLM?

CVE-2026-42271 needs a valid LiteLLM proxy key. Horizon3 found that BadHost on Starlette 1.0.0 or earlier removes that requirement, making the chain unauthenticated RCE.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.