8 min čteníJohnny UnarJohnny Unar

Vaše LLM gateway je trezor na credentials a breach LiteLLM to dokázal

Útok na dodavatelský řetězec LiteLLM z března 2026 ukázal, proč je váš AI proxy nejcennější cíl v celém stacku a proč ho většina týmů pořád bere jako obyčejnou utility knihovnu.

co se vlastně stalo

Writeup toho incidentu jste už nejspíš viděli, ale mechaniku stojí za to projít pořádně, protože zajímavá není payload, ale cesta. TeamPCP nenašli díru v tom, jak LiteLLM zpracovává requesty, ani žádnou chytrou prompt injection do proxy vrstvy. Šli o úroveň výš a kompromitovali security scanner, který běžel uvnitř vlastní CI pipeline LiteLLM. Přečtěte si to ještě jednou. Nástroj, který měl chytat škodlivý kód, se stal zbraní, a jakmile měl execution v důvěryhodném build prostředí, sebral publishing tokeny pro PyPI ležící v prostředí runneru a vypustil podvržený release balíčku, který má kolem 95 milionů stažení měsíčně.

Samotný harvester byl pěkně zákeřný kus inženýrství. Nešáhli na hlavní logiku balíčku, kde by si reviewer mohl všimnout diffu při bumpu verze. Do site-packages hodili soubor .pth, který Python automaticky spustí při startu interpreteru, protože přesně tohle .pth soubory smějí dělat, když začínají import statementem. Takže v okamžiku, kdy jakýkoli proces cokoli naimportoval v prostředí, kde byl otrávený wheel nainstalovaný, harvester se rozjel, prošel prostředí a hledal cokoli, co vypadalo jako API klíč, a poslal to domů útočníkům.

A kde LiteLLM sídlí? Uvnitř jediné služby ve vaší infrastruktuře, která má z podstaty každý OpenAI klíč, každý Anthropic klíč, vaše Bedrock IAM credentials, JSON servisního účtu pro Vertex a obvykle pořádnou hromadu CI tokenů, protože někdo zadrátoval config gateway do deploy pipeline. Je to nejbohatší dump environment proměnných v celé firmě. Útočníci to pochopili líp než většina týmů, které tu věc provozují.

gateway je trust boundary, přestaňte předstírat opak

Tady je strukturální problém, který nikdo nechtěl pojmenovat nahlas. LLM gateway agreguje credentials úplně stejně jako secrets manager, jenže secrets manager je postavený, auditovaný a provozovaný jako kus security infrastruktury, zatímco gateway nainstaloval backend inženýr jedním pip příkazem, protože potřeboval v úterý odpoledne A/B otestovat dva modely.

Tahle asymetrie je celý příběh. Vault, AWS Secrets Manager, Doppler, cokoli používáte, ty věci mají threat modely. Lidi se hádají o jejich IAM policies. Nikdo neshipuje Vault cluster tak, že spustí unpinned instalaci z PyPI a zapomene na to. Jenže LiteLLM, nebo jakýkoli proxy, který hraje stejnou roli, nakonec drží úplně stejnou třídu secretů, a přitom se s ním zachází jako s requests nebo httpx, jako s pohodlným wrapperem, na který po prvním importu nikdy nepomyslíte.

Oprava se odehrává v hlavě, je to reklasifikace. Proxy, který rozstřeluje váš traffic mezi šest model providerů, není knihovna. Je to jediný bod agregace pro každý provider klíč a cloud credential ve vašem stacku a zaslouží si stejnou paranoiu, jakou byste aplikovali na box, který terminuje TLS, nebo službu, která razí JWT. Jakmile to takhle zarámujete, mitigace přestanou působit jako přehnané a začnou působit jako baseline, který jste z nedbalosti přeskočili. Tenhle přesně rozhovor jsme v týdnu, kdy zpráva praskla, vedli s pár klienty, a ti, co už měli gateway izolovaný, sotva mrkli, zatímco ti, co ho provozovali jako sidecar ve stejném podu jako aplikaci a se sdíleným servisním účtem, dva dny rotovali úplně všechno.

