Autopsie de l'attaque LiteLLM 1.107.0 : Quand l'assistant IA écrit les clés de publication dans les logs

Autopsie de l'attaque LiteLLM 1.107.0 : Quand l'assistant IA écrit les clés de publication dans les logs

Sécurité de la chaîne d'approvisionnementLiteLLMPyPIInfrastructure IAFuite d'identifiants

Sources:HN + web research

[tool.maturin]
data = "cook"

Le 21 octobre 2026 à 11:39:51 UTC, la passerelle de modèles de langage open source LiteLLM publiait sa version officielle 1.107.0 sur PyPI. Dans le fichier pyproject.toml du dépôt, une unique ligne de configuration avait été ajoutée. Pourtant, au cours des deux heures suivantes, cette modification a transformé chaque environnement déployé sans version verrouillée en moissonneuse d’identifiants. La frontière de confiance de l’infrastructure d’IA s’est effondrée : il ne suffit plus d’auditer le code source des dépendances, il faut désormais protéger farouchement les identifiants de compilation. Et l’outil qui a livré les clés du royaume aux attaquants n’est autre que l’assistant de programmation IA quotidien des développeurs.

Un hook de 12 octets s’empare de l’interpréteur sans le moindre import

litellm==1.107.0 est resté en ligne sur PyPI jusqu’à 13:23 UTC, soit une fenêtre critique d’environ deux heures. L’ensemble des sept paquets wheels couvrant Linux, macOS et Windows embarquaient la charge utile malveillante. Simultanément, quatre tags clés sur Docker Hub et GHCR — v1.107.0, latest, main-latest et main-stable — ont été détournés. De 11:46:56 à 14:20 UTC, ces balises pointaient invariablement vers l’image compromise sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e. Dès qu’un pipeline d’intégration continue non verrouillé téléchargeait la mise à jour, la porte dérobée pénétrait immédiatement en production.

Le code malveillant a été dissimulé dans le répertoire purelib/ du wheel via la directive data = "cook" de Maturin. Une fois le paquet installé, trois fichiers faisaient leur apparition dans site-packages : un fichier cook.pth de 12 octets, un script cook.py de 22 252 octets et sitecustomize.py pesant 25 509 octets. Le dépôt public BerriAI/litellm n’a jamais comporté de dossier cook/, et l’archive des sources (sdist) en était également dépourvue. Tout audit de sécurité se cantonnant à l’historique public des commits sur GitHub s’est révélé totalement inopérant face à une injection survenue au moment du build.

Le fichier cook.pth ne contient qu’une ligne : import cook. Selon le fonctionnement standard du mécanisme .pth de Python, l’interpréteur exécute au démarrage toute instruction débutant par import. Situé dans le même dossier, sitecustomize.py constitue un autre hook global évalué dès l’initialisation, formant avec la charge utile compressée de cook.py une double assurance d’exécution. La charge malveillante contourne intégralement le cycle d’importation de l’application : même si un serveur n’exécute jamais import litellm, le simple appel à pip ou le démarrage du serveur de langage d’un IDE déclenche immédiatement le logiciel espion.

Injection de fichiers et chaîne d'exécution de la charge utile de LiteLLM 1.107.0 Figure : Injection de fichiers et chaîne d’exécution de la charge utile à deux niveaux du paquet LiteLLM 1.107.0. Source : SafeDep (Rapport d’analyse de sécurité de la chaîne d’approvisionnement)

16 modules d’exfiltration ciblent l’écosystème IA et les bases de données

