LiteLLM 1.107.0 compromesso su PyPI: come verificare, ripulire, ruotare
LiteLLM 1.107.0 (PyPI, Docker) conteneva uno stealer che parte a ogni avvio di Python. Chi è esposto, come verificare senza riattivarlo, cosa ruotare.
Pubblicato da Zero Hunt, un red team AI autonomo su un'appliance on-premise con AI privata: penetration test automatizzato per reti e infrastrutture, in black box o gray box, con una persona che approva ogni passo che conta.
Per circa due ore, il 21 ottobre 2026, pip install litellm e il tag Docker latest di LiteLLM hanno distribuito uno stealer di credenziali e un loader che ogni ora scarica ed esegue codice remoto. La 1.107.0 malevola è stata costruita dalla pipeline di rilascio di LiteLLM, firmata e pubblicata con una provenance valida, ma il sorgente era un repository sosia, Berri-AI/litellm, a un trattino di distanza da quello vero, BerriAI/litellm. Wiz conta circa 49.000 download della versione compromessa e ha visto l'attaccante provare le credenziali AWS rubate quattro ore e mezza dopo. Questa pagina è il runbook: chi è esposto (anche tramite altri framework), come verificare senza far ripartire il payload, l'ordine di bonifica indicato dal vendor, cosa ruotare per primo.
Notizia in evoluzione — pubblicata alle 22:27 (20:27 UTC) del 22 ottobre 2026. Aggiornata man mano che LiteLLM e i ricercatori pubblicano.
In breve
| Prodotto / versioni colpite | litellm==1.107.0 su PyPI (7 wheel) · Docker Hub e GHCR v1.107.0, latest, main-latest, main-stable (anche litellm-database, litellm-non_root) |
| Finestra di esposizione | PyPI 2026-10-21 11:39–13:23 UTC · tag Docker 11:46–14:20 UTC |
| Versione sicura | 1.106.2 (l'ultima su PyPI al 2026-10-22; nessuna 1.107.1 è stata pubblicata) |
| Payload | cook.pth, cook.py, sitecustomize.py in site-packages: stealer a ogni avvio di Python, loader orario che tiene /tmp/remote_code_loader.lock |
| Sfruttata attivamente | Sì: credenziali di ruolo AWS rubate provate tra le 16:20 e le 17:01 UTC del 21 ottobre (Wiz) |
| Download | Circa 49.000 della 1.107.0 (Wiz, su dati ClickPy) |
| Advisory ufficiale | Report di incidente di LiteLLM |
Cos'è successo: pipeline vera, sorgente falso
LiteLLM è la libreria Python e il proxy che molti team mettono fra il proprio codice e le API di OpenAI, Anthropic, Bedrock e degli altri modelli; Socket la stima in circa 23 milioni di download a settimana. È proprio quella posizione a renderla un bersaglio: un host LiteLLM custodisce le chiavi dei provider di tutto ciò che gli sta dietro.
Secondo il report di incidente di LiteLLM, dopo la compromissione di marzo 2026 (versioni 1.82.7 e 1.82.8) la pipeline era stata irrobustita: repository di rilascio privato, PyPI Trusted Publishing, tag Docker immutabili, immagini firmate. Non è servito, perché l'attaccante non ha dovuto aggirare la pipeline. L'ha usata.
- L'attaccante aveva i token OAuth della GitHub CLI di tre ingegneri di LiteLLM. Secondo LiteLLM provenivano dai log di un deployment interno esposto su Internet, dove era comparso un account utente inatteso; i corpi delle risposte finiti nei log potevano contenere i token (il report spiega che un assistente di coding AI può includere il token
ghdell'utente nel corpo di una risposta). Il punto d'ingresso iniziale, forse una vulnerabilità corretta di recente, non è confermato. - Alle 10:08 UTC il token di un owner dell'organizzazione ha scavalcato la branch protection sul workflow di rilascio privato. Alle 10:46 è partita una run da
Berri-AI/litellm, dove l'attaccante aveva aggiunto dieci commit sopra il tag pulito; alle 11:11 la credenziale di un secondo ingegnere ha superato il gate di approvazione di produzione. - Alle 11:39 le sette wheel erano su PyPI; alle 11:46 le immagini erano pubblicate. Alle 11:53 una terza credenziale ha cancellato i record delle run.
Il tag v1.107.0 su GitHub è pulito: una review del codice del tag non avrebbe visto nulla. StepSecurity ha trovato la modifica nella configurazione di build (data = "cook" sotto [tool.maturin] in pyproject.toml), e la provenance SLSA delle immagini indica come sorgente il repository col trattino. La provenance era valida: descriveva semplicemente il repository sbagliato, la stessa lezione dell'ondata Shai-Hulud che a maggio distribuiva pacchetti firmati.
La domanda da fare a un rilascio firmato non è "la firma è valida?", ma "la provenance indica il repository che mi aspettavo?". Qui non lo indicava, e nessuno lo controllava.
Cosa fa il payload
Dalle analisi di Datadog Security Labs, StepSecurity e Aikido:
cook.pthcontieneimport cook. Python esegue i file.pthdi site-packages a ogni avvio dell'interprete, quindi lo stealer parte con qualunque processo Python di quell'ambiente, compresopip, anche se nessun codice importa LiteLLM.cook.pyraccoglie credenziali cloud (AWS, GCP, Azure, compresi metadata d'istanza e credenziali di ruolo), segreti di Kubernetes, Docker, Vault e Terraform, chiavi SSH, GPG e TLS, token Git, npm e PyPI, file.env, history della shell e dei database, l'ambiente di ogni processo leggibile, password e cookie dei browser, le configurazioni di oltre 20 strumenti di coding AI e, seDATABASE_URLè impostata, le tabelle del proxy LiteLLM con le chiavi dei provider. Cifra l'archivio (AES-256, chiave protetta con RSA-4096) e lo invia in POST ahttp://www.litellm.dev/healthcheck. Scrive/tmp/.pc_once_72129118c59f5636c0bbsolo dopo un invio riuscito: se l'upload è bloccato, ci riprova a ogni avvio.sitecustomize.pyavvia su Linux e macOS un demone staccato che ogni ora legge un hostname da un file su GitHub (letters9813/pythonclient-test/main/domain.txt) ed esegue ciò che restituiscehttp://<host>/codepy. Finché è rimasto online il file conteneva solo valori di test, quindi non è stato osservato un secondo stadio, e l'account è stato cancellato. Su Windows il loader fallisce, ma lo stealer parte comunque.
Wiz ha visto il passo successivo: tra le 16:20 e le 17:01 UTC credenziali di ruolo AWS prese da runner CI e nodi Kubernetes sono state provate da 168.100.8[.]57 e 165.227.201[.]8, con sts:GetCallerIdentity seguito da bedrock:ListFoundationModels, le prime mosse del LLMjacking, e in alcuni ambienti da chiamate di discovery su IAM e CloudTrail.
Chi è esposto
Sei esposto se qualcosa nel tuo perimetro ha installato o eseguito la 1.107.0 nella finestra, e il perimetro è più ampio di "usiamo LiteLLM":
| Percorso | Esposto se |
|---|---|
| Installazione diretta | pip install litellm / uv add litellm senza versione fissata, tra le 11:39 e le 13:23 UTC |
| Proxy LiteLLM su Docker / Kubernetes | Pull di latest, main-latest, main-stable o v1.107.0 tra le 11:46 e le 14:20 UTC (conta anche un riavvio con imagePullPolicy: Always) |
| Framework con range LiteLLM aperto | Installazione da zero o aggiornamento del lock nella finestra. Metadata PyPI attuali: DSPy richiede litellm>=1.65.8 (dipendenza principale); anche gli extra CrewAI [litellm], smolagents [litellm] e Google ADK [extensions]/[all] sono aperti |
| Framework con versione esatta | Non esposti per quella via: aider-chat fissa litellm==1.81.10, OpenHands 1.84.1; mem0ai limita a <1.98.0 |
| Runner CI, notebook, portatili degli sviluppatori | Qualunque job o kernel che ha risolto le dipendenze nella finestra; anche i runner effimeri hanno ceduto i segreti che avevano |
| Mirror e cache interni | Artifactory, Nexus, devpi o una cache pip che ha conservato la wheel 1.107.0 possono servirla di nuovo anche dopo la rimozione da PyPI |
LiteLLM dichiara che i clienti enterprise sul registry privato non sono stati colpiti e che tutte le versioni precedenti sono sicure.
Remediation
1. Sono colpito? Verifica senza avviare Python
Su un host forse infetto, ogni comando python o pip riesegue lo stealer. Verifica prima dalla shell:
# file del payload e metadata della 1.107.0 in qualunque ambiente
find / \( -name cook.pth -o -name cook.py -o -name 'litellm-1.107.0.dist-info' \) -path '*-packages*' 2>/dev/null
find / -name sitecustomize.py -path '*-packages*' -exec sha256sum {} + 2>/dev/null | grep 34bef04fe88ac9d0
# marker: lo stealer ha inviato i dati / il loader è (o era) attivo
ls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock 2>/dev/null
lsof /tmp/remote_code_loader.lock 2>/dev/null
Alcune distribuzioni includono un sitecustomize.py legittimo fuori da site-packages: confronta l'hash invece di cancellare a vista. Trovare i file o i marker conferma l'esposizione; non trovarli non prova nulla, perché container e pulizia di tmp cancellano i marker.
Poi l'inventario, che non richiede un interprete infetto:
# file requirements nei repository
grep -rnE 'litellm(\[[^]]*\])?==1\.107\.0' --include='requirements*.txt' .
# uv.lock / poetry.lock / Pipfile.lock riportano la versione su una riga successiva
grep -rn -A3 --include='uv.lock' --include='poetry.lock' --include='Pipfile.lock' -E '"litellm"' . | grep '1\.107\.0'
# immagini sull'host e pod in esecuzione
docker images --digests | grep -i litellm
kubectl get pods -A -o jsonpath='{range .items[*]}{range .status.containerStatuses[*]}{.imageID}{"\n"}{end}{end}' | grep -i 1bbcb1443bc962e45f
L'indice d'immagine compromesso è sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e; gli hash delle sette wheel sono nei report di Datadog e Aikido. Se hai uno SBOM o un inventario delle dipendenze, cerca [email protected].
Le evidenze di rete chiudono la questione: qualunque risoluzione DNS di litellm.dev, qualunque POST a /healthcheck con header X-Token, qualunque richiesta al percorso GitHub di cui sopra o a un percorso /codepy. Una richiesta a /codepy significa che è stato eseguito codice dell'attaccante.
2. Bonifica, nell'ordine del vendor
- Termina il processo che tiene
/tmp/remote_code_loader.lock. - Dalla shell, cancella
cook.pth,cook.py,sitecustomize.pye i relativi__pycache__/cook*.pycesitecustomize*.pycda ogni site-packages colpito. Disinstallarelitellmnon li rimuove. - Non fare
pip installdella correzione in quell'ambiente: ricostruisci da zero virtualenv o immagine conlitellm==1.106.2(o l'immaginev1.106.2fissata per digest). - Elimina wheel e immagini 1.107.0 dai mirror interni, dai registry e dalle cache pip.
- Blocca
litellm.devsu DNS e in uscita.
3. Ruota, da una macchina pulita, in quest'ordine
Se la 1.107.0 è stata eseguita su un host con accesso a Internet in uscita, LiteLLM indica di dare per riuscito l'invio dei dati.
- Ruoli e chiavi cloud raggiungibili dall'host: ruoli d'istanza e di task, ruoli di deploy della CI, access key statiche. Sono state le prime a essere usate.
- Segreti del proxy LiteLLM: tutte le chiavi dei provider nel database del proxy e nelle configurazioni dei modelli, master key, salt key, virtual key e la password di
DATABASE_URL. - Token dei provider LLM e degli strumenti AI (OpenAI, Anthropic, Bedrock, Gemini, Copilot, Cursor e simili), che generano costi diretti.
- Token di sorgenti e registry: GitHub/GitLab (compresi i token OAuth della CLI
gh), npm, PyPI, registry di container. Un token di pubblicazione rubato è il modo in cui viene avvelenato il prossimo pacchetto. - Segreti di Kubernetes, Vault, Terraform e della CI, chiavi SSH e GPG.
- Macchine degli sviluppatori: sessioni e password salvate nei browser, che lo stealer ha letto.
Poi cerca l'uso: in CloudTrail, GetCallerIdentity e ListFoundationModels dai tuoi ruoli dopo le 11:39 UTC del 21 ottobre, qualunque chiamata da 168.100.8.57 o 165.227.201.8, credenziali di ruolo usate da fuori dalle tue reti. Una credenziale è "ruotata" solo quando quella vecchia smette di funzionare.
4. Evitare il prossimo
- Fissa le versioni con gli hash.
pip install --require-hashes -r requirements.txt, oppure unuv.lock/poetry.lockversionato. Una nuova versione deve arrivare con un aggiornamento del lock rivisto, non con unpip installda zero. - Aggiungi un periodo di attesa. La maggior parte dei rilasci avvelenati viene scoperta in poche ore; risolvere solo versioni più vecchie di qualche giorno (ad esempio con
uv --exclude-newer) avrebbe saltato del tutto questa. - Controlla cosa dice la provenance. Firme e attestazioni qui erano valide; era sbagliato il repository sorgente. Verifica il repository, non solo la firma.
- Mirror e allowlist. Scarica i pacchetti da un mirror interno che possa bloccare una versione appena viene segnalata.
- Limita le uscite della CI ai registry e agli endpoint che servono davvero ai job: lo stealer doveva raggiungere
litellm.devper inviare qualcosa. - Tratta i token CLI degli sviluppatori come segreti di produzione. Brevi, con scope ridotto, mai nei log; non loggare i corpi delle risposte degli strumenti AI.
- Per i tuoi pacchetti: Trusted Publishing al posto dei token a lunga durata, tag di rilascio protetti, revisori obbligatori sull'ambiente di rilascio, modifiche ai workflow che un token di owner da solo non può pubblicare. Il caso IronWorm su npm mostra lo stesso abuso della pipeline.
Lo schema dietro l'attacco
Non è un evento isolato. LiteLLM era già stata colpita a marzo; il 23 settembre, secondo Safedep, il pacchetto MemoryOS su PyPI e un plugin npm di MemTensor hanno distribuito un worm che rubava i token di pubblicazione dai job di rilascio degli stessi progetti e inseriva workflow loader in ogni repository raggiungibile con le credenziali rubate. In tutti i casi il registry ha servito esattamente ciò che la pipeline legittima del progetto aveva caricato. Il punto di controllo si è spostato da "questo pacchetto viene dal maintainer giusto?" a "è stato il repository giusto, al commit giusto, a produrre questo artefatto?", e pochissimi consumatori lo verificano. Per il resto degli incidenti di ottobre, vedi il nostro tracker verificato di Black October; per la vulnerabilità precedente di LiteLLM, la nostra analisi di CVE-2026-42271.
Cosa non si sa ancora
- Come l'attaccante sia entrato inizialmente nel deployment interno di LiteLLM (LiteLLM sospetta una vulnerabilità corretta di recente).
- Se il dead drop su GitHub abbia mai servito a qualcuno un vero secondo stadio prima della cancellazione dell'account.
- Quanti dei circa 49.000 download siano installazioni reali e non mirror o scanner, e quanti invii siano riusciti.
Dopo la bonifica: dimostrare che non resta nulla di raggiungibile
Ruotare le chiavi risponde a "cosa hanno preso?". La domanda più difficile per il team di sicurezza è "cosa può ancora raggiungere chi ha quelle credenziali, e la rotazione l'ha davvero chiuso?". È una domanda di test, ed è dove aiuta un red team AI autonomo on-premise: gli agenti Credential e Pivot di Zero Hunt partono dall'accesso che avrebbe un attaccante, mostrano quali sistemi gli account e gli host esposti raggiungono ancora e ripetono gli stessi test dopo la rotazione, con un essere umano che approva ogni passaggio rilevante. Gira sui propri modelli di AI privata dentro il tuo perimetro, quindi sull'appliance non c'è nessuna chiave di un provider da far prendere al prossimo stealer, e risultati e credenziali non escono dalla tua rete. Se stai scegliendo una piattaforma per questo, la nostra guida all'acquisto di una piattaforma di pentest on-premise elenca le domande da fare.
È sfruttabile nel tuo ambiente?
Zero Hunt lo verifica sulla tua rete: un red team AI autonomo su un'appliance on-premise, con AI privata, in black box o gray box e con una persona che approva ogni passo che conta. La prova di cosa è sfruttabile, la correzione e le evidenze firmate — nessun dato esce dal tuo perimetro.