Principaux enseignements
- Un fichier malveillant
build.rsle script s'exécute pendantcargo buildavec les privilèges du processus qui a lancé Cargo. - Stable Cargo ne propose aucun moyen officiel de compiler une arborescence de dépendances tout en refusant d'exécuter ses scripts de compilation.
- Six des 34 paquets malveillants de TrapDoor ont été détectés sur crates.io, contre 21 sur npm et 7 sur PyPI.
- npm et pnpm bloquent désormais par défaut les scripts d'installation de dépendances non approuvés, tandis que la version stable de Cargo ne dispose pas d'équivalent à l'échelle des dépendances.
Les développeurs de Rust entendent depuis des années que ce langage est sûr. Sécurité de la mémoire, absence de conflits d’accès dans le code sécurisé, un système de types qui détecte à la compilation ce que d’autres langages ne repèrent qu’à l’exécution. Rien de tout cela ne concerne le code qui s’exécute avant même que votre programme n’existe. En mai 2026, une campagne visant la chaîne d’approvisionnement logicielle, baptisée TrapDoor a publié 34 paquets malveillants répartis sur plus de 384 versions sur npm, PyPI et crates.io. Aucun d’entre eux n’attendait que vous appeliez le paquet. Ils se déclenchaient lors de l’installation, de l’importation ou de la compilation. Six de ces 34 paquets se trouvaient sur crates.io, déguisés en outils Sui et Move, utilisant build.rs pour rechercher les fichiers de clés des portefeuilles cryptographiques. L'auteur d'une crate peut désactiver son propre script de compilation dans le manifeste. En tant qu'utilisateur, vous ne disposez d'aucun commutateur générique permettant de désactiver les scripts de compilation des dépendances dans l'arborescence. C'est cette lacune que traite cet article.
Ce qu'a fait TrapDoor dans trois registres
npm, PyPI et crates.io n’ont pas été touchés de la même manière. Sur les 34 paquets identifiés, 21 se trouvaient sur npm et utilisaient des scripts post-installation pour exécuter une charge utile commune visant à collecter des identifiants dès qu’un utilisateur en installait un. Sept se trouvaient sur PyPI et s’exécutaient lors de l’importation pour récupérer une charge utile JavaScript distante et l’exécuter localement. Seuls six ont été détectés sur crates.io, utilisant des scripts de compilation Rust qui s’exécutaient pendant la compilation. C’est ce schéma qui caractérise cette campagne. Dans chaque écosystème, l’attaquant a exploité le point d’ancrage d’exécution que cet écosystème lui offrait déjà.
| Registre | Paquets nommés | Hook d'exécution | Ce qu'il a fait |
|---|---|---|---|
| npm | 21 | Scripts de cycle de vie post-installation | Ran trap-core.js, un outil de collecte d'identifiants partagés permettant des mouvements latéraux via SSH |
| PyPI | 7 | Exécution lors de l'importation | A récupéré et exécuté une charge utile JS à distance via node -e |
| crates.io | 6 | build.rs scripts de compilation |
J'ai recherché les fichiers de clés des portefeuilles Sui et Aptos, je les ai chiffrés à l'aide de l'algorithme XOR, puis je les ai transférés vers GitHub Gists. |
Les six paquets de crates.io – move-analyzer-build, move-compiler-tools, move-project-builder, sui-framework-helpers, sui-move-build-helper, et sui-sdk-build-utils – s'adressaient spécifiquement aux développeurs Sui et Move plutôt qu'à l'ensemble de la communauté Rust. C'est le côté npm qui a essuyé l'essentiel des dégâts, tant en termes de nombre de paquets que de sophistication des charges utiles. Le côté crates.io reste toutefois le plus important pour les ingénieurs Rust, car il met en évidence une build.rs script qui fait exactement ce qu'un build.rs C'est le rôle de ce script. Il s'exécute en arrière-plan, avec les privilèges du processus de compilation, avant même que l'application elle-même n'ait eu l'occasion de se lancer.
Pourquoi build.rs est une meilleure solution qu'un script post-installation