pinujte hashe, nebo přiznejte, že hrajete kostky

Pokud pořád spouštíte pip install litellm bez omezení verze, nebo dokonce litellm==1.x jen s verzí a bez hashe, věříte, že vám PyPI dnes naservíruje stejné byty, které někdo včera zreviewoval, a incident LiteLLM je přesně ten případ, kdy tenhle předpoklad padá.

Hash pinning je nudná odpověď, která by tohle reálně zablokovala. Když v requirements souboru pinujete --require-hashes, nebo použijete uv se zamčeným uv.lock, který zaznamenává sha256 každého wheelu, prohozený release spadne při instalaci na hash mismatch místo toho, aby potichu přistál v site-packages. Otrávený wheel měl jiný hash než verze, kterou znal váš lockfile, tečka, takže build spadne nahlas místo aby tiše sklízel vaše klíče.

Konkrétně: vygenerujte plně hashovaný lock a odmítněte nainstalovat cokoli mimo něj. S uv je to uv pip compile requirements.in --generate-hashes -o requirements.txt a pak uv pip sync --require-hashes requirements.txt v CI. S čistým pipem je to pip install --require-hashes -r requirements.txt, a ano, to znamená, že hashe potřebují i tranzitivní závislosti, což je poprvé otrava a pak už zadarmo. Ta otrava je smysl celé věci, protože vynutí lidské rozhodnutí pokaždé, když se závislost skutečně změní.

Co lidi dělají špatně, je, že berou lockfile jako formalitu, která se regeneruje na autopilota ve stejném PR, co bumpuje feature. Když vám CI regeneruje hashe automaticky při každé změně závislosti a nikdo se nedívá, co se pohnulo, znovu jste postavili tu zranitelnost, jen s pár kroky navíc. Někdo se musí podívat na diff a zeptat se, proč litellm dnes ráno skočil o patch verzi. Ten pětivteřinový pohled je celá kontrola.

runnery bez secretů a konec ambientního tokenu

PyPI token se ukradl proto, že seděl v prostředí CI runneru, dostupný jakémukoli kódu, který během buildu běžel, včetně scanneru, o kterém všichni předpokládali, že je neškodný. Ambientní credentials v build prostředích jsou nejstarší past dodavatelského řetězce a my ji pořád dokola nabíjíme.

Přestaňte dávat dlouhověké publishing tokeny do env proměnných runneru. GitHub Actions už nějakou dobu má OIDC trusted publishing do PyPI a funguje to: nakonfigurujete trusted publisher na straně PyPI a runner si v okamžiku publikace vymění krátkověký OIDC token, takže není žádný statický token ke krádeži, protože v prostředí neexistuje déle než pár vteřin a je scopovaný přesně na jeden workflow. Kdyby harvester TeamPCP běžel v publish flow založeném na OIDC, nebylo by v prostředí co sebrat.

Kromě publikace platí, že build krok, který spouští vaši test suite, linters a scannery, by neměl mít vůbec přístup k produkčním provider klíčům. Vaše unit testy nemají žádný důvod potřebovat živý Anthropic klíč. Rozdělte pipeline tak, aby stage se třetostranným tooling měla prostředí vydrhnuté od čehokoli citlivého, a stage, která reálně deployuje, běžela s těsně scopovanými, krátkověkými, čerstvě vyraženými credentials, co expirují za pár minut. Je to víc YAMLu. Je to taky rozdíl mezi tím, jestli kompromitace scanneru bude pokrčení rameny nebo událost, co položí firmu.

Většinu naší AI infrastruktury ve steezru dnes stavíme defaultně na tomhle předpokladu: že jakákoli závislost v buildu může a časem bude spouštět libovolný kód. Otázka tedy nikdy nezní, jak scanneru důvěřovat, ale co scanner dosáhne, když se obrátí proti vám.

egress control by payload zabila na místě

