Comment un scanner de sécurité piégé a permis de compromettre LiteLLM
24 mars 2026
0 minutes de lectureLe 24 mars 2026, deux versions du package Python litellm sur PyPI se sont révélées contenir du code malveillant. Les packages (versions 1.82.7 et 1.82.8) ont été publiés par un acteur malveillant connu sous le nom de TeamPCP, qui avait obtenu les identifiants PyPI de la personne chargée de la maintenance à la suite d’une compromission antérieure de Trivy, un scanner de sécurité open source utilisé dans le pipeline CI/CD de LiteLLM.
Les versions malveillantes ont été disponibles pendant environ trois heures, avant que PyPI ne mette le package en quarantaine. LiteLLM est téléchargé environ 3,4 millions de fois par jour.
Snyk suit cet incident. Si vous êtes client de Snyk, vous avez peut-être déjà vu l’alerte affichée dans l’application et reçu une notification par e-mail. La fiche de vulnérabilité est SNYK-PYTHON-LITELLM-15762713 et les mises à jour sur la situation sont publiées dans le Snyk Trust Center.
En bref
Package concerné |
|
Versions concernées | 1.82.7, 1.82.8 |
Versions sûres | ≤ 1.82.6 |
Identifiant Snyk | |
Première détection | 10:39 UTC, 24 mars 2026 (publication de la version 1.82.7) |
Mise en quarantaine sur PyPI | \~13:38 UTC, 24 mars 2026 |
Attaquant | TeamPCP (également connu sous les noms PCPcat, Persy_PCP, ShellForce et DeadCatx3) |
Vecteur d’attaque | Chaîne d’approvisionnement : identifiants de publication PyPI compromis via une GitHub Action Trivy piégée dans le CI/CD de LiteLLM |
Type de charge utile | En trois étapes : vol d’identifiants + exfiltration chiffrée + porte dérobée persistante + ver Kubernetes |
Domaine d’exfiltration |
|
MITRE ATT\&CK | T1546.018 (hooks de démarrage Python), T1003 (vol d’identifiants), T1610 (déploiement de conteneur) |
Principaux événements
Heure (UTC) | Preuve | Événement |
|---|---|---|
Fin février 2026 |
| |
19 mars, 17:43 UTC | Les tags de la GitHub Action Trivy | |
23 mars, 12:58 UTC | Endor Labs (métadonnées PyPI capturées avant suppression) | La GitHub Action Checkmarx KICS est compromise ; le domaine C2 |
24 mars, 10:39 UTC | Endor Labs (métadonnées PyPI capturées avant suppression) | La version malveillante |
24 mars, 10:52 UTC | La version malveillante | |
24 mars, 11:48 UTC | FutureSearch (Callum McMahon) ouvre un ticket de divulgation | |
24 mars, 12:36 UTC | Un fil de discussion est publié sur HN ; il atteint 324 points | |
24 mars, \~12:44 UTC | Issue GitHub n° 24512 (visible dans les horodatages des commentaires) | Des bots inondent l’issue n° 24512 de commentaires ; l’issue est fermée à l’aide du compte compromis de la personne chargée de la maintenance |
24 mars, 13:03 UTC | FutureSearch (mise à jour horodatée) | FutureSearch confirme la fermeture de l’issue et le spam des bots |
24 mars, 13:48 UTC | Une nouvelle issue de suivi, sans contenu malveillant, est ouverte | |
24 mars, 15:09 UTC | La personne chargée de la maintenance de LiteLLM confirme que toutes les clés GitHub, Docker et PyPI ont été renouvelées et que les comptes de maintenance ont migré vers de nouvelles identités | |
24 mars, 15:27 UTC | Les versions compromises sont supprimées ; le package sort de quarantaine sur PyPI |
Comment l’incident a été découvert
Callum McMahon, de FutureSearch, testait un plugin MCP de Cursor qui incluait litellm comme dépendance transitive. Peu après le démarrage de Python, son ordinateur a cessé de répondre à cause d’une saturation de la mémoire vive. Il a remonté le problème jusqu’au package litellm récemment installé et découvert litellm_init.pth, un fichier de 34 628 octets dans site-packages/, encodé deux fois en base64.
La saturation de la mémoire vive était un effet secondaire de la charge utile, et non une fonctionnalité intentionnelle. Le mécanisme .pth s’exécute à chaque démarrage de l’interpréteur Python. Comme la charge utile lance un nouveau sous-processus Python, et que ce nouveau processus déclenche lui aussi l’exécution de .pth, le résultat a été une bombe à forks involontaire. McMahon a publié ses conclusions sur futuresearch.ai ; en moins d’une heure, l’information s’était propagée sur r/LocalLLaMA, r/Python et Hacker News.
La chaîne d’attaque
L’attaque contre LiteLLM a commencé cinq jours plus tôt avec Trivy.
19 mars : Les attaquants ont modifié les tags Git du dépôt de la GitHub Action trivy-action pour qu’ils pointent vers une version malveillante (v0.69.4) contenant la même charge utile de vol d’identifiants et la même infrastructure d’exfiltration que celles utilisées lors des opérations ultérieures. (Pour en savoir plus sur la compromission de Trivy, consultez l’analyse par Snyk de la compromission de la chaîne d’approvisionnement de la GitHub Action Trivy.)
23 mars : La même infrastructure a été utilisée lors d’une attaque distincte contre Checkmarx KICS (Keep Infrastructure as Code Secure). Le domaine C2 checkmarx.zone, qui usurpe l’identité de l’entreprise de sécurité Checkmarx, a été enregistré et activé dans le cadre de cette opération.
24 mars : Le pipeline CI/CD de LiteLLM a exécuté Trivy dans le cadre de son processus de compilation, en le récupérant via apt sans version verrouillée. L’action compromise a exfiltré le jeton PYPI_PUBLISH depuis l’environnement du runner GitHub Actions. Grâce à cet identifiant, les attaquants ont publié les versions litellm 1.82.7 à 10:39 UTC et 1.82.8 à 10:52 UTC, chacune contenant des charges utiles malveillantes.

