3 points par GN⁺ 2025-12-19 | Aucun commentaire pour le moment. | Partager sur WhatsApp
  • Le Seattle Times a évité par hasard l’attaque Shai-Hulud 2.0, mais part du principe que la chance ne peut pas constituer une stratégie de sécurité, et a donc mis en place des défenses côté client
  • Les améliorations de npm comme Trusted publishing / provenance / granular access tokens renforcent le volet « publication », mais laissent un angle mort : elles n’empêchent pas l’exécution de code malveillant au moment de l’« installation / mise à jour »
  • pnpm conserve le registre npm tel quel, tout en ajoutant des contrôles qui compliquent l’exécution de paquets malveillants à l’étape de consommation (install/update)
  • Dans le pilote, trois contrôles de pnpm ont été appliqués afin de bloquer respectivement des vecteurs comme l’exécution de scripts de cycle de vie, l’installation immédiate des dernières releases et la baisse du niveau de confiance
  • Les exceptions ne sont pas vues comme un échec mais comme une partie de la conception ; l’objectif est d’opérer en defense-in-depth en documentant les exceptions tout en laissant les autres couches continuer à protéger

Contexte de l’incident et hypothèses de départ

  • En novembre 2025, un ver npm autoréplicatif a infecté 796 paquets et s’est propagé à l’échelle de 132 millions de téléchargements mensuels
  • L’attaque utilisait un script preinstall pour voler des identifiants, installer des backdoors persistantes et, dans certains environnements, aller jusqu’à supprimer l’environnement de développement
  • Si notre organisation n’a pas été touchée, ce n’est pas grâce à une défense solide, mais par hasard, parce qu’aucun npm install / npm update n’a été exécuté pendant la période de l’attaque
  • Pour une rédaction, la confiance est essentielle : une compromission de la supply chain peut exposer les données clients, les identifiants des employés, l’infrastructure de production et le code source, avec des coûts élevés de restauration et de notification

L’équipe et le contexte d’adoption

  • Le Seattle Times utilise npm comme gestionnaire de paquets principal depuis longtemps, et a aussi expérimenté Yarn sans que cela s’impose durablement
  • La raison d’adopter pnpm tient à ses contrôles de sécurité côté client qui complètent les améliorations au niveau du registre
  • pnpm a été jugé crédible comme drop-in replacement, car il utilise le même registre, les mêmes commandes et le même workflow
  • Il ne s’agit pas d’une étude de cas achevée, mais du partage des problèmes rencontrés et du raisonnement d’une vraie équipe qui commence tout juste à travailler sérieusement sur la sécurité de la supply chain

Pourquoi des contrôles côté client sont nécessaires

  • Les améliorations de sécurité de npm rendent déjà plus difficile le déploiement de paquets malveillants après compromission d’un compte
  • Ces améliorations protègent le volet « publication (publishing) », mais n’empêchent pas en soi l’installation de paquets malveillants au stade de la « consommation (consuming) »
  • Lors de npm install / npm update, les lifecycle scripts (preinstall/postinstall, etc.) peuvent exécuter du code arbitraire avec les privilèges du développeur avant même que la sûreté du paquet ait été évaluée
  • Ces scripts peuvent accéder aux identifiants npm / GitHub / AWS / DB, au code source, à l’infrastructure cloud et à l’ensemble du système de fichiers
  • Des attaques comme Shai-Hulud exploitent cette architecture : si le compte d’un mainteneur est compromis, un script malveillant peut s’exécuter au moment de l’installation, avant que la communauté n’ait le temps de le détecter
  • Les améliorations de npm côté publication et les contrôles de pnpm côté consommation sont réunis comme défenses complémentaires dans une approche de « defense-in-depth »

Les 3 couches mises en place

  • Dans le pilote, trois contrôles visant des vecteurs d’attaque différents ont été utilisés ensemble
  • Chaque contrôle comporte une porte de sortie pour des exceptions réalistes, et leur conception part du principe que des exceptions seront nécessaires en production