Všechno výše je prevence. Tohle je vrstva, která vás zachrání, když prevence selže, a ona selže, protože nemůžete auditovat každou tranzitivní závislost balíčku, který jich vtáhne desítky.

Celá hodnota harvesteru závisí na jedné věci: na schopnosti otevřít odchozí spojení na host řízený útočníkem a poslat tam ukradené klíče. Pokud váš gateway běží v prostředí, kde je egress defaultně zakázaný a dosáhnout lze jen na explicitně povolené cíle, payload posbírá úplně všechno, pokusí se to exfiltrovat a narazí do zdi. Klíče nikdy neopustí ten box.

Tohle je strašně málo využívané a docela mě to štve. Většina lidí obsesivně konfiguruje ingress pravidla a egress nechá dokořán, takže kompromitovaný proces dosáhne na jakoukoli IP na internetu. Otočte to. Váš LiteLLM gateway potřebuje dosáhnout na api.openai.com, api.anthropic.com, váš regionální Bedrock endpoint, možná Postgres a Redis, a to je celý seznam. Všechno ostatní má být odmítnuté spojení. Na Kubernetes je to NetworkPolicy s explicitním egress allowlistem, nebo service mesh s outbound policy, nebo minimálně NAT gateway s restriktivními pravidly a sada security groups, které povolí jen známé provider CIDR.

DNS tu taky hraje roli, protože spousta naivních egress pravidel jede podle DNS jmen, zatímco skutečné filtrování probíhá na IP vrstvě. Spusťte proto egress filtering, který povolené hostnames resolvuje a pinuje, místo aby věřil tomu, co workload zrovna vyresolvuje. Provider endpointy jsou dost stabilní na to, aby udržování tohohle seznamu byla měsíční, ne denní záležitost, a výměnou za to se z credential harvesteru, který dostane execution ve vašem gateway, stane proces, co nasbíral hromadu secretů, které nikam nemůže poslat.

co reálně udělat tento týden

Nemusíte vařit oceán. Udělejte tohle zhruba v tomhle pořadí a zavřete většinu blast radiusu.

Zauditujte, kde váš gateway běží a jakou má identitu. Pokud sdílí pod, node nebo servisní účet s vaším aplikačním kódem, oddělte ho, protože credentials gateway nesmí být nikdy dosažitelné z request handleru, který zpracovává uživatelský vstup. Dejte mu vlastní těsně scopovanou identitu, která sáhne na provider klíče a na nic jiného.

Regenerujte lockfile s plnými hashi a přepněte CI na --require-hashes nebo uv pip sync. Pak vynuťte, aby bumpy závislostí vyžadovaly lidský review diffu, ne automatickou regeneraci, kterou nikdo nečte. Zabijte všechny statické PyPI nebo provider tokeny žijící v prostředí runnerů a přesuňte publikaci na OIDC.

Napište egress allowlist. Začněte v log-only režimu, ať se pár dní naučíte, s čím váš gateway reálně mluví, a jakmile si seznamem budete jistí, přepněte ho na deny by default. Tenhle jeden krok by sám o sobě payload LiteLLM kompletně zneškodnil a chrání vás i proti tomu dalšímu, jehož jméno ještě neznáte.

Nepříjemné ponaučení z března je, že útočníci měli o téhle infrastruktuře jasnější mentální model než lidi, co ji provozují. Věděli, že gateway je nejtučnější credential cíl v budově. Věděli, že build prostředí je měkké podbřišek. Berte proxy jako trust boundary, kterým odjakživa byl, a další TeamPCP najde mnohem menší a mnohem naštvanější povrch, na kterém může pracovat. Jestli chcete druhý pár očí na to, jak máte ten svůj zapojený, tak přesně tohle z velké části ve steezru děláme, a je to levnější než hromadná rotace ve dvě ráno.

Johnny Unar

Napsal/a

Johnny Unar

Chcete s námi spolupracovat?

Útok na dodavatelský řetězec LiteLLM z března 2026 ukázal, proč je váš AI proxy nejcennější cíl v celém stacku a proč ho většina týmů pořád bere jako obyčejnou utility knihovnu.