LiteLLM 1.107.0 volait les credentials dès le démarrage de Python, sans jamais être importé
Le 21 octobre 2026, une release compromise de LiteLLM 1.107.0 a été publiée sur PyPI et dans les images Docker officielles : trois fichiers de démarrage Python exécutaient un collecteur de credentials dès l’initialisation de l’interpréteur, sans que l’application importe jamais LiteLLM. Si vous avez installé la 1.107.0, ne dégradez pas seulement la version : révoquez les credentials accessibles depuis chaque hôte affecté et reconstruisez les environnements.
21 octobre 2026, 11 h 39 UTC. Sept wheels malveillants de LiteLLM 1.107.0 sont publiés sur PyPI. 21 octobre 2026, 11 h 46 UTC. Les images Docker v1.107.0, latest, main-latest et main-stable pointent vers l’index compromis. 23 octobre 2026. Microsoft publie son analyse technique complète. Le point clé : l’application n’avait pas besoin d’importer LiteLLM — le simple démarrage de Python suffisait à lancer un collecteur de credentials sur la machine.
Pour un RSSI, la leçon tient en une phrase : ce n’est pas un énième paquet empoisonné, c’est la démonstration que les hooks de démarrage Python (*.pth et sitecustomize.py) transforment une dépendance compromise en compromission de tout ce qui tourne sur l’hôte.
Une passerelle d’IA au centre de gravité très sensible
LiteLLM est une passerelle open source qui fédère des centaines de fournisseurs de modèles LLM derrière une API unique. En production, elle se retrouve en position de porte d’entrée : elle détient les clés API des fournisseurs de modèles, les chaînes de connexion aux bases de données, les identités de charge de travail, la configuration de routage et les données de politique des locataires. La même bibliothèque est aussi installée sur des postes de développeurs, des agents de build, des systèmes de test et des conteneurs — ce qui élargit mécaniquement la surface exposée.
Ce n’est pas la première fois que LiteLLM est visé. En mars 2026, le groupe TeamPCP avait empoisonné les versions 1.82.7 et 1.82.8 via un build Trivy compromis, une affaire que nous avions documentée ici. Depuis, l’équipe avait « significativement durci » son pipeline : dépôt de release privé, Trusted GitHub Publishers, tags Docker immuables, images signées. L’incident d’octobre montre que ces contrôles n’ont pas suffi — ils ont été contournés, pas cassés.
Trois fichiers de démarrage, zéro import
La charge utile ne se cachait pas dans le code applicatif. Le wheel compromis embarquait _cook.pth, _cook.py et _sitecustomize.py, placés sous litellm-1.107.0.data/purelib/. À l’installation, ces fichiers sont relocalisés dans le répertoire de bibliothèque de l’environnement Python, hors du répertoire applicatif litellm.
C’est précisément là que tout se joue. Python traite les instructions exécutables des fichiers *.pth pendant l’initialisation du site, puis tente d’importer le module sitecustomize. Résultat : démarrer Python — n’importe quelle invocation, y compris pip install d’une dépendance sans rapport — suffisait à exécuter le code malveillant. Le paquet n’avait pas besoin d’être importé, seulement d’être installé.
La première voie d’exécution commençait par l’unique ligne exécutable de _cook.pth : import cook. _cook.py décodait en Base64, décompressait en zlib, puis exécutait un programme Python embarqué qui s’identifiait comme PRIVCOLLECT. Avant de lancer son thread de collecte, le programme vérifiait la variable d’environnement PRIVCOLLECT_ACTIVE : si elle était vide, il la posait et démarrait la collecte dans un thread daemon, pour limiter la duplication dans les processus descendants.
Ce que le collecteur visait
Le tableau des cibles reconstitué par Microsoft est plus large qu’un vol de clés de modèle :
- Outils développeur et IA : fichiers d’authentification et de configuration des assistants IA, des IDE, de GitHub Copilot, de VS Code et des intégrations MCP.
- Contrôle de source et écosystèmes de paquets : credentials Git, configuration des gestionnaires de paquets, fichiers
dotenvsélectionnés, extensions de clés privées. - Cloud et identités de charge de travail : caches de credentials AWS, Azure CLI et Google Cloud, fichiers de service account Kubernetes, configuration Docker, secrets montés, et tentatives de récupération des credentials de métadonnées AWS et GCP.
- Navigateurs et historiques : données de credentials des navigateurs Chromium et Firefox, historiques de shell, de clients de base de données et d’outils interactifs.
- Environnements de processus : l’environnement du processus courant et, sous Linux, celui des autres processus lisibles via
procfs. - Clés privées et infrastructure : matériel SSH, configuration GPG, TLS, CI/CD, Vault.
- Données du proxy LiteLLM : deux tables de la base du proxy lues via un processus enfant Prisma, avec une limite de 500 lignes par requête.
Chaque module tournait avec sa propre gestion d’erreur, pour que la collecte continue même quand une cible était inaccessible.
Chiffrement et exfiltration
Après collecte, la charge utile sérialisait les résultats et les métadonnées de l’hôte en JSON. Chaque exécution générait une clé AES de 32 octets et un vecteur d’initialisation de 16 octets ; AES-256-CBC chiffrait le document, et une clé publique RSA de 4 096 bits enveloppait la clé AES en OAEP. Le tout était archivé en gzip avec trois membres — data.bin, key.bin et hdr.json — puis envoyé en POST vers hxxp://www[.]litellm[.]dev/healthcheck, un domaine de ressemblance du légitime litellm.ai. Seul un HTTP 200 comptait comme succès ; la routine de téléversement réessayait sinon.
À côté du collecteur, _sitecustomize.py portait un second mécanisme, orienté Unix : un loader qui prenait un verrou exclusif sur /tmp/remote_code_loader.lock, puis lisait un fichier domain.txt hébergé sur GitHub — un pointeur mutable vers un serveur contrôlé par l’attaquant. S’il résolvait, le loader récupérait /codepy et l’exécutait comme code Python dans l’environnement affecté. Au moment de l’analyse, domain.txt était vide et aucune exécution de second étage n’a été confirmée — mais le mécanisme restait un RCE ré-armable, modifiable sans republier de paquet.
Comment le pipeline de release a été contourné
Le rapport des mainteneurs de LiteLLM livre la chronologie, minute par minute, le 21 octobre :
- 10 h 08 UTC : la protection de branche rejette une modification non autorisée du workflow de release privé.
- 10 h 08 : un credential lié à un compte développeur privilégié passe outre la protection et applique le commit de workflow
9cf97e9…. - 10 h 46 : le workflow modifié lance un run de staging utilisant le dépôt source contrôlé par l’attaquant,
Berri-AI/litellm— un quasi-homographe du légitimeBerriAI/litellm. - 11 h 08 : le staging réussit, le pipeline de release production démarre.
- 11 h 11 : un credential lié à un second compte développeur valide la porte d’approbation de production.
- 11 h 39 : sept wheels malveillants montent sur PyPI.
- 11 h 53 : un credential lié à un troisième compte supprime les enregistrements de workflow, amputant les logs d’investigation.
- 13 h 23 : PyPI retire la version ; 14 h 20, les tags Docker Hub sont repointés ou retirés.
La cause racine est aussi instructive que l’attaque : le lundi, l’équipe avait temporairement élargi les permissions de release pour permettre à deux ingénieurs de publier des backports quotidiens. L’attaquant détenait trois jeux de credentials GitHub CLI de développeurs, dont un capable d’exécuter des workflows. Microsoft n’a pas déterminé comment il les avait obtenus.
Verdict
Si vous avez installé, mirroré ou buildé LiteLLM 1.107.0, ne dégradez pas seulement la version. Microsoft et les mainteneurs sont alignés : retirer le paquet ou le tag ne suffit pas, car les fichiers de démarrage vivent hors du répertoire applicatif. Concrètement :
- N’exécutez pas
pythonnipipdans un environnement suspect pour vérifier la présence du paquet — c’est le geste qui relance la charge utile. - Reconstruisez les environnements, les caches
uv/pip, les miroirs internes, les lockfiles et les images ; préférez les digests d’image aux tags flottants. - Révoquez les credentials atteignables depuis chaque hôte : clés de fournisseurs de modèles, identités cloud et de charge de travail, secrets CI/CD, tokens Git, clés SSH/GPG, sessions navigateur, et — sur les hôtes proxy — les credentials stockés et la
DATABASE_URL. - Contraignez l’egress : un poste de dev ou un agent de build ne devrait pas pouvoir joindre n’importe quelle destination en HTTP sortant.
Si vous gérez un proxy LiteLLM en production, isolez-le comme une surface sensible à part entière : c’est un concentrateur de secrets, pas une simple dépendance. Pinez vos versions sur des hashes, signez vos images et traitez toute fenêtre de publication compromise comme un événement de rotation globale, pas comme un simple rollback.