Publication malveillante du paquet elementary-data sur PyPI : des identifiants cloud dérobés à des ingénieurs data
27 avril 2026
0 minutes de lectureUn paquet Python disponible sur PyPI, nommé elementary-data et téléchargé plus d’un million de fois par mois, a été victime d’une attaque de la chaîne d’approvisionnement exploitant un vecteur d’attaque GitHub Actions.
En bref
Avis de sécurité | |
Gravité | Critique (CVSS v4.0 : 9.3) |
Paquet concerné |
|
Versions saines | Toutes les versions sauf |
Type d’attaque | Chaîne d’approvisionnement (injection dans le CI/CD GitHub Actions, puis paquet volant des identifiants) |
Identifiants dérobés | Profils dbt, identifiants Snowflake/BigQuery/Redshift, clés AWS/GCP/Azure, jetons d’API, clés SSH, fichiers |
Périmètre | Le paquet CLI PyPI et une image Docker ont été compromis ; Elementary Cloud et le paquet dbt Elementary n’ont pas été affectés. |
Indicateur de détection |
|
Divulgation | 25–26 avril 2026 |
Qu’est-ce qu’elementary-data ?
elementary-data est un outil CLI d’observabilité des données natif de dbt. Les ingénieurs data et analytics l’utilisent pour surveiller l’état des pipelines, détecter les anomalies et suivre les échecs de tests dans les entrepôts de données tels que Snowflake, BigQuery, Redshift et Databricks. Le paquet est téléchargé environ 280 000 fois par semaine et plus de 1,1 million de fois par mois, ce qui en fait un outil de données largement adopté.
Le paquet s’intègre à la plupart des principales plateformes de données cloud, ce qui en faisait précisément une cible de choix. Un outil qui gère régulièrement des connexions à Snowflake, BigQuery et AWS lors de l’exécution du CI/CD est au contact d’un grand nombre d’identifiants précieux.
Déroulement de l’attaque
La compromission s’est déroulée en deux étapes : d’abord, la compromission du pipeline de publication, puis la publication d’un contenu malveillant conçu pour dérober d’autres identifiants. À noter que cet incident de sécurité lié à la compromission du paquet elementary-data constitue l’un des vecteurs les plus marquants exploités récemment par TeamPCP et d’autres acteurs malveillants.
Étape 1 : injection de script dans GitHub Actions
Le 24 avril 2026 à 22:10 UTC, un attaquant utilisant un compte GitHub créé deux jours auparavant (realtungtungtungsahur) a publié un commentaire spécialement conçu à cet effet sur la PR #2147 du dépôt elementary-data. Ce commentaire exploitait une faille d’injection de script dans .github/workflows/update_pylon_issue.yml, un workflow qui gérait les événements liés aux commentaires sur les problèmes et les PR.
Le bloc run: vulnérable insérait directement ${{ github.event.comment.body }} dans un script shell avant son analyse par bash. Comme cette expression est développée au moment du traitement du modèle de workflow plutôt que nettoyée en tant qu’argument de chaîne, l’injection de métacaractères shell ou de sous-commandes dans le corps du commentaire permet d’exécuter du code arbitraire sur le runner. Au déclenchement du workflow, la charge utile de l’attaquant s’est exécutée avec le GITHUB_TOKEN du dépôt dans son périmètre.
Point crucial : l’attaquant n’avait pas besoin d’un accès direct en écriture au dépôt. Le GITHUB_TOKEN à la disposition du runner avait suffisamment d’autorisations pour créer des commits, pousser des tags et déclencher d’autres workflows. La tâche handle_comment injectée est restée active pendant deux heures et quarante-six minutes, donnant à l’attaquant une longue fenêtre pour préparer chaque étape suivante.
À l’aide du jeton dérobé, l’attaquant a forgé un commit de publication dont le hash était b1e4b1f3aad0d489ab0e9208031c67402bbb8480. Le commit était conçu pour paraître automatisé et officiel : c’était un commit orphelin (inaccessible depuis toute branche), attribué à github-actions[bot], doté d’une fausse signature PGP « Verified » et accompagné du message release/v0.23.2 (#2188), copié mot pour mot d’une PR légitime fusionnée neuf jours plus tôt. L’attaquant a attribué le tag v0.23.3 à ce commit orphelin, puis déclenché le workflow Release package du dépôt avec tag=v0.23.3 comme entrée. L’étape de récupération du code de ce workflow utilisait ref: ${{ inputs.tag || github.ref }} ; la compilation s’est donc effectuée directement à partir du commit orphelin malveillant, sans toucher à master. Le pipeline CI/CD légitime a empaqueté et publié le code malveillant. À 22:20 UTC, elementary-data==0.23.3 était disponible sur PyPI. Quatre minutes plus tard, une image Docker compromise (ghcr.io/elementary-data/elementary:0.23.3 et :latest, empreinte sha256:31ecc5939de6d24cf60c50d4ca26cf7a8c322db82a8ce4bd122ebd89cf634255) a suivi.
Ce vecteur d’attaque est apparu à plusieurs reprises dans l’écosystème PyPI. L’attaque de la chaîne d’approvisionnement d’Ultralytics en décembre 2024 a utilisé le même schéma d’injection pull_request_target pour dérober des identifiants et publier quatre versions malveillantes. La compromission de LiteLLM début 2026 a suivi une voie légèrement différente (une action GitHub tierce piégée), mais le résultat était le même : des jetons PyPI dérobés utilisés pour publier un paquet volant des identifiants.
Étape 2 : le paquet malveillant
L’attaquant a intégré la charge utile malveillante dans un fichier nommé elementary.pth, inclus dans le répertoire site-packages du paquet.
Les fichiers .pth sont des fichiers de configuration des chemins Python que site.py, le module de démarrage de Python, traite automatiquement au lancement de l’interpréteur. Toute ligne d’un fichier .pth commençant par import est exécutée comme du code Python au démarrage de l’interpréteur, avant l’exécution de votre propre code. Le logiciel malveillant s’active donc à chaque démarrage de Python sur le système concerné, y compris lors des opérations pip install, et pas seulement lorsqu’un utilisateur importe explicitement elementary.
Cette technique a également été utilisée lors de la compromission de LiteLLM v1.82.8. Elle est plus persistante et plus difficile à détecter que l’intégration de code malveillant dans __init__.py, car elle ne nécessite pas que la victime importe le paquet piégé. Il suffit de l’installer.
Dans la charge utile : ce que faisait le logiciel malveillant
Le code intégré dans elementary.pth était un voleur d’identifiants comportant trois étapes de chiffrement : une enveloppe externe en base64, puis un chiffrement XOR utilisant un flux de clés MD5 (graine : swabag), suivi d’une seconde couche de déchiffrement XOR. L’obfuscation n’est pas sophistiquée au regard des normes actuelles en matière de logiciels malveillants, mais elle est délibérée : elle empêche la détection triviale de la charge utile par recherche de chaînes et allonge le temps nécessaire pour analyser le fonctionnement réel du paquet.
Une fois Python lancé sur une machine concernée, la charge utile décodée :
1. A récolté des identifiants et des secrets sur l’ensemble du système de fichiers, en ciblant un large éventail de données :
Profils dbt (
~/.dbt/profiles.yml) et identifiants d’entrepôts de données (Snowflake, BigQuery, Redshift, Databricks).Identifiants de fournisseurs cloud : identifiants AWS
~/.aws/credentialset identifiants de rôles actifs récupérés depuis le point de terminaison de métadonnées IMDSv2, avec des appels directs signés SigV4 à AWS Secrets Manager et SSM Parameter Store ;application_default_credentials.jsonpour GCP ; répertoires~/.azure/pour Azure.Clés privées SSH (
id_rsa, id_ed25519, ~/.git-credentials).Secrets de conteneurs et d’orchestration :
~/.docker/config.json,~/.kube/config,tous les/etc/kubernetes/*.conffichiers, jetons de comptes de service Kubernetes.Identifiants de gestionnaires de paquets :
~/.npmrc,~/.pypirc,~/.cargo/credentials.toml.Autres secrets stockés : fichiers
.env*(analyse jusqu’à six niveaux de répertoires),~/.vault-token,~/.netrc,~/.pgpass,~/.my.cnf, jetons d’API dans les variables d’environnement.Fichiers de portefeuilles de cryptomonnaies (Bitcoin, Litecoin, Dogecoin, Zcash, Dash, Monero, Ripple, Ethereum, Cardano et paires de clés de validateurs Solana).
Fichiers système :
/etc/passwd,/etc/shadow, historiques de shell,/var/log/auth.log.
2. A regroupé toutes les données recueillies dans une archive nommée trin.tar.gz, puis l’a exfiltrée à l’aide de curl --data-binary vers le serveur C2 igotnofriendsonlineorirl-imgonnakmslmao.skyhanni.cloud, avec l’en-tête HTTP X-Rise-To-The-Trinny: agree.
3. A laissé un fichier témoin à l’emplacement $TMPDIR/.trinny-security-update (Linux/macOS) ou %TEMP%\.trinny-security-update (Windows), indiquant que le logiciel malveillant s’était exécuté au moins une fois.
Les identifiants ciblés vont bien au-delà de dbt et des entrepôts de données. La charge utile est conçue pour passer au crible tous les secrets accessibles sur la machine, notamment les clusters Kubernetes, les gestionnaires de secrets d’infrastructure et les clés de cryptomonnaies. Les cibles dbt et entrepôts de données concernent directement les utilisateurs de l’outil, mais toute personne l’exécutant sur une machine de développement ou un runner CI risque de perdre bien davantage.
Le profil des identifiants ciblés correspond bien à celui des utilisateurs habituels de l’outil. Les ingénieurs data qui exécutent le CLI elementary-data l’utilisent presque certainement avec un entrepôt de données connecté et disposent d’identifiants de fournisseur cloud, souvent dans un environnement CI/CD où ces identifiants sont stockés sous forme de secrets ou de variables d’environnement. Il s’agit d’une attaque ciblée, pas d’une attaque par balayage générique.
Impact et périmètre
La fenêtre d’attaque s’étend du 24 avril à 22:20 UTC (date de mise en ligne du paquet sur PyPI) jusqu’à son retrait le 25 avril, entre 8:51 et 11:51 UTC, après que des membres de la communauté ont signalé le problème à 6:18 UTC. L’exposition a donc duré environ huit à dix heures.
Toute personne concernée par l’un des cas suivants doit considérer que le logiciel malveillant s’est exécuté et que ses identifiants ont été exfiltrés :
a exécuté
pip install elementary-dataou effectué une mise à niveau pendant cette période ;a utilisé une image Docker téléchargée depuis le registre elementary-data entre le 24 avril à 22:24 UTC et son retrait ;
ou disposait d’un pipeline CI/CD qui téléchargeait automatiquement la dernière version.
Elementary Cloud et le paquet dbt Elementary n’ont pas été affectés, et aucune autre version du CLI ne contenait le code malveillant.
Détection : êtes-vous concerné ?
Étape 1 : vérifiez la version installée
Si le résultat affiche Version: 0.23.3, votre environnement a été exposé.
Étape 2 : recherchez le marqueur d’exécution
Le logiciel malveillant crée un fichier témoin lors de son exécution :
La présence de ce fichier signifie que le code volant les identifiants s’est exécuté dans cet environnement. Son absence ne garantit pas que votre système est sain : le logiciel malveillant n’a peut-être pas créé le fichier témoin dans tous les scénarios d’exécution, ou le répertoire temporaire a pu être effacé.
Étape 3 : effectuez une vérification avec Snyk
Pour vérifier si vos dépendances Python contiennent ce paquet ou d’autres paquets connus comme malveillants ou vulnérables :
La base de données des vulnérabilités de Snyk comprend SNYK-PYTHON-ELEMENTARYDATA-16316110 et signalera tout environnement qui utilise encore la version 0.23.3.
Remarque : nous vous recommandons de consulter notre fiche pratique sur les bonnes pratiques de sécurité Python ainsi que notre article sur les bonnes pratiques de conteneurisation des applications Python avec Docker pour appliquer les recommandations de développement sécurisé.
Mesures correctives
1. Effectuez immédiatement la mise à niveau
La version 0.23.4, publiée le 25 avril 2026, ne contient aucun code malveillant.
Si vous utilisez un fichier requirements.txt ou pyproject.toml, mettez à jour la version spécifiée :
2. Renouvelez tous les identifiants susceptibles d’avoir été exposés
Considérez comme compromis tout identifiant accessible aux processus Python sur les machines concernées. En particulier :
Profils dbt : renouvelez les mots de passe d’entrepôt de données et les jetons OAuth dans
~/.dbt/profiles.yml.Clés de fournisseurs cloud : renouvelez ou révoquez les clés IAM AWS (et vérifiez les valeurs consultées dans Secrets Manager et SSM Parameter Store), les clés de comptes de service GCP et les principaux de service Azure.
Kubernetes : renouvelez les jetons de comptes de service et vérifiez tous les fichiers
/etc/kubernetes/*.confqui étaient accessibles.Registres de conteneurs : renouvelez les identifiants stockés dans
~/.docker/config.json.Jetons de gestionnaires de packages : faites tourner les jetons de
~/.npmrc,~/.pypircet~/.cargo/credentials.toml.Gestionnaires de secrets : faites tourner les jetons HashiCorp Vault (
~/.vault-token) ainsi que tous les identifiants.netrc,.pgpassou.my.cnf.Clés SSH : si des clés privées se trouvaient sur la machine, considérez-les comme compromises et remplacez-les.
Secrets CI/CD : si la machine concernée était un runner CI, faites tourner tous les secrets stockés dans cet environnement.
La rotation ne suffit pas. Consultez les journaux d’accès pour repérer le domaine d’exfiltration igotnofriendsonlineorirl-imgonnakmslmao.skyhanni.cloud ainsi que les journaux de tous vos services afin de détecter tout accès non autorisé qui aurait déjà pu se produire.
3. Videz les caches Python
4. Récupérez des images Docker saines
L’image compromise (ghcr.io/elementary-data/elementary:0.23.3 et :latest) avait pour digest sha256:31ecc5939de6d24cf60c50d4ca26cf7a8c322db82a8ce4bd122ebd89cf634255. La dernière image connue comme saine est 0.23.2, avec le digest sha256:b3bbfafde1a0db3a4d47e70eb0eb2ca19daef4a19410154a71abee567b35d3d9. Récupérez une image saine créée après le 25 avril 2026 :
Vérifiez que vous n’utilisez pas une copie en cache de l’image compromise :
5. Auditez vos workflows GitHub Actions
Si vous gérez des packages Python, cet incident vous invite à auditer tous les workflows qui traitent des événements liés aux commentaires de problèmes ou de PR. Recherchez en particulier les expressions de contexte non mises entre guillemets et directement interpolées dans des blocs run: :
L’article de Snyk sur les vulnérabilités de GitHub Actions et l’analyse de la compromission de TJ Actions de Snyk présentent les schémas plus généraux à surveiller.
Au-delà de la désinfection des entrées, la solution la plus durable consiste à supprimer complètement les jetons API PyPI à longue durée de vie des secrets de vos workflows. PyPI prend en charge les Trusted Publishers, qui utilisent des jetons OIDC éphémères limités à un workflow précis dans un dépôt précis — des jetons qui ne peuvent être ni exfiltrés ni réutilisés. L’attaquant ciblant elementary-data avait besoin d’un secret à longue durée de vie pour publier ; Trusted Publishers élimine cette surface d’attaque. Protégez également les workflows de publication à privilèges par des étapes d’approbation manuelle exigeant une confirmation humaine avant le lancement de la publication.
Un schéma récurrent
Cette attaque suit une méthode désormais bien connue : repérer une faille dans la configuration GitHub Actions d’un projet, injecter du code pour dérober le jeton de publication PyPI, utiliser ce jeton pour publier une version malveillante, puis intégrer un fichier .pth ou un mécanisme similaire au démarrage pour maximiser la portée de l’attaque.
Le même schéma s’est retrouvé dans l’attaque contre Ultralytics (décembre 2024, injection de branche via pull_request_target, mineur de cryptomonnaie), l’attaque contre LiteLLM (début 2026, action Trivy empoisonnée, voleur d’identifiants doté d’une porte dérobée persistante) et l’incident Cline/Clinejection (injection de prompt assistée par l’IA dans Actions, jetons dérobés).
Ce schéma n’a rien de nouveau. Les outils permettant de l’exploiter sont bien connus et semblent utilisés activement, à plusieurs reprises. Pour les mainteneurs de packages, la priorité est de renforcer les workflows : limitez the use of pull_request_target, exigez une approbation manuelle pour les workflows de publication, utilisez des jetons OIDC éphémères pour publier sur PyPI plutôt que des jetons API à longue durée de vie et configurez des règles de protection des branches pour empêcher les déclenchements non autorisés de publications.
Pour les utilisateurs de elementary-data, l’équipe Elementary a réagi rapidement : moins de quatre heures se sont écoulées entre le signalement de la communauté et les premières mesures correctives, et l’équipe a publié un rapport d’incident complet. Cette réactivité mérite d’être soulignée, au même titre que l’incident lui-même.
Sécurisez votre chaîne d’approvisionnement avec Snyk
87 % des personnes interrogées ont été touchées par des problèmes de sécurité de la chaîne d’approvisionnement. Protégez la vôtre avec Snyk.