build.rs Il s'agit d'un programme Rust que Cargo compile et exécute avant de compiler la crate à laquelle il appartient, avec les privilèges du processus exécutant Cargo. Son existence se justifie par de bonnes raisons, telles que la génération de code et la liaison native, et de nombreuses crates légitimes en intègrent une. Toute dépendance dont le script de construction s’exécute peut exécuter du code arbitraire avec les autorisations dont dispose le processus Cargo. TrapDoor illustre ce phénomène lorsqu’une personne l’utilise délibérément. Les six crates dont les noms ressemblent à ceux des outils de construction Sui et Move utilisés build.rs pour rechercher sur un ordinateur les fichiers de stockage de clés de portefeuille dès qu'un utilisateur a lancé cargo build. Le script chiffrait par XOR tout ce qu’il trouvait et l’envoyait vers GitHub Gists. Pas de hook d’installation distinct, pas d’importation, il suffisait simplement de compiler un projet qui dépendait justement de l’un des six noms de paquets. Les macros procédurales constituent l’autre voie d’exécution à la compilation dans Rust, mais TrapDoor n’en avait pas besoin. Une macro « proc » s’exécute au sein du compilateur lors de l’expansion des macros, ce qui signifie qu’elle ne se déclenche que lorsqu’un élément l’invoque effectivement. Un script de compilation se déclenche simplement parce que la crate figure dans le graphe de compilation, ce qui en fait justement l’option la plus fiable pour un attaquant.
| Mécanisme | Se déroule pendant | Contrôle général du gestionnaire de paquets |
|---|---|---|
| script de cycle de vie npm | Installer | Oui, bloqué par défaut depuis la version 12 de npm |
| Exécution lors de l'importation en Python | Importation de paquets | Non |
Rouille build.rs |
Compilation | Non |
| Macro procédurale Rust | Expansion macroéconomique | Non |
Cargo ne propose pas de commutateur stable et polyvalent permettant à un utilisateur de compiler un arbre de dépendances tout en refusant les scripts de construction des dépendances ou l'expansion des macros. Il existe toutefois une exception très limitée. Lorsqu'une crate déclare un links key, vous pouvez remplacer son script de compilation depuis la configuration Cargo en fournissant vous-même les métadonnées ; ce script ne sera alors ni compilé ni exécuté. Cela concerne les shims de bibliothèques natives, et non une dépendance quelconque qui aurait décidé d'inclure un script de compilation. cargo build --offline empêche Cargo d'effectuer des requêtes réseau et un commit Cargo.lock ces versions sont fixées, mais cela n'empêche pas l'exécution d'un script de compilation déjà présent dans votre arborescence, et cela ne sert à rien si la version fixée dans votre fichier de verrouillage est justement celle qui est malveillante.
Où s'exécute un script de compilation et ce qu'il peut atteindre

cargo build contient généralement les identifiants d’accès aux services cloud, les jetons de registre, les droits d’accès au registre de conteneurs et, parfois, des clés de signature. L’ordinateur portable d’un développeur peut contenir la plupart de ces éléments, ainsi que des clés SSH, les données de session du navigateur et le code source des projets qui n’ont pas encore été déployés. C’est précisément ce qui fait qu’un hôte de compilation vaut la peine d’être compromis. Sakshi Grover, responsable de recherche senior chez IDC Asia Pacific Cybersecurity Services, a déclaré à CSO Online après que TrapDoor a révélé qu’« un seul poste de travail compromis peut discrètement devenir un point d’entrée dans les pipelines CI/CD ». Un script de compilation récupérant des identifiants constitue l’un des moyens d’y parvenir, et l’extraction directe des fichiers de clés de portefeuille depuis le système de fichiers lors d’une compilation non surveillée est précisément ce qu’a fait la branche crates.io de TrapDoor.
La partie consacrée aux assistants de codage basés sur l'IA

