Pokud provozuješ cokoliv postaveného na FastAPI, vLLM, LiteLLM nebo MCP serveru, tohle si přečti hned. Před pár dny se zveřejnila zranitelnost ve Starlette, frameworku, na kterém stojí prakticky celý Python AI ekosystém. Útočník bez jakéhokoliv hesla obejde path-based autorizaci jediným speciálním znakem v Host headeru. Starlette má 325 milionů downloadů týdně, takže blast radius je obří.

Fix existuje, je to upgrade na Starlette 1.0.1. Háček je v tom, že "upgradovat FastAPI" nestačí a hromada lidí si tím pádem bude myslet, že je opravená, i když nebude. Pojďme na to.

TL;DR: FastAPI drží verzi Starlette zastropovanou (starší verze ji limitují pod 1.0, např. starlette<1.0.0 nebo starlette<0.48.0). Oprava je až ve Starlette 1.0.1, takže pro spoustu projektů je fix mimo dosah, dokud nezvednou i FastAPI - a samotné pip install -U fastapi Starlette na 1.0.1 stejně nemusí dotáhnout.

Co se vlastně stalo?

CVE-2026-48710 (přezdívka BadHost) je chyba ve Starlette, kde se HTTP Host header použije ke skládání request.url bez validace. Vadný Host header pak posune hranice cesty, takže request.url.path neodpovídá tomu, kam request reálně zaroutoval.

Mechanika je hezky zákeřná v tom, jak je jednoduchá. Klient pošle:

GET /admin HTTP/1.1
Host: example.com/health?x=

Starlette si request.url poskládá jako http://{host}{path}, tedy http://example.com/health?x=/admin, a tohle znova naparsuje. Protože je v Host headeru ?, parser posune hranice a request.url.path vyjde /health. Jenže router pracuje s reálnou cestou ze scope, takže request normálně doběhne na /admin a endpoint se vykoná.

A teď ten průšvih: pokud máš middleware, který hlídá přístup podle request.url.path (typicky auth, allowlist, rate limiting), uvidí /health, řekne "ok, veřejná cesta, pustím to" - a pod tím se ti v klidu spustí chráněný /admin. Autorizace obejitá, žádné credentials potřeba.

Stejný trik jde se znaky /, ? i #.

Koho to zasáhne

Krátká odpověď: skoro každého v Python AI světě. Postižené jsou všechny verze Starlette <= 1.0.0 (fix až v 1.0.1). A protože na Starlette stojí FastAPI a na FastAPI stojí zbytek, dědí to dál:

  • FastAPI appky s vlastním path-based auth middlewarem
  • vLLM (mimochodem chybu našli právě při auditu vLLM) - exponované jsou /v1/models a model/runtime control endpointy
  • LiteLLM - admin a key-management surface (/key/info, /model/info)
  • MCP servery - tady je to nejhorší, protože drží credentials k mailům, databázím a dalším systémům, ke kterým má agent přístup. MCP spec navíc počítá s neautentizovanými discovery endpointy, což útočníkovi dává spolehlivou cestu dovnitř.
  • OpenAI-compatible proxy, agent harnessy, eval dashboardy, model-management UI

Nejvíc exponované jsou inference servery běžící přímo na uvicornu bez reverse proxy, což je u AI nasazení dost běžné.

Oficiální CVSS je 6.5 (Moderate), ale výzkumníci z X41 D-Sec, kteří to našli (audit sponzorovaný OSTIF), říkají, že to realitu downstreamu dost podceňuje. Patch vyšel 21. 5. 2026, disclosure den nato. Lead time na záplatování byl prakticky nulový.

Není to vždycky pohroma: rozhoduje jediná otázka

Než vyhlásíš poplach (nebo ho naopak smeteš ze stolu): zkratka "FastAPI je relativně safe, problém je hlavně MCP" je nebezpečně optimistická. Přesnější rámec není "FastAPI vs MCP", ale jedna jediná otázka - rozhoduješ někde o přístupu podle request.url.path? Na tom stojí všechno.

Proč ti FastAPI může připadat safe: pokud děláš autorizaci idiomaticky přes Depends() / Security() přímo na routách, tenhle trik tě prakticky neobejde - dependency se naváže na reálně zaroutovaný endpoint a spustí se bez ohledu na otrávenou request.url.path. To je ta robustní část a hodně lidí to takhle má.

Jenže FastAPI je v Pythonu všude právě proto, že je flexibilní, a strašně běžný pattern je nalepit si na něj vlastní middleware pro cross-cutting věci (auth, logging, rate limiting). A přesně tam to bije. Typický zranitelný kód, který jsi nejspíš někde viděl:

@app.middleware("http")
async def auth_gate(request: Request, call_next):
    public = ("/health", "/docs", "/openapi.json")
    if not request.url.path.startswith(public):   # <- otrávitelné
        if not is_authorized(request):
            return JSONResponse({"detail": "unauthorized"}, status_code=401)
    return await call_next(request)