Control 1: Lifecycle Script Management

  • Par défaut, pnpm bloque les lifecycle scripts, tout en pouvant poursuivre l’installation avec un avertissement
  • Craignant qu’un avertissement soit ignoré, l’équipe a choisi strictDepBuilds: true, afin de forcer un échec immédiat de l’installation en présence d’un script
  • Exemple de configuration dans pnpm-workspace.yaml avec les champs suivants
    • strictDepBuilds: true
    • onlyBuiltDependencies : liste d’autorisation des paquets dont les scripts de build sont nécessaires
    • ignoredBuiltDependencies : liste des paquets dont les scripts de build ne sont pas nécessaires et doivent être bloqués (ou ignorés)
  • Les « scripts nécessaires » sont définis comme des actions telles que la compilation d’extensions natives ou le linkage de bibliothèques dépendantes de la plateforme
  • Les « scripts non nécessaires » sont définis comme des optimisations ou des configurations facultatives qui, dans l’usage de l’équipe, n’ont pas d’impact fonctionnel
  • L’échec de l’installation force alors les étapes suivantes
    • pnpm indique clairement quels paquets contiennent des scripts
    • investigation et compréhension du comportement du script
    • décision consciente, documentée, d’autoriser ou de bloquer sur jugement humain
  • L’équipe pnpm envisage de faire de strictDepBuilds: true la valeur par défaut dans la v11 et réfléchit aussi à améliorer l’intitulé de la syntaxe allow/deny

Control 2: Release Cooldown

  • Les versions publiées récemment ne peuvent pas être installées pendant une certaine période de cooldown, ce qui laisse à la communauté le temps de détecter et de retirer un paquet malveillant
  • Exemple de configuration dans pnpm-workspace.yaml avec les champs suivants
    • minimumReleaseAge: <duration-in-minutes>
    • minimumReleaseAgeExclude : liste d’exceptions autorisées, par exemple pour des hotfixes urgents
  • Cela suppose d’abandonner le réflexe « le plus récent est le meilleur » au profit d’une idée selon laquelle, du point de vue de la supply chain, une version un peu plus ancienne peut être plus sûre
  • Lors de l’attaque de septembre 2025 (16 paquets, dont debug et chalk), le retrait a pris environ 2,5 heures ; pour Shai-Hulud 2.0 en novembre 2025, environ 12 heures
  • Selon la tolérance au risque de l’organisation, ce cooldown peut se compter en heures, en jours ou en semaines ; sous n’importe laquelle de ces formes, il aurait bloqué l’attaque en question
  • Cela s’accorde bien avec la réalité où les organisations ne fonctionnent déjà pas toujours sur la toute dernière version, si bien que le cooldown ne perturbe pas fortement le travail
  • Quand c’est indispensable, par exemple pour un correctif de sécurité ou un bug critique, l’exception peut être levée après examen

Control 3: Trust Policy

  • L’installation est bloquée si une version a été publiée avec une authentification plus faible que la version précédente
  • L’idée est d’y voir un signal indiquant qu’un compte de mainteneur a été compromis et que la publication a été effectuée depuis la machine d’un attaquant plutôt que depuis la CI/CD officielle
  • Exemple de configuration dans pnpm-workspace.yaml avec les champs suivants
    • trustPolicy: no-downgrade
    • trustPolicyExclude : liste d’exceptions autorisées, par exemple lors d’une migration de CI/CD
  • npm suit, selon l’article, trois niveaux de confiance pour la publication des paquets (du plus fort au plus faible)
    • Trusted Publisher : basé sur GitHub Actions + OIDC + npm provenance
    • Provenance : attestation signée depuis la CI/CD
    • No Trust Evidence : publication via nom d’utilisateur / mot de passe ou via token
  • Si une nouvelle version présente un niveau de confiance inférieur à la précédente, l’installation échoue
  • Lors de l’attaque s1ngularity d’août 2025, où l’attaquant a publié localement une version malveillante sans accès à la CI/CD et sans provenance, ce contrôle aurait bloqué l’installation
  • Parmi les cas légitimes possibles de baisse de confiance figurent l’arrivée d’un nouveau mainteneur, une migration de CI/CD ou un hotfix manuel pendant une panne de CI/CD ; après enquête, ces cas sont ajoutés à la liste d’exceptions
  • Cette fonction est une nouveauté ajoutée à pnpm en novembre 2025, et l’équipe apprend encore à quelle fréquence les baisses de confiance légitimes se produisent en pratique