Première réponse de la personne chargée de la maintenance de LiteLLM sur Hacker News.
Deux mécanismes de distribution
Les deux versions malveillantes ont utilisé des techniques d’injection différentes :
1.82.7 (injection dans le code source) : la charge utile était encodée en base64 et intégrée directement dans litellm/proxy/proxy_server.py. Elle s’exécute dès qu’un élément importe litellm.proxy, le chemin d’importation standard du mode serveur proxy de LiteLLM.
1.82.8 (.pth file) : cette version ajoutait litellm_init.pth à site-packages/. Le mécanisme .pth s’exécute à chaque démarrage de l’interpréteur Python, sans nécessiter d’importation, notamment au lancement de pip, de python -c ou du serveur de langage Python d’un IDE.
Le fichier .pth est correctement déclaré dans le fichier RECORD de la wheel ; il passe donc la vérification d’empreinte de pip. Le package satisfait à tous les contrôles d’intégrité standard, car le contenu malveillant a été publié à l’aide d’identifiants légitimes, et non injecté après coup. Snyk Learn propose un guide détaillé sur le fonctionnement de ce type d’attaque dans Compromise of a Legitimate Package.
Les responsables de CPython ont reconnu le risque de sécurité des hooks de démarrage .pth dans l’issue n° 113659, mais aucun correctif n’a été appliqué. Cette technique correspond à la technique MITRE ATT\&CK T1546.018 (hooks de démarrage Python).
La charge utile en trois étapes
Étape 1 : collecte d’informations
Le script collecte les éléments suivants :
Données système : nom d’hôte, variables d’environnement, processus en cours, routage réseau, journaux d’authentification récents de
/var/log/auth.logIdentifiants : clés privées SSH, fichiers
.env, identifiants Git, clés d’API issues de l’historique du shell, jetons webhook Slack et Discord, configurations CI/CD (Jenkins, Travis CI, Terraform), hachages de mots de passe issus de/etc/shadowIdentifiants cloud : identifiants et configuration AWS, fichiers de comptes de service GCP et Azure, métadonnées cloud récupérées par des requêtes IMDS directes, AWS Secrets Manager et SSM Parameter Store (avec implémentation complète de la signature IMDSv2 dans le script)
Conteneurs et orchestration : fichier Docker
config.json(identifiants de registre), fichiers Kuberneteskubeconfig, jetons de compte de service, secrets et certificatsCryptomonnaies : fichiers de portefeuille et phrases de récupération Bitcoin, Ethereum, Solana, Cardano et Monero, configurations de portefeuilles matériels Ledger

