LiteLLM 1.107.0 kompromittiert: was wir am eigenen Gateway nachgemessen haben
Am 21. Oktober 2026 hat die Veröffentlichungskette von LiteLLM vergiftete Artefakte ausgeliefert: sieben Wheels litellm==1.107.0 auf PyPI, live von 11:39:51 bis 13:23 UTC, dazu dieselbe Build-Pipeline in den Docker-Tags v1.107.0, latest, main-latest und main-stable, live von 11:46:56 bis 14:20 UTC. Die Zeiten stammen aus dem Incident-Post des Anbieters. Jedes Wheel bringt drei Dateien mit, die beim nächsten Start des Python-Interpreters laufen. Für Betreiber eines eigenen AI-Gateways ist die Versionsfrage die kleinere Baustelle, die größere liegt in der eigenen Datenbank: Dort stehen die Provider-Schlüssel, und bei uns auch Volltexte der Anfragen.
Ein Lieferkettenangriff auf Software trifft nicht den Code im Repository, sondern den Weg, auf dem Nutzer ihn beziehen. Bei LiteLLM 1.107.0 enthielt der Quelltext-Tag auf GitHub laut Anbieter keine Schadsoftware, doch die auf PyPI und in den Docker-Tags ausgelieferten Pakete wurden aus einem Fork mit Schadcode gebaut. Betroffen ist, wer das Paket oder ein bewegliches Tag wie latest im Zeitfenster gezogen hat. Ein fest gepinnter, älterer Tag war nicht betroffen. Dieser Beitrag ist die Gegenprobe am eigenen Gateway: Digest, Version und Spend-Logs, am 22. Oktober gemessen. Was nach einem Treffer zu tauschen ist, steht im Schwesterbeitrag zur Schlüsselrotation.
Korrektur vom 5. November 2026: Die Erstfassung vom 22. Oktober übernahm die Vermutung des Anbieters, die GitHub-Tokens seien aus den Logs einer internen LiteLLM-Instanz gestohlen worden. Der Anbieter hat das am 23. Oktober selbst korrigiert (Abschnitt „Woher die Tokens kamen"). Wir haben diesen Abschnitt, die dritte FAQ-Antwort und die Schlussfolgerungen daraus umgeschrieben, die Versionsangaben auf den Stand vom 5. November gebracht und eine unbelegte Dauerangabe zu den Märzversionen gestrichen.
Was in der Veröffentlichung steckte
Die Analyse von Upwind im öffentlichen Issue #47826 (eröffnet 21.10. 12:06 UTC) führt die Dateien mit Größe und Hash auf: cook.pth mit 12 Byte, cook.py mit 22.252 Byte, sitecustomize.py mit 25.509 Byte (letztere Größe nennt Wiz). pip legt sie ins Site-Packages-Verzeichnis, cook.pth enthält einen Import. Danach läuft cook.py bei jedem Start dieses Interpreters, unabhängig davon, ob LiteLLM gerade benutzt wird.
cook.py nennt sich intern privcollect und sammelt, was auf einem Rechner mit Cloud-Anbindung liegt: Host- und Netzwerkinformationen, gespeicherte Passwörter und Cookies aus Browsern, SSH-Schlüssel, Shell-Verläufe, Zugangsdaten von AWS, Azure, Google Cloud, Kubernetes, Docker und GitHub CLI, .env-Dateien, private Schlüssel, dazu die Umgebungsvariablen aller lesbaren Prozesse über /proc/*/environ. Für Gateway-Betreiber zählt ein Detail: Steht DATABASE_URL in der Umgebung, startet der Prozess Prisma und liest die Tabellen LiteLLM_CredentialsTable und LiteLLM_ProxyModelTable aus, also jeden Provider-Schlüssel, den das Gateway in seiner Datenbank hält. Dazu kommen Rollen-Zugangsdaten aus den Cloud-Metadaten-APIs, die der Schädling mit sts:GetCallerIdentity prüft. Wiz hat solche Prüfungen am 21.10. zwischen 16:20 und 17:01 UTC beobachtet und als aktive Nutzung erbeuteter Rollen eingeordnet.
Der zweite Teil sitzt in sitecustomize.py: ein abgekoppelter Daemon mit der Sperrdatei /tmp/remote_code_loader.lock, der stündlich die Datei domain.txt in einem GitHub-Repo liest und den dort genannten Code per exec() ausführt. Laut Upwind war diese Datei seit 20.10. 09:15 UTC leer, der Loader also schlafend und mit einem einzigen Commit wieder scharf zu schalten. Das Repo ist laut Datadog inzwischen von GitHub entfernt. Der Abfluss läuft als POST auf http://www.litellm.dev/healthcheck, verpackt mit AES-256-CBC und einem RSA-4096-OAEP-gewickelten Schlüssel, ist im Netzverkehr also unlesbar. litellm.dev wurde laut Upwind am 13.10.2026 registriert und ist keine Domain des Projekts (litellm.ai). OSV führt den Fall unter MAL-2026-2144 zusammen mit den Märzausgaben 1.82.7 und 1.82.8.
Woher die Tokens kamen
Der Anbieter hat seine Darstellung geändert. Im Incident-Post vom 21. Oktober hieß es, der Angreifer habe die GitHub-CLI-OAuth-Tokens aus den Logs einer öffentlich erreichbaren internen LiteLLM-Instanz gezogen. Dort steht auch die Beobachtung, Claude Code bette in bestimmten Fällen das Token des Nutzers in einen Antworttext ein. Am 23. Oktober schrieb der Anbieter dazu: Der erste Post habe die Tokens in den Logs einer internen Instanz vermutet, die Request-Logs zeigten aber, dass das erste Token aus einem anderen Dienst kam.
Die Kette laut Anbieter (Beitrag vom 23.10., mit Zeitleiste):
- Am 27. Mai 2026 landete in einem öffentlichen Repo (LiteLLM-Labs/lite-harness) eine Konfigurationsdatei mit dem Admin-Schlüssel einer internen Plattform für Cloud-Agenten, der LiteLLM Agent Platform. Etwa drei Stunden später wurde die Datei gelöscht, der Schlüssel laut Anbieter aber nicht widerrufen (menschliches Versagen), und der Commit blieb bis zum 22. Oktober in der öffentlichen Historie.
- Am 10. Oktober rief der Angreifer mit diesem Schlüssel die API der Plattform auf und bekam das GitHub-CLI-Token eines der beiden Organisationsinhaber zurück.
- Mit diesem Token ließ er in CI-Workflows Secrets an einen fremden Endpunkt senden und erbeutete so die Tokens zweier Maintainer.
- Am 21. Oktober schaltete er mit dem Inhaber-Token den Branch-Schutz des Release-Repos ab, änderte den Workflow so, dass er aus dem Fork Berri-AI/litellm baute, und gab den Release mit dem Token eines Maintainers frei. Die Freigabe-Regel war laut Anbieter früher in derselben Woche vorübergehend gelockert worden, damit zwei beliebige Entwickler Releases auslösen und freigeben konnten.
- Um 11:53 und 11:54 UTC löschte er mit dem dritten Token die Laufprotokolle (Run Records) des Produktions- und des Staging-Laufs.
Das Token aus dem Antworttext von Claude Code taucht in dieser Kette nicht mehr auf. Die Beobachtung steht weiter im ersten Post des Anbieters. Als Erklärung für diesen Vorfall taugt sie nach der Korrektur nicht. Als allgemeine Frage bleibt sie berechtigt, weil Antworttexte in Logs landen können.
Wie lange jedes Artefakt zu erreichen war
| Artefakt | Beginn (UTC) | Ende (UTC) | Dauer in Minuten | Quelle |
|---|---|---|---|---|
| PyPI-Wheels | 11:39:51 | 13:23 | 103 | Anbieter, 21.10.2026 |
| GHCR-Tags | 11:46:56 | 13:51 | 124 | Anbieter, 21.10.2026 |
| Docker-Hub-Tags | 11:46:56 | 14:20 | 153 | Anbieter, 21.10.2026 |
Alle Zeiten stammen vom Anbieter, die Dauer haben wir daraus gerechnet. Datadog nennt abweichende Enden (PyPI 13:20, Images 13:53). Eigene Messungen stehen im Abschnitt weiter unten.
Zu den Docker-Tags nach 14:20 UTC: Der Anbieter schreibt, die Tags seien dann entfernt oder auf geprüfte saubere Images umgestellt worden. Unsere Registry-Abfrage vom 22.10. gegen 04:05 UTC (vom Gateway-Host aus) zeigte für latest, main-latest und main-stable saubere Indizes mit Push-Zeiten von 17:50:38 bis 17:51:10 UTC. Das ist mit der Anbieter-Angabe vereinbar, wenn die Tags dazwischen entfernt waren; was ein Pull in dieser Phase lieferte, haben wir nicht gemessen. Ob ein bestimmter Lauf das erlebt hat, entscheidet nur das eigene Pull-Protokoll. Die Zwischenzustände der Tags können wir heute nicht mehr nachmessen.
Stand der Messung am 22.10. gegen 04:05 UTC: 1.107.0 ist von PyPI verschwunden, v1.107.0 gibt es auf Docker Hub und GHCR nicht mehr (jeweils 404), und latest, main-latest, main-stable sowie v1.106.2 zeigten auf GHCR alle auf denselben sauberen Index sha256:7294b90f868c…. Stand 5. November laut PyPI: 1.107.0 existiert weiter nicht, neu sind 1.106.3 bis 1.106.7 und 1.108.0 (hochgeladen am 4. November 2026).
Was wir am eigenen Gateway gemessen haben
Wir betreiben LiteLLM als Gateway vor vier RTX 5090. Gemessen am 22.10.2026 zwischen 04:00 und 04:25 UTC auf dem Host des Containers litellm-router, nur lesend. Aus dem Internet ist die Proxy-Adresse nicht erreichbar, im eigenen LAN war der Listener erreichbar (Punkt 8). Wie wir das Gateway sonst prüfen, steht in unserem Beitrag zur Sicherheitsprüfung des Gateways.
- Das laufende Bild ist
ghcr.io/berriai/litellm:v1.100.4, gezogen am 30.09., Container lautdocker inspectam 22.10. seit 03.10. 21:33 UTC aktiv (unser Schwesterbeitrag nennt für den 24.10. einen Start am 20. Oktober, die Abweichung haben wir nicht aufgeklärt; beide Daten liegen vor dem Vorfall), innere Version 1.100.4. Der GHCR-Digest vonv1.100.4lautetsha256:ed8efb7e8c58…und ist identisch mit dem Digest im lokalen Image-Store. Das eigene Bild ist damit unverändert das Upstream-Artefakt. - Die drei vergifteten Index-Digeste aus dem Incident-Post (
1bbcb1443bc9…,c00f9d92c427…,7b00e91cd2fe…) kommen im lokalen Image-Store nirgends vor. - Wir fahren Versions-Tags,
latestwar bei uns nie in Verwendung. Diese Entscheidung war an dem Tag die eigentliche Sicherheit. Die beiden selbstgebauten Bilder (Auto-Router und Log-Ansicht, beide am 21.10. gebaut) enthalten das Paketlitellmgar nicht, das einzige per pip installierte LiteLLM auf dem Host ist 1.97.0 in einer alten Testumgebung. - Die Dateisuche nach
cook.pthundcook.pyergibt keine Treffer, die nachsitecustomize.pydrei Treffer aus den mitgelieferten ddtrace- und OpenTelemetry-Paketen desselben Images. Ohne Hash wäre das eine Geistersuche. - Das Gateway speichert Volltexte:
store_prompts_in_spend_logs: true, Aufbewahrung 30 Tage. Die Spend-Log-Tabelle hielt am 22.10. 1.170.710 Zeilen im Fenster vom 03.10. bis 22.10. auf 20 GB, davon 60.952 Anfragen in den letzten 24 Stunden. Vollständige Anfragekörper stecken in 215.607 Zeilen in der Spalteproxy_server_request, Antworttexte in 205.131 Zeilen inresponse; die Spaltemessagesbleibt in dieser Fassung überall leer. Die Tabelle liegt in derselben Datenbank, die das Schadprogramm überDATABASE_URLanspricht. Die Berichte nennen als Ziel nur die Credentials- und die Modell-Tabelle. - Die Suche nach Schlüssel-Mustern in den gespeicherten Volltexten: Die Zeichenkette
github_pat_kommt in 94 Zeilen vor (Stand 04:07 UTC), 84 davon sind vor 04:00 UTC entstanden, verteilt über die Zeit seit dem 3. Oktober. Ein Schlüssel dieser Klasse in voller Länge, also das Präfix mit zwanzig oder mehr Zeichen daran, steht in keiner der 1,17 Millionen Zeilen. Die Kurzformenghp_,gho_,ghu_,ghs_undghr_in Schlüssellänge ebenfalls nicht. Der maskiert gelesene Kontext der neuesten Treffer zeigt Agenten-Prompts, die über dieses Gateway laufen und dabei über Token-Leaks nachdenken. Wer eine solche Tabelle nach einem Präfix durchsucht, bekommt eine Liste von Textstellen zurück, in denen jemand über Schlüssel schreibt, und hält das für einen Fund. - Anthropic-förmiger Verkehr ist die Ausnahme: Sieben Anfragen liefen über den Pfad
anthropic_messages, elf Zeilen tragen Claude-Modellnamen, alle zwischen 21:33:43 und 21:34:57 UTC am 3. Oktober, also die Startchecks beim Anlegen des Containers. Claude Code über dieses Gateway nutzen wir nicht. - Die Proxy-Adresse hat keinen öffentlichen DNS-Eintrag (vier Kandidatennamen, alle NXDOMAIN), kein Virtual Host an unserem Edge zeigt auf diesen Port, der Listener sitzt auf der LAN-Adresse und für die Upstream-Schiene auf 127.0.0.1. Die öffentliche Chat-Oberfläche davor ist ein eigener Dienst mit Anmeldung.
Die Zahlen aus Punkt 5 bis 7 stammen aus unserer eigenen Auswertung der Spend-Log-Tabelle am 22.10. Eine Nachweisdatei haben wir dazu nicht abgelegt, sie sind darum als Momentaufnahme zu lesen.
| Messgröße | Wert | Datum und Träger |
|---|---|---|
| Zeilen in der Spend-Log-Tabelle | 1.170.710 | 22.10.2026, Postgres des Gateway-Hosts |
| Größe der Tabelle | 20 GB | dito |
| Zeilen mit Anfragekörper | 215.607 | dito |
| Zeilen mit Antworttext | 205.131 | dito |
Zeilen mit der Zeichenkette github_pat_ | 94 | 22.10.2026 04:07 UTC, dito |
| Schlüssel in voller Länge | 0 | dito |
Was Betreiber jetzt prüfen
Die Reihenfolge folgt der Beweislage.
Zuerst die Frage, was am 21. Oktober zwischen 11:46 und 14:20 UTC gelaufen ist. Nachweis führt man über den Digest im Image-Store und im Deploy-Protokoll. Wer latest fährt, braucht zusätzlich die Push-Zeitstempel der Registry, sonst bleibt offen, ob der Lauf Gift zog oder einen Abbruch produzierte. Danach die Rechner, auf denen an dem Tag Python etwas installiert hat, also CI-Läufer, Notebooks, Entwicklungsmaschinen. Ein pip show litellm auf dem heutigen Stand beweist wenig, denn eine später gelöschte Datei hinterlässt in der Paketliste keine Spur. Die drei Dateinamen, die Sperrdatei unter /tmp und der Marker /tmp/.pc_once_72129118c59f5636c0bb sind die besseren Anker, die veröffentlichten Hashes die saubere Unterscheidung.
Bei einem Treffer gilt: In der betroffenen Umgebung zuerst keine Python-Aufrufe mehr, jeder Start ruft die Datei erneut auf. Dateien und Loader entfernen, Image oder Umgebung neu bauen, danach alles drehen, was die Umgebung erreichen konnte: Provider-Schlüssel im Gateway, Master- und virtuelle Schlüssel, Datenbankpasswort und die Cloud-Rolle der Instanz. Der Anbieter geht davon aus, dass der Abfluss gelungen ist, solange die Umgebung Internetzugang hatte. Eine gelöschte Datei ist aus demselben Grund keine Entwarnung. Die Schritte im Einzelnen stehen in unserem Beitrag Welche Schlüssel nach LiteLLM 1.107.0 zu tauschen sind.
Beim Punkt Protokolle hilft der Blick in die eigene Konfiguration mehr als jede Empfehlung von außen: Werden Anfrage- und Antworttexte gespeichert? Bei uns ist es eingeschaltet, und 20 GB Volltext über 30 Tage sind die konkrete Antwort darauf, was im Zweifel rückwirkend lesbar wäre. Wer das nicht braucht, schaltet die Speicherung ab oder verkürzt die Frist. Wer sie braucht, sollte wissen, dass Schlüssel und Tokens darin auftauchen können, ohne dass sie jemand absichtlich hineinschreibt. Mit der Ursache dieses Angriffs hat das nach der Korrektur des Anbieters nichts zu tun, es ist ein eigenes Risiko dieser Tabelle.
Der wichtigere Hebel aus dem Vorfall steht in der Ursachenkette: ein laut Anbieter nicht widerrufener Schlüssel in der Git-Historie und ein unbeschränktes GitHub-CLI-Token. Wer ein Gateway oder eine CI betreibt, prüft zuerst, welche Schlüssel je in einem Repo standen und ob sie noch gültig sind. Weitere Beiträge zum Thema bündelt der Hub KI im Betrieb. Wie wir KI-Gateways für Kunden aufsetzen, steht auf der Seite Unternehmens-LLM im eigenen Haus.
Weiterführende Quellen
- Contained: Oct 21, 2026 LiteLLM release v1.107.0 Compromised, LiteLLM, 21.10.2026 (mit Korrekturhinweis vom 23.10.)
- Root-Cause-Analyse zum Vorfall vom 21. Oktober, LiteLLM, 23.10.2026
- Issue #47826 im LiteLLM-Repo, Datei-Hashes und Payload-Analyse von Upwind, 21.10.2026
- LiteLLM supply chain compromise: What you need to know, Datadog Security Labs, 21.10.2026
- LiteLLM Supply Chain Compromise, Wiz, 21.10.2026
- LiteLLM PyPI Package and Docker Images Compromised to Steal Cloud and AI Credentials, Socket, 21.10.2026
- MAL-2026-2144, OSV, Stand 5.11.2026
- litellm auf PyPI (JSON-Schnittstelle), Versionsliste, abgerufen 5.11.2026
- Simple Icons: pypi.svg, github.svg und docker.svg (Logos, CC0-1.0, Stand 5.11.2026; Marken bleiben bei den Inhabern, für GitHub gelten die Logo-Richtlinien)
- AI-Gateway lokal betreiben: unsere Strecke und der Admin-Übernahmefall von Anfang Oktober
Müssen wir sofort die Version wechseln?+
Wer 1.107.0 nie installiert oder gezogen hat, wechselt wegen dieses Vorfalls nichts. Der Anbieter nannte am 21. Oktober 1.106.2 als Pin. Seitdem sind laut PyPI weitere Versionen erschienen (1.106.3 bis 1.106.7, 1.108.0 am 4. November), und der Anbieter hat den Signaturschlüssel für Docker-Images getauscht. Entscheidend ist, was am 21. Oktober zwischen 11:46 und 14:20 UTC im Container lief. Nachweisbar ist das am Image-Digest und im Deploy-Protokoll, der Tag-Name allein genügt nicht.
Reicht es, nach cook.py zu suchen?+
Nein. cook.pth, cook.py und sitecustomize.py stehen auf der Prüfliste des Anbieters, aber sitecustomize.py ist ein gebräuchlicher Dateiname für Auto-Instrumentierung. In unserem laufenden Bild liefert die Suche drei Treffer, die alle aus den mitgelieferten ddtrace- und OpenTelemetry-Paketen stammen. Eindeutig wird es erst mit den veröffentlichten Hashes aus Issue 47826.
Warum interessieren die Spend-Logs, wenn unsere Instanz nicht angegriffen wurde?+
Das Schadprogramm liest bei gesetzter DATABASE_URL die Provider-Schlüssel aus der Datenbank des Gateways. Dieselbe Datenbank hält bei uns auch Volltexte der Anfragen. Der Anbieter schreibt außerdem, Claude Code bette in bestimmten Fällen das GitHub-CLI-Token des Nutzers in einen Antworttext ein, und Antworttexte landen in Logs. Die Frage an jeden Betreiber: Speichere ich Volltexte, und welche Schlüssel darin wären nach 30 Tagen Aufbewahrung noch ein Schaden?
senn-tech