build.rs astuce. Elle provenait de l'injection de code dans npm .cursorrules et CLAUDE.md fichiers utilisés à des fins de persistance, contenant les instructions malveillantes caché derrière un caractère Unicode de largeur nulle, ainsi que le même agresseur Création de pull requests sur GitHub contre des projets d'IA et d'outils de développement sans rapport entre eux. Ces deux types de noms de fichiers constituent des conventions que les outils de codage basés sur l'IA interprètent pour comprendre une base de code ; ainsi, un fichier corrompu constitue une injection indirecte de prompt. Le texte de l'attaquant se retrouve dans un fichier que l'assistant traite comme une instruction plutôt que comme des données. La condition préalable est toutefois plus restrictive que l'hypothèse selon laquelle « tous les outils d'IA sont compromis ». La réussite de cette technique dépend des fichiers d'instructions qu'un assistant donné détecte, considère comme fiables et sur lesquels il agit. Cela vaut néanmoins la peine d'être fait. Recherchez les fichiers d’instructions de l’assistant partout ailleurs que dans la racine de votre référentiel, et considérez par défaut comme suspect tout résultat trouvé dans une dépendance de fournisseur ou de chemin d’accès. Cela fonctionne comme un contrôle d’intégration continue (CI), à condition d’inverser le code de sortie afin que la détection d’un élément entraîne l’échec de la compilation plutôt que sa réussite.
if find . -name 'CLAUDE.md' -o -name '.cursorrules' \
| grep -vE '^\./(CLAUDE\.md|\.cursorrules)$' | grep -q .; then
echo "Unexpected AI assistant instruction file found"
exit 1
fi
Cet objectif diffère de celui consistant à tromper un réviseur humain. « Empoisonner » un fichier au moment de la persistance signifie que l’effet ne se fait sentir que plus tard, lorsque l’assistant consulte à nouveau le projet et traite ce fichier comme une instruction légitime. La solution réside principalement dans la définition du périmètre. En restreignant l’ensemble des fichiers d’instructions auxquels l’assistant fait confiance, on réduit la surface d’attaque, en particulier lorsque les chemins contrôlés par les dépendances sont exclus.
Ce que l'on retire réellement en tirant sur une caisse
Le registre crates.io fonctionne d'une manière inhabituelle, mais globalement positive. Les versions publiées sont immuables et ne peuvent pas être supprimées, mais seulement « retirées ». Cela empêche toute une catégorie d'attaques dont souffrent les registres modifiables. Cela signifie également qu'un « retrait » constitue une mesure corrective moins efficace que ce que la plupart des gens imaginent.
| Action | Effet |
|---|---|
| Télécharger une version | Les nouvelles résolutions ne l'utilisent pas, tandis que les fichiers de verrouillage existants continuent de s'en servir |
| Supprimer toutes les versions | Le nom de la caisse reste réservé, le code reste téléchargeable |
| Supprimer une version | Ce n'est pas possible sur crates.io |
Une version supprimée déjà enregistrée dans une branche validée Cargo.lock reste en service. C'est un choix délibéré, afin d'éviter que les compilations ne tombent en panne d'elles-mêmes, mais cela signifie qu'une organisation ayant « épinglé » une version malveillante continuera à l'utiliser pour ses compilations jusqu'à ce que quelqu'un modifie effectivement le fichier de verrouillage. Exécutez cargo update --dry-run pour voir ce qui changerait, puis exécutez la commande correspondante cargo update pour réécrire le fichier de verrouillage une fois que vous aurez vérifié que les versions de remplacement ne présentent aucun risque.
Pourquoi cela devait forcément arriver à Rust
Les attaquants suivent la vague d’adoption. Le rôle croissant de Rust dans les domaines de l’infrastructure, de la cryptographie et des logiciels système au cours des dernières années faisait que la question n’était pas de savoir « si », mais « quand ». Des crates qui n’étaient que de petits projets parallèles il y a quelques années font désormais partie intégrante des piles de production. Les scripts de compilation sont culturellement courants en Rust, contrairement aux scripts post-installation dans npm. Un paquet comportant un script post-installation attire davantage l’attention des relecteurs. build.rs Ce n'est pas le cas, car les crates qui génèrent du code, sondent les capacités de la plateforme ou lient des bibliothèques natives ont toutes une raison légitime d'en inclure une. C'est cette utilisation légitime qui fait que la simple présence de build.rs un signal si faible. Ce n’est d’ailleurs pas la première fois que TrapDoor crates.io est confronté à un logiciel malveillant. En 2023, le registre avait déjà retiré neuf crates victimes de typosquatting dont build.rs les fichiers envoyaient les métadonnées de l'hôte vers un canal Telegram, et attaques contre la chaîne d'approvisionnement sur crates.io sont apparues si souvent depuis lors que l'équipe a cessé d'en parler dans son blog dès février 2026. La nouveauté avec TrapDoor, c'est que crates.io s'inscrit dans une campagne coordonnée qui a touché npm et PyPI dans la même vague. Ces deux plateformes avaient déjà connu leur propre version de ce phénomène quelques années auparavant, à une échelle bien plus grande, lorsque le nombre de leurs paquets avait atteint typosquatting et les noms similaires qui méritent que les pirates s'y intéressent. TrapDoor intègre crates.io au modèle de menaces inter-écosystèmes que les équipes de sécurité appliquent déjà à npm et PyPI.
Sécuriser la chaîne d'approvisionnement de Rust
SecureChain pour Rust vérifie une bibliothèque avant qu'elle n'intègre votre arborescence de dépendances, en analysant le code source à la recherche de schémas d'attaques pouvant survenir lors de la compilation, y compris les éléments malveillants build.rs les scripts et les macros procédurales, et en bloquant ceux qui échouent. Les crates sont recompilées à partir d’un code source vérifié plutôt que d’être transmises telles quelles depuis le registre public. C’est le genre de barrière qui doit être mise en place devant un paquet tel que sui-framework-helpers, car dès qu'un ennemi build.rs se trouve dans l'arborescence, en cours d'exécution cargo build il est déjà trop tard. Il continue également à corriger les vulnérabilités des crates déjà présentes dans votre pile, y compris celles dont les responsables ne donnent plus de nouvelles. TrapDoor est le problème immédiat. La plupart des éléments qui se retrouvent dans une arborescence Rust de production sont plus lents, et cela vaut également la peine de s’en occuper. Abonnez-vous à la Lettre d'information TuxCare pour une analyse de ce type la prochaine fois qu’une campagne choisira un nouveau nom de paquet et un nouvel écosystème dans lequel se cacher.
FAQ
Qu'est-ce qu'une attaque de la chaîne d'approvisionnement Rust ?
Une attaque qui compromet le code ou les dépendances avant qu'ils ne vous parviennent, le plus souvent par le biais de crates malveillants ou piratés sur crates.io. En Rust, les scripts de compilation et les macros procédurales constituent les deux voies d'exécution au moment de la compilation qui importent le plus, car elles exécutent toutes deux du code avant que l'application finale ne le fasse.
Qu'est-ce que build.rs et en quoi est-ce dangereux ?
build.rs Il s'agit d'un programme Rust que Cargo compile et exécute avant de construire la crate à laquelle il appartient, avec les privilèges du processus exécutant Cargo. Il est destiné à la génération légitime de code et à la liaison native ; toute dépendance qui en intègre un permet d'exécuter du code arbitraire avec les autorisations dont dispose le processus de construction de Cargo.
TrapDoor avait-il réellement besoin de build.rs, ou n'importe quel hook de compilation Rust aurait-il fait l'affaire ?
build.rs Cela suffisait, et c'était la solution la plus simple ; c'est donc ce qu'ont utilisé les six crates. Une macro procédurale constitue l'autre voie possible au moment de la compilation, mais elle ne s'exécute que lorsque le code utilisateur l'appelle effectivement, alors qu'un script de compilation s'exécute simplement parce que le crate figure dans le graphe de compilation. Les attaquants ont choisi build.rs car cela leur a permis d'obtenir le point décisif dont ils avaient besoin.
Est-ce que le fait de retirer une caisse de la liste la supprime de crates.io ?
Non. Le retrait empêche les nouvelles résolutions de sélectionner cette version tout en la laissant téléchargeable, et une version déjà épinglée dans un fichier existant Cargo.lock continue d'être utilisée. Il n'est pas possible de supprimer une version publiée sur crates.io.
Quels étaient les fichiers corrompus de l'assistant IA dans TrapDoor ?
Le .cursorrules et CLAUDE.md Ces fichiers utilisaient des caractères Unicode de largeur nulle pour dissimuler des instructions destinées aux assistants de codage basés sur l'IA. La campagne a également tenté d'introduire des fichiers similaires via des demandes de modification (pull requests) sur GitHub, dans le cadre de projets sans rapport avec le sujet.