Útočník pošle GET /admin s hlavičkou Host: x/health?a=, request.url.path vyjde /health, middleware si řekne "veřejná cesta, pustím" - a pod tím se spustí chráněný /admin. Bez tokenu.

Co všechno v čisté FastAPI appce spadá do téhle kategorie:

  • Gating autorizace podle prefixu cest
  • Allowlist/denylist veřejných endpointů
  • Rate limiting scopovaný podle path
  • CSRF výjimky
  • Tenant scoping podle URL

Plus tišší dopad: otrávené logy. V auditu uvidíš /health, i když reálně běžel /admin, takže ti kulhá i forenzika. Bezpečná verze je číst raw routovanou cestu ze scope (request.scope["path"]) místo request.url - detailní before/after máš v sekci Co dělat, když nemůžeš upgradovat hned.

Konkrétní věc, kterou můžeš udělat hned: projeď si codebase, jestli někde nevětvíš přístup podle request.url.path:

grep -rn "request.url.path" --include="*.py" .
grep -rn "BaseHTTPMiddleware\|@app.middleware" --include="*.py" .

Když najdeš request.url.path uvnitř něčeho, co rozhoduje o přístupu, jste reálně zranitelní a bez proxy tam není záchranná síť. Když je všechno přes Depends() / Security() a žádná dependency sama nevětví podle request.url.path, jste na tom o dost líp.

A k MCP: není to "buď FastAPI, nebo MCP" - MCP server je většinou taky FastAPI/Starlette appka pod kapotou. Nebezpečnější je ze dvou důvodů: za těmi dveřmi jsou credentials k mailům, DB a interním systémům (to nejcennější), a MCP spec počítá s neautentizovanými discovery endpointy, takže má útočník spolehlivý vstupní bod. Ale obyčejná FastAPI appka s path-based middlewarem je zranitelná úplně stejně, jen je za dveřmi typicky míň šťavnatá kořist.

Správný rámec: FastAPI přes dependencies je relativně odolné, FastAPI s vlastním path-based middlewarem je v ohrožení, a MCP je nejvyšší sázka. Tak jako tak Starlette zvedni, ať na tom vůbec nestavíš.

Jak zjistit, jestli jsi zranitelný

1. Zjisti, co reálně běží v prostředí (ne co máš v requirements, ale co je nainstalované):

pip show starlette

Když je verze 1.0.0 nebo nižší, jsi zranitelný.

2. Otestuj běžící endpoint scannerem od autorů: https://badhost.org (umí i autodiscovery MCP a inference endpointů). Pro CI/repo jsou tam i Semgrep pravidla a CodeQL dotazy: https://github.com/x41sec/poc/tree/master/starlette-host-header.

Jak to opravit (a kde se to typicky zvorá)

Fix žije ve Starlette, ne ve FastAPI. FastAPI si do žádné chyby nesáhlo, jen dědí chování request.url ze Starlette. To má jeden důležitý důsledek:

pip install -U fastapi ti SÁM o sobě fix nezaručí. FastAPI má schválně širokou škálu povolených verzí Starlette, takže ti klidně nechá v prostředí starou zranitelnou Starlette a tváří se spokojeně.

Správný postup:

1. Zkus zvednout přímo Starlette:

pip install -U "starlette>=1.0.1"

2. Když to hodí dependency conflict (ResolutionImpossible), znamená to, že tvoje verze FastAPI stropuje Starlette pod 1.0 (starší FastAPI mají třeba starlette<0.48.0 nebo starlette<1.0.0). Pak musíš zvednout i FastAPI na verzi, která Starlette 1.0.0+ podporuje, a nechat resolver vyřešit obojí naráz:

pip install -U fastapi "starlette>=1.0.1"

3. Ověř výsledek - tohle je ten krok, který lidi vynechávají:

pip show starlette   # musí být >= 1.0.1

4. Rebuildni a redeployni. V Dockeru nestačí změnit requirements, musíš přestavět image. Pokud máš lock soubor, regeneruj ho:

# poetry
poetry update starlette
# uv
uv lock --upgrade-package starlette
# pip-tools
pip-compile --upgrade-package starlette

Pro vLLM platí to samé - není to oprava v kódu vLLM, je to dotáhnout do prostředí Starlette >= 1.0.1. A protože vLLM běží často přímo na uvicornu, dej před něj reverse proxy (viz níž).

Tohle zadej svému AI agentovi na serveru a on to zalepí

Pokud máš na serveru nebo v projektu agenta s přístupem k shellu (Claude Code, Cursor, vlastní agent), nemusíš to klikat ručně. Zkopíruj mu tenhle prompt. Schválně v něm má agent zabudované pojistky - fix udělá a ověří, ale produkční restart a přepisy auth kódu si nechá potvrdit od tebe, ať ti nesahá na produkci naslepo:

Na tomhle projektu řeš bezpečnostní zranitelnost CVE-2026-48710 (BadHost) ve Starlette.
Postižené jsou všechny verze Starlette <= 1.0.0, opravená je 1.0.1+. Fix žije ve Starlette,
ne ve FastAPI - FastAPI jen dědí chování request.url. Pozor: starší FastAPI stropují
Starlette pod 1.0 (např. starlette<1.0.0 nebo <0.48.0), takže samotný upgrade Starlette
může spadnout na dependency conflict a je pak potřeba zvednout i FastAPI.

Udělej tohle a po každém kroku mi řekni výsledek:

1. Najdi všechna Python prostředí v projektu (venv, Docker, pyproject.toml / requirements.txt
   / lock soubory) a zjisti aktuálně nainstalovanou verzi: pip show starlette.
   Pokud je <= 1.0.0, pokračuj.
2. Zkus: pip install -U "starlette>=1.0.1"
   Když to spadne na ResolutionImpossible, zvedni zároveň FastAPI:
   pip install -U fastapi "starlette>=1.0.1"
3. Ověř pip show starlette - musí být >= 1.0.1.
4. Uprav dependency a lock soubory tak, ať fix zůstane i po čistém buildu (requirements.txt,
   pyproject.toml, poetry.lock, uv.lock). Lock regeneruj odpovídajícím nástrojem:
   poetry update starlette / uv lock --upgrade-package starlette / pip-compile --upgrade-package starlette.
5. Lokálně (NE na produkci) ověř, že aplikace po upgradu naběhne a odpovídá - upgrade Starlette
   i FastAPI může být major verze s breaking changes. Ve vývojovém/test prostředí spusť app
   (např. uvicorn na dočasném portu) a curlni jeden endpoint (/health, /docs nebo veřejnou cestu),
   ať vidíš, že vrací očekávaný status. Pokud něco spadne, nahlas mi to a nepokračuj v deployi.
6. Projdi kód a najdi auth nebo security middleware, který rozhoduje podle request.url.path.
   Pokud takový najdeš, upozorni mě a navrhni přepis na request.scope["path"] (resp. scope["path"]
   v čistém ASGI middlewaru) nebo na FastAPI Depends() / Security(). Tuhle změnu neprováděj sám
   bez mého potvrzení.
7. NErestartuj produkci sám. Připrav mi přesné kroky k rebuildu a redeployi (Docker rebuild,
   restart služby) a počkej na potvrzení.
8. Na závěr mi dej krátký souhrn: jaká byla verze Starlette před a po, co jsi změnil v souborech,
   a co ještě musím udělat ručně.

Po doběhnutí stejně ověř výsledek scannerem na https://badhost.org, ať máš jistotu, že běžící služba je opravdu zalepená a ne jen requirements soubor.

Co dělat, když nemůžeš upgradovat hned

Upgrade je jediné skutečné řešení, ale když potřebuješ zmírnit riziko do doby, než to nasadíš:

Reverse proxy před aplikací. RFC-konformní proxy (nginx, Caddy, Cloudflare, AWS ALB) vadné Host headery odmítne nebo znormalizuje, takže se k Starlette vůbec nedostanou. Pozor ale na podmínky: chrání tě to jen pokud proxy ten vadný Host opravdu odmítne nebo znormalizuje, a zároveň tvoje appka nedůvěřuje útočníkem ovládanému headeru (např. X-Forwarded-Host) někde jinde. Není to plná náhrada za patch.

U nginx pomáhá konkrétní server_name a default server, který neznámý/vadný Host zahodí:

server {
    listen 443 ssl default_server;
    server_name _;
    return 444;
}
server {
    listen 443 ssl;
    server_name tvojedomena.cz www.tvojedomena.cz;
    location / {
        proxy_pass http://127.0.0.1:8000;
    }
}

Oprava v kódu. V auth middlewaru čti raw ASGI cestu ze scope místo request.url.path:

# zranitelné
path = request.url.path
# bezpečné - bere reálnou routovanou cestu
path = request.scope["path"]   # v middlewaru přímo: scope["path"]

Ještě lepší je nezakládat autorizaci na vlastním path-based middlewaru a používat FastAPI Depends() / Security(). Ty pracují nad routovanou cestou, ne nad poskládaným request.url, takže tímhle trikem je neobejdeš.

Shrnutí akčních kroků

  1. pip show starlette -> je to <= 1.0.0? Jsi zranitelný.
  2. pip install -U fastapi "starlette>=1.0.1" (samotné -U fastapi nestačí)
  3. pip show starlette znovu -> ověř, že je >= 1.0.1
  4. Rebuild image, regeneruj lock soubory, redeploy
  5. Otestuj na https://badhost.org
  6. Reverse proxy před uvicorn ber jako standard, ne jako luxus

Pokud máš MCP server nebo inference endpoint vystavený do internetu se starou Starlette, řeš to dneska, ne příští sprint.

Zdroje

Máš AI stack vystavený do internetu?

Pokud provozuješ FastAPI, vLLM nebo MCP servery a nejsi si jistý, jestli jsou zabezpečené, ozvi se. Projdu nasazení a řeknu rovnou, kde je díra.