Extrait du code malveillant décodé, montrant l’itération structurée et la collecte de secrets dans différents répertoires
Étape 2 : chiffrement et exfiltration
Les données collectées sont chiffrées avant leur transmission :
Une clé de session AES-256 de 32 octets est générée avec
openssl randLes données sont chiffrées avec AES-256-CBC (dérivation de clé PBKDF2)
La clé de session est chiffrée avec une clé publique RSA de 4 096 bits codée en dur (rembourrage OAEP)
Le tout est regroupé dans
tpcp.tar.gzL’archive est envoyée par requête POST à
https://models.litellm.cloud/à l’aide decurl
models.litellm.cloud est distinct du domaine légitime litellm.ai et a été enregistré le 23 mars, la veille de la publication des packages malveillants.
La clé publique RSA utilisée dans cette charge utile est identique à celle découverte dans les charges utiles de Trivy et KICS. Les chercheurs de Wiz ont confirmé cette correspondance pour les trois opérations, ce qui constitue le lien technique d’attribution le plus probant.
Fichiers temporaires créés pendant l’exfiltration : session.key, payload.enc, session.key.enc, tpcp.tar.gz dans le répertoire temporaire du système.

Vue neutralisée du code malveillant. Le nom de l’archive tpcp.tar.gz est un marqueur commun aux opérations Trivy, KICS et LiteLLM.
Étape 3 : persistance et déplacement latéral
Persistance locale :
Écrit un script Python de porte dérobée dans
~/.config/sysmon/sysmon.py(ou/root/.config/sysmon/sysmon.py)Crée un service systemd utilisateur à l’emplacement
~/.config/systemd/user/sysmon.service, avec la description « System Telemetry Service »L’enregistre et l’active :
systemctl --user daemon-reload && systemctl --user enable --now sysmon.service
Le script de persistance interroge https://checkmarx.zone/raw toutes les 5 minutes pour récupérer une URL, télécharge le fichier dans /tmp/pglog, le rend exécutable et l’exécute en arrière-plan. L’état est suivi dans /tmp/.pg_state. Au moment de l’analyse, le point de terminaison renvoie des URL YouTube ; l’opérateur peut à tout moment passer à la distribution de charges utiles actives.
Déplacement latéral dans Kubernetes : Si le script trouve un jeton de compte de service Kubernetes au chemin de montage standard, il lit tous les secrets de tous les espaces de noms. Il tente ensuite de déployer un pod privilégié sur chaque nœud dans kube-system à l’aide de alpine:latest. Ces pods montent le système de fichiers de l’hôte et installent la porte dérobée sysmon sur le nœud sous-jacent.
Les pods malveillants sont nommés node-setup-{node_name} (nom du nœud tronqué à 35 caractères) et contiennent un conteneur nommé setup.
À propos de TeamPCP
TeamPCP (également connu sous les noms PCPcat, Persy_PCP, ShellForce et DeadCatx3, selon le Wiz Threat Center) est actif depuis au moins décembre 2025. L’acteur gère les chaînes Telegram @Persy_PCP et @teampcp, et intègre la chaîne « TeamPCP Cloud stealer » dans ses charges utiles. Wiz a suivi l’ensemble de la campagne (Wiz Threat Center ; blog de Wiz ; ramimac.me).
La compromission de LiteLLM constitue la phase 09 d’une campagne en cours. L’infrastructure est cohérente dans toutes les opérations : même paire de clés RSA, même nom d’archive tpcp.tar.gz et dépôts GitHub préfixés par tpcp-docs utilisés comme dépôts C2 de transit. Les trois domaines de cette opération partagent le même bureau d’enregistrement (Spaceship, Inc.) et le même hébergeur (DEMENIN B.V.).
L’acteur a également déployé CanisterWorm, qui utilise l’Internet Computer Protocol (ICP) comme canal C2. Les canisters ICP ne peuvent pas être mis hors service par des bureaux d’enregistrement de domaines ou des hébergeurs. Les chercheurs en sécurité d’Aikido décrivent cela comme le premier recours observé à ICP comme mécanisme C2 dans une campagne visant une chaîne d’approvisionnement.
Un composant appelé hackerbot-claw utilise un agent IA (openclaw) pour automatiser le ciblage des attaques. Les chercheurs d’Aikido ont documenté ce cas comme l’un des premiers exemples d’utilisation opérationnelle d’un agent IA dans une attaque visant une chaîne d’approvisionnement.
Suppression de l’issue
Lorsque des membres de la communauté ont commencé à signaler la compromission dans l’issue GitHub n° 24512, les attaquants ont publié 88 commentaires de bots depuis 73 comptes distincts en 102 secondes (de 12 h 44 à 12 h 46 UTC). Les comptes utilisés étaient des comptes de développeurs précédemment compromis, et non des profils créés pour l’occasion. L’analyse de Rami McCarthy a révélé que 76 % des comptes se recoupaient avec le botnet utilisé lors de la divulgation concernant Trivy.
À l’aide du compte de mainteneur compromis krrishdholakia, les attaquants ont fermé l’issue n° 24512 en la marquant « not planned » et ont effectué des commits dans des dépôts sans rapport avec l’incident, avec le message « teampcp update ».
La communauté a ouvert une issue de suivi parallèle (n° 24518) et poursuivi la discussion sur Hacker News, où le fil a atteint 324 points.
Impact confirmé
Les versions concernées étaient disponibles sur PyPI pendant environ 3 heures. Les projets suivants ont soumis des PR de sécurité ou ouvert des issues le 24 mars afin d’éviter les versions 1.82.7 et 1.82.8 :
Projet | Éléments de preuve |
|---|---|
DSPy | PR n° 9498 fusionnée ; rapport d’échec de CI |
MLflow | PR n° 21971 fusionnée |
OpenHands | |
CrewAI | PR n° 5040 (dissociée de litellm) ; PR n° 5039 |
langwatch | |
strands-agents/sdk-python | |
Arize Phoenix | |
nanobot | |
dreadnode/rigging | |
CoPaw | |
Aider | Sûr confirmé (épinglé sur |
Le mécanisme .pth se déclenche au démarrage de tout processus Python, y compris pip. Dans les environnements CI/CD, la charge utile peut donc s’exécuter pendant les étapes de build, et pas seulement à l’exécution de l’application.
Détection : êtes-vous concerné ?
Étape 1 : vérifiez la version installée
Si le résultat indique 1.82.7 ou 1.82.8, considérez le système comme compromis et suivez les instructions de correction ci-dessous. Ne vous contentez pas de mettre à niveau : la charge utile a peut-être déjà été exécutée.
Étape 2 : recherchez des éléments de persistance
Étape 3 : recherchez des fichiers .pth malveillants
Étape 4 : vérifiez les hachages des fichiers
Étape 5 : recherchez des indicateurs réseau
Étape 6 : vérifiez Kubernetes
Étape 7 : lancez une analyse avec Snyk

Bannière d’alerte dans l’application Snyk, avec les dépôts concernés affichés dans l’inventaire des actifs. Le lien Trust Center permet d’accéder à l’état de l’incident en temps réel.
Les clients Snyk peuvent également consulter Snyk Advisor pour le package litellm et la fiche complète de vulnérabilité SNYK-PYTHON-LITELLM-15762713.
Correction
Si vous n’avez PAS installé la version 1.82.7 ou 1.82.8 :
Épinglez la version sur <=1.82.6 jusqu’à la disponibilité d’une version saine :
Si vous AVEZ installé la version 1.82.7 ou 1.82.8 :
La charge utile s’exécute au démarrage de Python, y compris lors de pip install. Considérez le système comme potentiellement compromis, que vous ayez exécuté du code applicatif ou non.
Supprimez les éléments de persistance :
Faites tourner les identifiants sur le système concerné :
Clés privées SSH : générez de nouvelles clés et révoquez les anciennes dans
authorized_keys, GitHub et GitLabIdentifiants cloud : clés d’accès AWS, clés de compte de service GCP, principaux de service Azure
Clés API : fichiers
.env, variables d’environnement du shell, secrets CI/CDIdentifiants de registre Docker :
~/.docker/config.jsonKubernetes :
~/.kube/config, jetons de compte de service dans le clusterMots de passe de base de données figurant dans les fichiers de configuration du système
Identifiants Git stockés dans
~/.gitconfigou dans le gestionnaire d’identifiants du systèmePhrases de récupération de portefeuilles de cryptomonnaies
Auditez AWS Secrets Manager et SSM Parameter Store, car la charge utile les interroge directement si les métadonnées d’instance sont accessibles.
Auditez les secrets du cluster Kubernetes. Si un jeton de compte de service était présent, tous les secrets de tous les espaces de noms ont peut-être été lus. Recherchez les pods
node-setup-*danskube-system.
Installez une version saine dans un nouvel environnement au lieu de mettre à niveau l’installation existante :
Pourquoi la vérification des hachages par pip n’a rien détecté
La vérification des hachages confirme qu’un fichier correspond à celui annoncé par PyPI, mais ne permet pas de savoir si le contenu annoncé est malveillant.
Le fichier litellm_init.pth de la version 1.82.8 est correctement déclaré dans le fichier RECORD de la wheel, avec un hachage correspondant. pip install --require-hashes aurait abouti. Le package passe tous les contrôles d’intégrité habituels, car le contenu malveillant a été publié à l’aide d’identifiants légitimes : aucun hachage ne diffère, aucun domaine suspect n’est détecté et le nom du package ne comporte aucune faute.
La seule façon de détecter le problème lors de l’installation consiste à vérifier si un package installe des fichiers .pth et si ces fichiers contiennent des motifs tels que subprocess, base64 ou exec. Aucun plugin pip largement déployé ne le fait automatiquement à l’heure actuelle.
La tendance générale
Dans cette campagne, les cibles sont des outils qui disposent d’un accès étendu aux pipelines automatisés : un scanner de conteneurs (Trivy), un outil d’analyse de l’infrastructure (KICS) et une bibliothèque de routage de modèles d’IA (LiteLLM). Par conception, chacun de ces outils doit disposer d’un accès étendu aux systèmes sur lesquels il intervient (identifiants, configurations, variables d’environnement).
LiteLLM est de plus en plus déployé comme passerelle LLM centralisée stockant les identifiants API de plusieurs fournisseurs de modèles. Dans cette configuration, les identifiants accessibles depuis un seul hôte compromis sont plus nombreux que dans le cas d’une application classique.
La divulgation initiale s’est propagée dans les communautés de développeurs en IA (r/LocalLLaMA, r/Python, Hacker News) plutôt que par les canaux de sécurité traditionnels tels que r/netsec ou les flux CVE.
Pour en savoir plus sur les risques de la chaîne d’approvisionnement propres aux outils LLM, consultez l’article de Snyk Learn Vulnérabilités de la chaîne d’approvisionnement des LLM. L’attaque de la chaîne d’approvisionnement Ultralytics AI Pwn Request de 2024 constitue également un point de comparaison utile : une autre bibliothèque Python d’IA largement utilisée, compromise au moyen d’une faille CI/CD, selon une chaîne d’attaque similaire.
Indicateurs de compromission
Hachages de fichiers :
Fichier | SHA-256 |
|---|---|
|
|
|
|
|
|
Réseau :
Exfiltration :
https://models.litellm.cloud/(POST)Interrogation C2 :
https://checkmarx.zone/raw(GET)
Système de fichiers :
~/.config/sysmon/sysmon.pyou/root/.config/sysmon/sysmon.py~/.config/systemd/user/sysmon.service(description : « System Telemetry Service »)/tmp/tpcp.tar.gz,/tmp/session.key,/tmp/payload.enc,/tmp/session.key.enc/tmp/.pg_state,/tmp/pglog
Kubernetes :
Pods :
node-setup-{node_name}danskube-systemNom du conteneur :
setup, image :alpine:latest
Préfixe de la clé publique RSA (codé en dur dans les charges utiles des trois opérations) :
Mesures à prendre immédiatement
Vérifiez votre version de litellm :
pip show litellm | grep VersionÉpinglez la version sur
<=1.82.6dans tous les environnementsExécutez
snyk test --package-manager=pipSi la version 1.82.7 ou 1.82.8 était installée : faites tourner les identifiants et recherchez des éléments de persistance
Auditez les pipelines CI/CD pour repérer les versions d’outils non épinglées, notamment dans GitHub Actions
Vérifiez Kubernetes :
kubectl get pods -A | grep node-setup-
LIVRE BLANC
La crise de la sécurité de l’IA dans votre environnement Python
Avec l’accélération fulgurante du développement, savez-vous vraiment à quoi votre environnement d’IA peut accéder ?
