FR
live
DevOps Critical

BadHost bypasses FastAPI authentication with a single character in the Host header

CVE-2026-48710, nicknamed BadHost, lets an unauthenticated attacker turn a blocked request into an allowed one by adding a character to the Host header, in every Starlette release before 1.0.1. Upgrade Starlette and audit every middleware that reads request.url.path instead of the raw ASGI path.

A wall of identical grey mail pigeonholes, one envelope half-inserted into the wrong slot, its corner glowing with a single amber accent.

September 2, 2026. CISA adds CVE-2026-48710 to the Known Exploited Vulnerabilities catalog, with an EPSS in the 98th percentile. Nicknamed BadHost, the flaw sits in Starlette, the ASGI framework beneath FastAPI and a large slice of the Python ecosystem — including vLLM, LiteLLM, and many MCP servers. The principle fits in one sentence: a forged Host header can bypass authentication middleware that reads request.url.path. Why it matters: more than 400,000 projects depend on Starlette, and the fix lands in a major version.

A Host header Starlette never validated

The root cause is a failure to validate the Host header. In Starlette releases from 0.8.3 through 1.0.0 (the fix arrives in 1.0.1), the framework rebuilds request.url from the client-supplied Host without checking its consistency with the actual request.

The result is a parsing disagreement — the same mechanism as classic request smuggling, applied to a single header. The underlying ASGI server (uvicorn, hypercorn) processes the request with one path, while the Starlette application computes a different one from the falsified Host. An attacker can therefore inject a path into the Host value and make the application believe the request targets an allowed endpoint.

The name BadHost comes from exactly that: the flaw does not break the server, it makes the application lie about its own URL.

The real bug is in your middleware, not in Starlette

Starlette supplies the mechanism. The exploitation happens in application code — specifically in the middleware and decorators that make security decisions from request.url.path instead of the raw ASGI path (scope["path"]).

The distinction is the whole game:

  • scope["path"] is the path the ASGI server actually received — the one the attacker cannot falsify once the request is accepted.
  • request.url.path is rebuilt by Starlette from the Host — and is therefore spoofable.

An authentication middleware that trusts request.url.path believes it is guarding a safe path while the real request targets another. Add one character to the Host, and a request aimed at a protected route is rewritten as an allowed one. The door is not forced: the guard is simply looking at the wrong address.

FastAPI is hit head-on, because its dependency injection and authorisation middleware are built on that layer. vLLM, LiteLLM, and MCP servers inherit the same defect, which extends the blast radius to AI-agent infrastructure exposed in production.

A severity the maintainer understated

The severity debate is itself instructive. The Starlette maintainer rated the flaw Moderate — the argument being that Host validation partly belongs to the server or the front proxy configuration. The researchers who documented BadHost counter that the real severity depends on the code consuming request.url, and that a flaw which silently bypasses authentication across hundreds of thousands of deployments cannot be “moderate”.

CISA settled the argument by adding the flaw to the KEV on September 2, 2026, with a note of active exploitation. The 98th-percentile EPSS — a 30-day exploitation probability above nearly all other CVEs — confirms this is not a theoretical exercise. The gap between a Moderate rating and a KEV entry is itself the lesson: severity is a function of how your code consumes a framework’s abstractions, not of the abstraction alone.

The lesson for teams is twofold. First, a vendor severity rating is a starting point, not a verdict: your code determines the real impact. Second, any dependency that rebuilds a URL from an unvalidated header is a time bomb inside a security middleware.

How to fix and verify

The fix is clear, but the audit is the part that takes time. Here is the order of operations:

  1. Upgrade Starlette to 1.0.1 (or later) across all dependencies, including transitive ones — FastAPI, vLLM, LiteLLM, and the MCP servers that bundle it.
  2. Audit your middleware: find every read of request.url, request.url.path, or request.base_url used in a security decision, and replace it with request.scope["path"].
  3. Validate Host at the front — reverse proxy or TrustedHostMiddleware — to reject unauthorised Host values before they reach the application.
  4. Check the dependency build with a scanner that flags Starlette < 1.0.1, including inside container images and lambdas.

The underlying rule is worth memorising: security decisions are made on the server’s raw path, never on a value rebuilt from a client-controlled header.

One practical warning: this fix propagates unevenly. Upgrading FastAPI does not automatically upgrade the Starlette it pins, and many MCP servers and AI gateways bundle their own copy. A single pip list is not enough — you need to enumerate every environment, container image, and lambda layer where the dependency tree resolves, because the vulnerable release can hide several levels deep.

The vulnerable pattern and its fix

To make it concrete, here is the pattern to avoid and the safe one. The first reads the path from request.url, rebuilt from the Host; the second reads the raw path supplied by the ASGI server.

python
# ❌ Vulnerable — request.url.path is rebuilt from the Host (spoofable)
async def auth_middleware(request, call_next):
    if request.url.path.startswith("/admin"):
        # security decision based on a client-controlled value
        return Response(status_code=401)
    return await call_next(request)
python
# ✅ Safe — scope["path"] is the path the server actually received
async def auth_middleware(request, call_next):
    if request.scope["path"].startswith("/admin"):
        return Response(status_code=401)
    return await call_next(request)

The rule reads in one sentence: any security decision touching the path, host, or scheme must read request.scope, never request.url.

This subtlety also explains why the flaw went unnoticed. The vulnerable code works under normal conditions: as long as the Host sent is honest, request.url.path and scope["path"] coincide. The defect only shows when an attacker spoofs the Host — a case unit tests almost never cover. It is the kind of bug only a security-oriented review of the middleware catches, which is exactly what the post-fix audit should target.

Verdict

If you deploy FastAPI, vLLM, LiteLLM, or an MCP server, upgrade Starlette to 1.0.1 immediately and audit every authentication middleware that reads request.url.path — that is where the exploitation happens, not in the framework alone.

If you run a reverse proxy in front of these services, add strict Host validation now: it neutralises the attack even if an internal dependency lags behind.

If you manage a system that consumes Python dependencies transitively, add the “Starlette ≥ 1.0.1” rule to your build policy, because the flaw can arrive through a library you never see directly.

References

cve

Linked vulnerabilities

The cyber brief, every Tuesday

The flaws that matter and the patches to apply, in a ten-minute read.

No spam. One-click unsubscribe.
read next

On the same topic

Docker Engine 29.8.0 adds --umask and blocks a 32-bit sandbox-escape path

Docker Engine 29.8.0, released September 3, 2026, introduces a --umask flag to set a container’s file-creation mask and adds AppArmor/SELinux rules that block the 32-bit socketcall(2) path to AF_VSOCK. Upgrade if you share volumes between host and containers or harden your containers.

Kubernetes 1.37 Promotes Rootless Mode for Node Components to Beta

Kubernetes v1.37 enables KubeletInUserNamespace by default: the kubelet, runtimes, and CNI plugins can now run as a non-root user inside a user namespace. Turn it on to confine container-breakout flaws away from host root.

← Back to the feed

Type at least two characters.

navigate open esc dismiss