La charge utile de premier niveau, cook.py, embarquait 16 modules d’exfiltration minutieusement conçus pour fouiller le système hôte. Elle commençait par lire les clés privées dans ~/.ssh/* et l’historique du shell, puis déchiffrait les mots de passe enregistrés dans Chromium et Firefox via l’API DPAPI ou les clés maîtresses. Elle passait ensuite au crible les fichiers d’authentification d’AWS, Azure, GCP, ~/.kube/config, Docker, gh, Vault et Terraform. Le script interrogeait également le point de terminaison de métadonnées AWS IMDS (en tentant IMDSv2 avant de basculer sur IMDSv1) et parcourait /proc/<pid>/environ pour aspirer les variables d’environnement des processus en cours. L’arsenal était taillé sur mesure pour la station de travail d’un ingénieur cloud-native contemporain.

Deux modules spécialisés trahissaient la cible première des assaillants : ai_devtools et db_litellm. Le premier traquait les identifiants de 17 outils de développement d’IA, parmi lesquels Claude Code, Claude Desktop, Cursor, Windsurf, Copilot, le fichier mcp.json de VS Code, Gemini CLI, Codex, Continue, Aider, Zed et Hugging Face. Le second analysait la variable DATABASE_URL pour exporter intégralement les tables LiteLLM_CredentialsTable et LiteLLM_ProxyModelTable de la base de données de la passerelle. Une passerelle d’IA est par nature le concentrateur d’authentification de l’entreprise vers les modèles LLM. Compromettre une seule instance proxy de LiteLLM revient à dérober d’un coup l’ensemble des clés d’API des modèles amont connectés.

Les informations volées étaient chiffrées à l’aide d’une clé aléatoire AES-256-CBC, encapsulée sous OAEP avec une clé publique RSA-4096 intégrée, puis empaquetées dans une archive tar.gz expédiée vers www.litellm.dev/healthcheck avec l’en-tête X-Token: XWiA8gZNa7NcX2XRoAEb. La charge utile de second niveau, sitecustomize.py, posait un verrou sur /tmp/remote_code_loader.lock et se transformait en démon résident via fork() et setsid(). Toutes les 3 600 secondes, elle consultait le fichier domain.txt sur le dépôt GitHub letters9813/pythonclient-test pour relancer l’exfiltration et téléchargeait un script distant /codepy exécuté directement via exec(). Bien que GitHub ait banni le compte letters9813 vers 13:40 UTC, les machines connectées avaient très probablement déjà exécuté le code de seconde phase. Avec le domaine trompeur litellm.dev, les sondes réseau habituelles n’ont décelé aucune anomalie en deux heures.

Trois clés à privilèges franchissent les barrières d’approbation d’un pipeline renforcé

Après un incident de sécurité survenu en mars dernier, l’équipe de LiteLLM avait pourtant considérablement renforcé son architecture de publication. Les workflows de build avaient été transférés vers des dépôts privés, Trusted GitHub Publishers avait été activé, les tags Docker verrouillés en immuabilité et la signature cryptographique des images rendue obligatoire. En théorie, nul intervenant extérieur ne pouvait altérer les scripts de publication ni falsifier une signature officielle.

Les journaux du 21 octobre dévoilent pourtant une intrusion menée avec de véritables clés maîtresses. À 10:08:29 UTC, les règles de protection de branche ont d’abord rejeté une modification de workflow dépourvue de validation de PR. Cinq secondes plus tard, à 10:08:34 UTC, l’attaquant a utilisé un jeton GitHub CLI doté des droits d’administrateur de l’organisation pour désactiver de force la protection et pousser le commit 9cf97e935aeaf791b9c8db224a2bb3ee156134ce. À 10:46:39 UTC, le workflow corrompu extrayait le commit 20535d8fa d’un dépôt usurpateur Berri-AI/litellm (avec un tiret supplémentaire par rapport au légitime BerriAI) et franchissait l’étape de pré-publication. À 11:11:18 UTC, un second compte développeur approuvait le passage en production. Puis, après la diffusion des paquets piégés à 11:53 UTC, un troisième identifiant effaçait l’historique d’exécution des workflows. Lorsqu’un pirate dispose de trois identifiants légitimes dotés des pleins privilèges, les signatures automatiques et les validations humaines n’arrêtent rien : elles apposent un tampon officiel d’authenticité sur un build malveillant.

Rapport officiel de LiteLLM sur la compromission du pipeline le 21 octobre Figure : Chronologie de la compromission du pipeline par des identifiants privilégiés, publiée dans l’avis de sécurité officiel de LiteLLM. Source : LiteLLM (Rapport de sécurité officiel)

Claude Code a injecté des jetons dans ses réponses, transformant les logs en tremplin

Comment les accès GitHub de trois développeurs clés ont-ils pu être dérobés ? L’analyse postmortem officielle de LiteLLM a mis en lumière un comportement inattendu : dans certaines situations, Claude Code incluait le jeton OAuth personnel GitHub CLI de l’utilisateur directement dans le corps de réponse de l’API. Si la passerelle était configurée pour journaliser l’intégralité des réponses HTTP, ces jetons porteurs à hauts privilèges se retrouvaient consignés en clair dans les logs de LiteLLM. Les investigations internes ont ensuite révélé l’existence d’une instance LiteLLM oubliée, accessible depuis l’Internet public. Les assaillants ont exploité une vulnérabilité récemment corrigée pour pénétrer sur cette instance et extraire les jetons GitHub CLI présents dans les fichiers de logs.

Cet enchaînement a suscité de vifs débats au sein de la communauté de la sécurité informatique. Les détracteurs des assistants IA ont souligné que les agents locaux disposent de droits d’accès système démesurés et que laisser fuiter des jetons dans un flux textuel bafoue les principes de moindre privilège et d’assainissement des données. Les experts d’infrastructure ont répliqué qu’exposer publiquement une passerelle enregistrant le corps intégral des requêtes relevait d’une erreur fondamentale d’administration. Ces deux visions mettent en lumière une réalité technique commune : les développeurs utilisent des assistants IA pour coder leurs passerelles, et l’assistant recrache les clés d’accès dans les logs de la passerelle. Cette boucle fermée des outils d’IA démultiplie l’onde de choc de la moindre faille.

Première étape de remédiation : Ne lancez pas pip, et verrouillez vos versions

Pour les équipes ayant déployé la version 1.107.0, la consigne officielle débute par un impératif contre-intuitif : ne lancez surtout pas pip install pour tenter de corriger le problème. pip étant lui-même un processus Python, son exécution réactive immédiatement cook.pth et sitecustomize.py. La procédure appropriée consiste à supprimer manuellement dans site-packages les fichiers cook.pth, cook.py, sitecustomize.py ainsi que les répertoires __pycache__, puis à tuer les démons résiduels détenant le verrou /tmp/remote_code_loader.lock. Ce n’est qu’après ce nettoyage manuel que l’on peut rétrograder vers la version 1.106.2 ou reconstruire intégralement ses conteneurs et environnements virtuels.

Supprimer les fichiers corrompus ne fait que stopper l’attaque en cours ; l’urgence absolue réside dans la rotation intégrale des identifiants. L’avis est formel : « Dès lors que la version 1.107.0 a été installée sur un hôte relié à Internet, il faut considérer que l’exfiltration a abouti. » Le redémarrage d’un conteneur ou la purge de /tmp efface le fichier verrou, son absence ne garantit donc aucune sécurité. Il est indispensable de renouveler les rôles IAM de la machine, les variables d’environnement, les mots de passe de bases de données et chaque clé d’API de modèle stockée dans la passerelle, tout en vérifiant dans AWS CloudTrail l’historique des appels GetCallerIdentity durant l’incident. Les versions 1.106.2, 1.107.0rc1, 1.107.0rc2 et les installations strictement figées de litellm-proxy-extras==0.4.110 sont confirmées saines : seuls les déploiements aux dépendances flottantes ont payé le prix fort de cette attaque éclair.

Le terrain d’affrontement de la sécurité logicielle a basculé. Les pirates ne perdent plus des semaines à soumettre des pull requests sournoises ; ils collectent des jetons dans des logs exposés et laissent les pipelines officiels certifier leurs charges malveillantes. À l’heure où les assistants de programmation IA manipulent au quotidien des secrets critiques, traiter les logs des passerelles comme des données ultraconfidentielles et verrouiller chaque dépendance par empreinte cryptographique constituent les seuls remparts efficaces pour protéger l’infrastructure d’IA.

Liens de référence :

  • Rapport SafeDep sur la sécurité de la chaîne logicielle : Analyse du piratage de LiteLLM sur PyPI et de sa porte dérobée
  • Avis de sécurité officiel LiteLLM : Bilan de l’incident de sécurité du 21 octobre 2026
  • Fil de discussion Hacker News autour de la compromission de LiteLLM 1.107.0