LiteLLM 1.107.0 malveillant vole les identifiants et installe des portes dérobées sur les hôtes
Un acteur malveillant a détourné le pipeline de publication de LiteLLM avec une clé maîtresse divulguée et jamais révoquée, puis publié une version 1.107.0 malveillante sur PyPI et Docker le 21 octobre 2026. Le wheel installe des fichiers de démarrage Python qui volent les identifiants et activent une porte dérobée toutes les heures, même si LiteLLM n’est jamais importé. PyPI a mis la version en quarantaine après 1 heure et 44 minutes ; toute personne l’ayant installée doit reconstruire l’environnement et renouveler tous les secrets accessibles.
En bref
- LiteLLM 1.107.0, publié sur PyPI à 11:39 UTC le 21 octobre 2026, puis sur Docker Hub et GHCR à 11:46 UTC, a été construit par un attaquant, pas par le projet. PyPI l’a mis en quarantaine à 13:23 UTC et les tags Docker ont été supprimés ou réorientés à 14:20 UTC, après le signalement de l’attaque par Upwind à 12:06 UTC.
- Le wheel malveillant écrit des fichiers de démarrage Python en dehors du répertoire du package LiteLLM. Le payload s’exécute donc à chaque démarrage de Python dans cet environnement, même si LiteLLM n’est jamais importé. Supprimer uniquement le package laisse ces composants de démarrage en place, comme l’ont documenté les chercheurs en sécurité de Microsoft.
- Le payload vole des identifiants : il collecte les secrets SSH et Git, les variables d’environnement, les jetons de registres et de gestionnaires de packages, les clés cloud et d’IA et les données des navigateurs, puis les envoie à litellm[.]dev/healthcheck. Il inclut aussi une porte dérobée Unix qui récupère et exécute du code fourni par l’attaquant toutes les heures.
- Si un élément de votre parc a installé ou récupéré 1.107.0 pendant cette fenêtre, considérez l’hôte et ses secrets comme compromis : reconstruisez l’environnement à partir d’une version propre vérifiée et renouvelez tous les identifiants accessibles à l’hôte, au build et à la CI environnante. La dernière version PyPI propre est 1.106.3.
Suis-je concerné ?
Vous êtes concerné si toutes ces conditions sont réunies :
- Un composant a installé litellm 1.107.0 depuis PyPI entre 11:39 UTC et la mise en quarantaine à 13:23 UTC le 21 octobre 2026, ou récupéré une image Docker ou GHCR LiteLLM publiée entre 11:46 UTC et la suppression ou la réorientation des tags v1.107.0, latest, main-latest et main-stable à 14:20 UTC.
- L’environnement a été démarré au moins une fois après l’installation. L’exécution passe par les fichiers de démarrage Python : importer LiteLLM n’est donc pas nécessaire, et ne l’avoir jamais appelé ne protège pas l’hôte.
- Vous n’avez pas depuis reconstruit l’environnement et renouvelé ses secrets.
Vérifiez ce qui est installé et figé sur les hôtes, dans les runners CI et dans les images :
pip show litellm | grep -E '^(Name|Version|Location)'
grep -rn 'litellm' requirements.txt pyproject.toml 2>/dev/null
grep -n -A2 'name = "litellm"' uv.lock poetry.lock 2>/dev/null # shows the pinned version
docker images | grep -i litellm # local caches
Dans la CI et sur les machines des développeurs, recherchez aussi les fichiers de démarrage Python qui ne devraient pas être là : fichiers .pth inattendus, sitecustomize.py ou usercustomize.py dans les répertoires site-packages ou user-site de l’environnement. C’est le mécanisme de persistance utilisé par le wheel. Au 25 octobre, PyPI ne répertorie aucune version finale 1.107.0 (uniquement des préversions dev et rc), et 1.106.3 est la version finale la plus récente. Cela confirme rapidement qu’aucune version finale 1.107.0 n’a jamais été publiée légitimement.
Pour mesurer l’ampleur : litellm a enregistré 21 343 835 téléchargements PyPI la semaine dernière (pypistats, vérification le 25 octobre 2026). Une fenêtre d’exposition de 1 heure et 44 minutes atteint donc tout de même un grand nombre d’installations automatisées, de caches CI et de builds nocturnes.
Que fait le code malveillant ?
La version malveillante transforme LiteLLM, initialement une bibliothèque, en implant au niveau de l’hôte en installant du code dans le chemin de démarrage de Python, en dehors du package supprimé par une désinstallation normale.
Les analyses de Socket, du groupe de recherche OpenSourceMalware et de Microsoft Security Research concordent sur deux chemins d’exécution dans le wheel 1.107.0 :
- Un collecteur d’identifiants qui s’exécute au démarrage de Python. Il recueille les informations système, les fichiers SSH, les identifiants et jetons Git et GitLab, les variables d’environnement, les éléments d’authentification des outils de développement, applications IA, environnements cloud et navigateurs web, ainsi que les jetons associés à npm, PyPI, pip, Yarn, Maven, Gradle, Composer, RubyGems et Cargo. Les données sont compressées puis envoyées par HTTP POST à litellm[.]dev/healthcheck, un domaine enregistré le 13 octobre 2026, huit jours avant la publication.
- Une porte dérobée destinée à Unix qui démarre en arrière-plan, contacte l’attaquant et récupère puis exécute du code arbitraire toutes les heures. Elle assure une persistance qui survit à la correction d’un vol de session ou à un changement de mot de passe.
Le détail déterminant est l’emplacement du code. Le wheel contenait trois fichiers, cook.pth, cook.py et sitecustomize.py, dans son répertoire de données litellm-1.107.0.data/purelib/. À l’installation, pip les copie à la racine de site-packages, en dehors du package litellm. Les fichiers figurent avec des hachages valides dans le manifeste RECORD du wheel : ils étaient donc intégrés à la version, et non ajoutés ensuite. Python exécute les fichiers .pth et sitecustomize.py de site-packages à chaque démarrage de l’interpréteur. Microsoft Security Research indique que les fichiers de démarrage malveillants se trouvaient hors du répertoire de l’application LiteLLM : « le simple fait de démarrer Python dans un environnement affecté pouvait activer le code malveillant, même lorsque LiteLLM lui-même n’était jamais importé », et « supprimer uniquement les fichiers de l’application pouvait laisser les composants de démarrage malveillants en place ». Désinstaller ou mettre à jour le package ne constitue donc pas un nettoyage ; il faut reconstruire l’environnement.
Comment la compromission s’est-elle produite ?
La divulgation de LiteLLM reconstitue toute la chaîne, et la chronologie de Socket ajoute les détails au niveau des workflows :
- Le 27 mai 2026, un fichier de configuration enregistré dans le dépôt public LiteLLM-Labs/lite-harness contenait la clé maîtresse d’un déploiement LiteLLM Agent Platform. L’erreur a été repérée, le commit a été remplacé par un force-push et la clé a été signalée comme devant être révoquée, mais cette révocation n’a jamais eu lieu.
- Le 10 octobre, un attaquant opérant depuis 69.48.229[.]143 a utilisé cette clé maîtresse non révoquée pour entrer dans Agent Platform et en extraire un jeton OAuth GitHub CLI configuré sur place. Ce jeton appartenait au compte propriétaire de l’organisation GitHub et ses permissions n’avaient pas été restreintes, ce qui donnait un accès de propriétaire à tous les dépôts de l’organisation.
- Entre 22:11 et 22:12 UTC ce jour-là, l’attaquant a poussé des commits vers des branches éphémères nommées ci-tmp-a351cc et ci-tmp-4d8517 dans des dépôts internes (BerriAI/litellm et BerriAI/litellm-auto). Chacun ajoutait un workflow dans
.github/workflows/cache.yml, sous l’intitulé anodin « ci: bump cache config », déclenché à chaque push. Les workflows collectaient des jetons GitHub et des clés d’API de l’espace Linear de l’organisation, du déploiement LiteLLM et de Together AI, ainsi qu’un identifiant GitHub App et sa clé de signature, puis les envoyaient vers deux endpoints webhook[.]site contrôlés par l’attaquant. Les exécutions et les branches ont été supprimées quelques minutes plus tard. Une seconde vague le 12 octobre a étendu la collecte aux clés Anthropic, OpenAI et AWS Bedrock, à une clé Tavily, à une clé de tenant de détection de secrets GitGuardian, à l’état de stockage Playwright et aux identifiants PostgreSQL et Redis. La clé de tenant GitGuardian est importante pour la défense : elle donne accès à la plateforme de détection de secrets elle-même, donc à la visibilité sur les alertes qui auraient révélé l’activité, voire à leur contrôle. - Le 21 octobre entre 10:08 et 11:08 UTC, l’attaquant a contourné la protection de branche et réorienté la source du pipeline de publication de BerriAI/litellm vers un dépôt ressemblant, « Berri-AI/litellm », qui ajoute un tiret entre « Berri » et « AI ». Les publications exigent normalement un ingénieur autorisé et l’approbation d’un compte spécial lié à un appareil physique au bureau ; cette restriction avait été assouplie plus tôt dans la semaine pour autoriser n’importe quels deux ingénieurs. L’attaquant a utilisé un jeton de mainteneur volé pour déclencher la publication et un autre pour l’approuver.
- PyPI a reçu 1.107.0 à 11:39 UTC, les images Docker Hub et GHCR ont suivi à 11:46, et les traces d’exécution de production et de préproduction ont été supprimées à 11:53. Le chercheur Upwind Lidor Machluf a signalé l’attaque de la chaîne d’approvisionnement à 12:06, PyPI a mis la version en quarantaine à 13:23, puis les tags Docker ont été supprimés ou réorientés vers des images propres vérifiées à 14:20.
LiteLLM a écarté son hypothèse initiale selon laquelle les jetons GitHub auraient été divulgués dans les journaux de déploiement internes, et révoque désormais tous les jetons associés à chaque système interne et plateforme de fournisseur.
Déroulement de l’attaque
dépôt public, jamais révoquée (27 mai)"] --> B["L’attaquant entre dans Agent
Platform avec la clé (10 oct.)"] B --> C["Extraction du jeton GitHub CLI
du propriétaire de l’organisation"] C --> D["Les branches ci-tmp-* exécutent
des workflows de collecte de jetons"] D --> E["Pipeline réorienté vers le dépôt
ressemblant Berri-AI (21 oct.)"] E --> F["La version malveillante 1.107.0 arrive
sur PyPI, Docker Hub et GHCR"] F --> G["Les hooks de démarrage volent les secrets vers
litellm[.]dev ; porte dérobée toutes les heures"] style E fill:#f8d7da
Que peut faire un attaquant ?
Sur tout hôte où le wheel malveillant s’est exécuté :
- Lire et exfiltrer tous les identifiants lisibles par le propriétaire du processus : clés SSH, configuration et jetons Git, clés de fournisseurs cloud et d’IA, identifiants de bases de données et de caches, variables d’environnement et jetons de registres de packages pour npm, PyPI, pip, Yarn, Maven, Gradle, Composer, RubyGems et Cargo.
- Réexécuter du code fourni par l’attaquant toutes les heures via la porte dérobée. L’attaquant conserve donc l’accès après le renouvellement des identifiants volés, sauf si l’environnement est reconstruit.
- Progresser latéralement grâce aux identifiants CI et de plateformes collectés : jetons GitHub de niveau mainteneur, clé de signature GitHub App, clé de tenant de détection GitGuardian et mêmes catégories de jetons d’API en amont que LiteLLM relaie pour ses utilisateurs.
Pour les mainteneurs du projet, l’impact dépasse une seule version : l’attaquant a détenu pendant onze jours un accès propriétaire à l’organisation GitHub, suffisant pour lire et modifier chaque dépôt, et a collecté les secrets de deux ingénieurs ayant des privilèges de mainteneur.
Que faire si je l’ai installée ?
Il n’existe aucune version vers laquelle simplement mettre à jour : aucune version finale 1.107.0 propre n’existe sur PyPI. Traitez une installation de 1.107.0 comme une compromission de l’hôte, dans cet ordre :
- Isolez les hôtes, conteneurs et runners CI affectés. Arrêtez-les plutôt que de vous y connecter pour une inspection informelle, puisque démarrer Python réexécute le payload.
- Reconstruisez les environnements à neuf (nouvelles images de conteneurs, nouveaux environnements virtuels, nouveaux runners CI) sur une version propre vérifiée. Figez
litellm==1.106.3, la dernière version propre, ou une version ultérieure vérifiée lorsque le projet en publiera une. Ne vous contentez pas depip uninstall litellm: les fichiers de démarrage se trouvent hors du répertoire du package et y survivent. - Renouvelez tous les secrets accessibles à l’environnement, à son build et à sa CI : variables d’environnement, clés SSH, jetons Git et GitLab, clés de fournisseurs cloud et d’IA, identifiants de bases de données et Redis, identifiants stockés dans les navigateurs et jetons de registres de packages de chaque écosystème ciblé par le voleur (npm, PyPI, pip, Yarn, Maven, Gradle, Composer, RubyGems, Cargo). Considérez que l’attaquant les détient, pas qu’il les a simplement vus.
- Vérifiez et nettoyez la source de l’installation : caches CI, images de base et digests Docker récupérés entre 11:39 et 14:20 UTC le 21 octobre, ainsi que les fichiers de verrouillage qui peuvent encore figer 1.107.0 pour le prochain build.
- Si vous travaillez dans l’environnement de l’organisation LiteLLM ou des workflows de ses ingénieurs, examinez le journal d’audit GitHub à la recherche des branches ci-tmp-*, des exécutions de workflows supprimées et des modifications de protection de branche, puis renouvelez la clé de tenant GitGuardian.
Comment vérifier si j’ai été compromis ?
Indicateurs issus des analyses publiques, tous publiés :
- Présence du package :
pip show litellmindiquant 1.107.0, ou fichiers de verrouillage et digests d’images qui la figent. La fenêtre d’exposition PyPI va de 11:39 à 13:23 UTC le 21 octobre 2026 ; celle de Docker Hub et GHCR, de 11:46 à 14:20 UTC. - Réseau : requêtes HTTP POST vers litellm[.]dev/healthcheck (domaine enregistré le 13 octobre 2026), et toute connexion aux deux endpoints d’exfiltration webhook[.]site utilisés par la collecte via workflows, 65a42464-3cfa-4b02-a18c-17a6d7b86f37 et 4da03473-a3e7-4dc2-a087-9c564b4208e1, qui concernent aussi la CI du projet.
- Hôte : fichiers de démarrage
cook.pth,cook.pyetsitecustomize.pydans le répertoiresite-packagesdes environnements ayant installé le wheel, ou tout autre.pth,sitecustomize.pyouusercustomize.pyinattendu ; marqueurs d’exécution/tmp/remote_code_loader.locket/tmp/.pc_once_*; processus d’arrière-plan inexpliqué contactant un hôte distant toutes les heures. - Organisation GitHub (mainteneurs) : branches nommées ci-tmp- suivies de caractères hexadécimaux, commits ajoutant
.github/workflows/cache.ymlsous le titre « ci: bump cache config », exécutions de workflows supprimées quelques minutes après leur création les 10 et 12 octobre, et modifications du pipeline ou de la protection de branche le 21 octobre.
Chronologie
- 2026-05-27 : la clé maîtresse d’Agent Platform est enregistrée dans le dépôt public LiteLLM-Labs/lite-harness ; le commit est remplacé par force-push, mais la clé n’est jamais révoquée.
- 2026-10-10 : l’attaquant utilise la clé, extrait un jeton GitHub du propriétaire de l’organisation et exécute le premier workflow de collecte de jetons depuis une branche ci-tmp-* à 22:11 UTC.
- 2026-10-12 : un second workflow étend la collecte aux identifiants d’IA, de cloud, de bases de données et de détection de secrets.
- 2026-10-13 : le domaine d’exfiltration litellm[.]dev est enregistré.
- 2026-10-21 : à 10:08 UTC, le pipeline de publication est réorienté vers le dépôt ressemblant ; 1.107.0 atteint PyPI à 11:39 et Docker à 11:46 ; les traces d’exécution sont supprimées à 11:53 ; Upwind signale l’attaque à 12:06 ; PyPI met la version en quarantaine à 13:23 ; les tags Docker sont nettoyés à 14:20.
- 2026-10-23 : Microsoft Security Research publie l’analyse des hooks de démarrage Python.
- 2026-10-24 : LiteLLM publie sa divulgation de la cause racine ; Socket publie la chronologie des workflows.
- 2026-10-25 : publication de cet article.
FAQ
PyPI lui-même a-t-il été compromis ?
Non. L’attaquant a utilisé le pipeline de publication propre à LiteLLM avec des identifiants GitHub de niveau mainteneur. Du point de vue de PyPI, l’envoi semblait donc légitime jusqu’au signalement des chercheurs. PyPI a mis la version en quarantaine une heure et quarante-quatre minutes après sa publication, ce qui a limité la fenêtre sans la supprimer.
Suis-je protégé si j’ai installé 1.107.0 mais jamais importé LiteLLM ?
Non. Le code malveillant est installé comme fichiers de démarrage Python en dehors du répertoire du package LiteLLM. Il s’exécute donc au lancement de l’interpréteur, avant tout import. Les environnements où le package était simplement installé et qui exécutaient du code Python sans rapport sont aussi exposés que les applications utilisant LiteLLM comme passerelle LLM.
Puis-je simplement désinstaller le package ou passer à une version après 1.107.0 ?
Non. La désinstallation supprime le package, pas les fichiers de démarrage, et aucune version 1.107.0 propre n’existe : PyPI indique 1.106.3 comme version finale la plus récente. Reconstruisez plutôt l’environnement en figeant la version propre, puis renouvelez les identifiants.
Les images Docker sont-elles aussi dangereuses que le wheel ?
Oui. Le même build malveillant a été publié sur Docker Hub et GHCR à 11:46 UTC, et les tags affectés n’ont été supprimés ou réorientés qu’à 14:20. Les images récupérées pendant cette fenêtre, y compris via latest, contenaient le même payload. Les digests mis en cache peuvent encore les fournir aux builds qui figent les images par digest.
Pourquoi l’approbation de publication n’a-t-elle pas bloqué l’attaque ?
L’approbation provient normalement d’un compte spécial lié à un appareil physique, mais cette restriction avait été assouplie cette semaine-là pour accélérer les publications : n’importe quels deux ingénieurs pouvaient déclencher et approuver. L’attaquant a utilisé un jeton de mainteneur volé pour déclencher la publication et un autre pour l’approuver, en respectant la règle assouplie.
Cela affecte-t-il aussi les clés virtuelles du proxy LiteLLM ?
Tout ce que le processus pouvait lire doit être considéré comme collecté : le voleur récupère les variables d’environnement et les identifiants des fournisseurs cloud et d’IA, précisément là où les déploiements LiteLLM stockent leurs clés en amont et leur configuration de base de données. Renouvelez-les avec tous les autres secrets plutôt que de tenter de déterminer lesquels le code pouvait atteindre.
Le voleur est également conçu pour les déploiements du proxy LiteLLM. Lorsque DATABASE_URL est défini, il lance un processus enfant Python qui utilise Prisma pour lire le champ credential_values de LiteLLM_CredentialsTable et le champ litellm_params de LiteLLM_ProxyModelTable, jusqu’à 500 lignes par requête. Il récupère ainsi les identifiants des modèles en amont stockés dans la base du proxy en plus de ceux de l’environnement.
Scannez vos dépôts, images de conteneurs et configurations CI avec Ostorlab pour repérer chaque environnement qui fige litellm 1.107.0 avant de le reconstruire.
Références
- Divulgation de la cause racine par LiteLLM, « Security incident October 21, 2026: attack chain », par Oliver Jensen et Mateo Wang, 24 octobre 2026 : https://docs.litellm.ai/blog/security-incident-oct-21-2026-attack-chain (consultée le 25 octobre 2026)
- Fichier de configuration divulgué dans le dépôt public LiteLLM-Labs/lite-harness au commit e10b14a : https://github.com/LiteLLM-Labs/lite-harness/blob/e10b14a/harnesses/opencode/opencode.json (consulté le 25 octobre 2026)
- Rectification par un mainteneur LiteLLM de l’hypothèse initiale de fuite de jetons : https://github.com/BerriAI/litellm/issues/47826#issuecomment-6374891275 (consultée le 25 octobre 2026)
- Microsoft Security Research, « Inside the LiteLLM 1.107.0 supply chain compromise: credential theft through Python startup hooks », 23 octobre 2026 : https://www.microsoft.com/en-us/security/blog/2026/10/23/inside-the-litellm-1-107-0-supply-chain-compromise-credential-theft-through-python-startup-hooks/ (consulté le 25 octobre 2026)
- Enquête de Socket sur les commits de workflows ci-tmp-* malveillants et leurs endpoints d’exfiltration : https://socket.dev/blog/litellm-attack-timeline (consultée le 25 octobre 2026)
- Analyse des deux chemins d’exécution du wheel par OpenSourceMalware : https://opensourcemalware.com/blog/litellm-1-107-0-pypi-wheel-contains-a-credential-stealer-and-persistent-python-backdoor (consultée le 25 octobre 2026)
- The Hacker News sur la clé maîtresse divulguée et le détournement du pipeline, 24 octobre 2026, citant la divulgation de LiteLLM : https://thehackernews.com/2026/10/leaked-litellm-master-key-let-attackers.html (consulté le 25 octobre 2026)
- The Hacker News sur le premier signalement du package compromis par SafeDep, Socket, StepSecurity et Upwind, 21 octobre 2026 : https://thehackernews.com/2026/10/litellm-compromised-in-supply-chain.html (consulté le 25 octobre 2026)
- Historique des versions PyPI de litellm, ne montrant aucune version finale 1.107.0 et indiquant 1.106.3 comme version finale la plus récente : https://pypi.org/pypi/litellm/json (consulté le 25 octobre 2026)
- Statistiques de téléchargement PyPI de litellm, 21 343 835 téléchargements la semaine dernière : https://pypistats.org/packages/litellm (consultées le 25 octobre 2026)