Exemple de fonctionnement des couches combinées : correctif d’une vulnérabilité React

  • Imaginons qu’il faille appliquer immédiatement le correctif d’une vulnérabilité critique dans React Server Components, annoncée en décembre 2025
  • En temps normal, le cooldown empêcherait l’« installation d’une version tout juste publiée », mais ici il est impossible d’attendre en raison du caractère critique du correctif
  • Dans ce cas, on ajoute la version React concernée à minimumReleaseAgeExclude, après avoir vérifié l’annonce de vulnérabilité et la légitimité du correctif
  • Même avec cette exception, les autres couches continuent à protéger
    • React n’a généralement pas de lifecycle scripts ; si un script apparaissait dans la version corrigée, ce serait immédiatement un signal suspect et le paquet pourrait être bloqué
    • Si un attaquant volait des identifiants pour publier localement un faux « correctif », la baisse du niveau de confiance pourrait entraîner un blocage
  • L’exception n’est donc pas considérée comme un « échec de sécurité », mais comme une conception où, même si une couche est contournée, les autres demeurent et éliminent le point de défaillance unique

Résultats du pilote

  • Un PoC a été mené sur un service backend avec les trois contrôles activés
  • Le temps total de préparation, entre investigation, compréhension et définition des règles d’accès, s’est limité à quelques heures
  • pnpm a identifié trois paquets comportant des lifecycle scripts
    • esbuild : optimise le démarrage de la CLI à la milliseconde près, mais l’équipe n’utilise que l’API JS et a donc jugé cela inutile
    • @firebase/util : configure automatiquement le SDK client, mais l’équipe n’utilise que le SDK serveur et a donc jugé cela inutile
    • protobufjs : effectue une vérification de compatibilité de schéma, mais n’est utilisé qu’en dépendance transitive et n’est pas nécessaire dans ce cas d’usage
  • Après consultation de la documentation et analyse des scripts (avec aide de l’IA pour interpréter les scripts), l’équipe a conclu que les trois scripts étaient inutiles pour son usage et les a bloqués
  • Aucun impact fonctionnel n’a été constaté
  • La friction est une fonctionnalité intentionnelle : elle force à ne plus faire confiance implicitement au code exécuté dans l’environnement
  • Si une nouvelle dépendance contient un script, l’équipe estime qu’il faudra environ 15 minutes pour la revue et la documentation

Enseignements tirés en production

  • La combinaison des couches côté client et des améliorations de npm côté publishing donne le sentiment qu’une defense-in-depth fonctionne réellement
  • Même lorsqu’une exception est appliquée, les autres couches restent présentes, ce qui réduit l’inquiétude liée à cette exception
  • Il faut du temps pour passer d’un modèle mental orienté confort à un modèle orienté sécurité, mais une fois l’habitude prise, cela devient naturel
  • Cette approche reste praticable même pour une organisation de taille moyenne sans équipe sécurité dédiée
  • La trust policy n’existe que depuis quelques semaines, il faut donc encore apprendre sa fréquence de déclenchement légitime et son ressenti opérationnel
  • L’équipe prévoit de l’étendre prochainement à d’autres bases de code, ce qui apportera plus de données sur des applications ayant des graphes de dépendances différents

Conseils d’adoption pour les autres équipes

  • Il est recommandé de commencer par un seul projet afin d’apprendre le workflow et les points de friction
  • Comme des exceptions peuvent être nécessaires pour les lifecycle scripts, le release cooldown et la trust downgrade, il faut concevoir dès le départ en partant du principe qu’il y aura des exceptions
  • Il est recommandé d’utiliser dès le premier jour strictDepBuilds: true, qui force l’échec de l’installation plutôt qu’un simple avertissement
  • Il faut documenter toutes les exceptions afin de conserver une trace d’audit et de faciliter un futur nettoyage
  • Il faut garder à l’esprit qu’une exception sur une couche laisse la protection des autres couches intacte

Aucun commentaire pour le moment.

Aucun commentaire pour le moment.