Composed

TanStack compromis par une attaque de type Supply-Chain

Tanstack visé par une attaque Mini Shai Hulud - Supply-Chain, qui s'est propagée. Ce que s'est passé, comment, et quoi faire.

Publié le par Emmanuel LASTRA 6 min de lecture

TanStack compromis par une attaque de type Supply-Chain
Image par Pete Linforth de Pixabay

Le 11 mai 2026, entre 19h20 et 19h26 UTC (21h20–21h26 heure de Paris), 84 versions malveillantes ont été publiées sur npm pour 42 paquets @tanstack/*. Personne n’a volé de token npm : les publications ont été signées et authentifiées par le pipeline de publication légitime de TanStack, via son identité OIDC de confiance, après que du code contrôlé par l’attaquant a détourné le runner GitHub Actions à mi-exécution. L’attaque est attribuée au groupe TeamPCP et constitue la quatrième vague de la famille de vers npm Shai-Hulud (après Aqua Security Trivy en mars et Bitwarden CLI en avril 2026).

CVE : CVE-2026-45321 (CVSS 9.6 - Critique) / GHSA : GHSA-g7cv-rxg3-hmpx

Paquets affectés

42 paquets de la famille @tanstack/router sont touchés (@tanstack/react-router, @tanstack/vue-router, @tanstack/solid-router, @tanstack/router-core, @tanstack/react-start, @tanstack/router-plugin…), avec deux versions malveillantes chacun. Les familles @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual* et @tanstack/store sont confirmées saines.

Via sa capacité d’auto-propagation, le ver a ensuite contaminé des dizaines de mainteneurs tiers en quelques heures : Mistral AI (@mistralai/mistralai 2.2.2–2.2.4), UiPath (40+ paquets), @draftlab/auth, @draftlab/db et une vingtaine d’autres. Plus de 170 paquets affectés au total, documentés par Snyk en fin de journée.

Trois vulnérabilités enchaînées

Pwn Request via pull_request_target. Le 10 mai, l’attaquant crée un fork de TanStack/router sous le compte zblgg, délibérément renommé zblgg/configuration pour ne pas apparaître dans les recherches. Le 11 mai à 10h49 UTC (12h49 heure de Paris), la PR malveillante #7378 est ouverte contre main. Le workflow bundle-size.yml utilisait le déclencheur pull_request_target (qui s’exécute dans le contexte de sécurité du dépôt de base) tout en checkoutant le code de la fork. Résultat : le code de l’attaquant tourne avec les permissions du dépôt principal et l’accès à son cache Actions.

Empoisonnement du cache GitHub Actions. Le payload de la PR (vite_setup.mjs) ne fait rien de visible. Il écrit une version corrompue du store pnpm sous la clé de cache exacte que le workflow de release consultera lors du prochain push sur main (calculable depuis pnpm-lock.yaml). L’entrée empoisonnée de 1,1 Go est sauvegardée à 11h29 UTC (13h29 heure de Paris) et reste dormante huit heures. Point méconnu : actions/cache@v5 sauvegarde le cache via un token interne au runner, pas via le GITHUB_TOKEN. Fixer permissions: contents: read ne protège pas contre les écritures en cache.

Extraction du token OIDC depuis la mémoire du runner. À 19h15 UTC (21h15 heure de Paris), un push légitime sur main déclenche release.yml, qui détient id-token: write. Le store pnpm corrompu est restauré, les binaires malveillants s’exécutent, localisent le processus Runner.Worker, lisent sa mémoire via /proc/<pid>/mem et extraient le token OIDC. Ils publient directement sur npm avec ce token valide, avant que l’étape de publication légitime ne soit atteinte. Les deux runs terminent en status: failure. npm reçoit pourtant 84 publications valides, signées et pourvues d’attestations SLSA Build Level 3.

Fenêtre d’exposition

ÉvénementHeure UTCHeure Paris (CEST)
PR malveillante ouverte (#7378)11 mai, ~10h4911 mai, ~12h49
Cache pnpm empoisonné11 mai, 11h2911 mai, 13h29
84 versions malveillantes publiées11 mai, 19h20–19h2611 mai, 21h20–21h26
Détection externe (chercheur carlini)11 mai, ~19h5011 mai, ~21h50
Début de la dépréciation des 84 versions11 mai, ~21h0011 mai, ~23h00

Les versions malveillantes ont été accessibles environ 3 heures sur npm.

Ce que fait le payload

Le fichier router_init.js (2,3 Mo, obfusqué sur trois couches) est injecté dans chaque tarball (un fichier compressé contenant les fichiers d’un paquet npm). Un optionalDependency malveillant déclenche son téléchargement et son exécution lors d’un npm install. Le payload collecte systématiquement tous les secrets accessibles depuis l’environnement : tokens AWS (IMDS v2, Secrets Manager), GCP, Kubernetes, Vault, ~/.npmrc, tokens GitHub, clés SSH, et jusqu’aux fichiers de session Claude Code (~/.claude/projects/*.jsonl). Les données sont exfiltrées via le réseau P2P Session/Oxen, chiffré de bout en bout et indiscernable de la messagerie ordinaire.

Le ver s’auto-propage ensuite en republiant tous les paquets maintenus par la victime avec la même injection. Il installe également des mécanismes de persistance dans les outils de développement : hooks Claude Code (.claude/settings.json) et tâches VS Code (.vscode/tasks.json), qui le réexécutent à chaque ouverture du projet.

Point critique : le payload installe un service systemd (Linux) ou LaunchAgent (macOS) qui surveille le token GitHub volé et détruit le répertoire home (rm -rf ~/) si le token est révoqué. Il faut impérativement désactiver ce service avant de faire pivoter les secrets, sous peine de déclencher la destruction.

Le problème SLSA

C’est la première attaque documentée produisant des attestations de provenance SLSA Build Level 3 valides pour des paquets malveillants. Les attestations Sigstore sont techniquement correctes : elles attestent que les paquets ont bien été construits par release.yml sur refs/heads/main. Ce qu’elles n’attestent pas : que le workflow était autorisé à s’exécuter, que le code compilé était sûr, ni que la publication venait de l’étape prévue. La configuration OIDC vulnérable ne ciblait que le dépôt, sans préciser le workflow ni la branche. Tout projet npm utilisant OIDC trusted publishing sans ce pinning est exposé à cette classe d’attaque.

Que faire

Si vous avez installé un paquet @tanstack/* le 11 mai 2026, deux risques immédiats sont à prendre en compte avant toute action.

Premier risque : le payload installe un service de surveillance (gh-token-monitor) qui détruit le répertoire home si le token GitHub est révoqué. Il faut impérativement désactiver ce service (systemd sur Linux, LaunchAgent sur macOS) avant toute rotation de secrets, sous peine de déclencher la destruction.

Second risque : le payload persiste après désinstallation via des hooks dans .claude/settings.json et .vscode/tasks.json, qui le réexécutent à chaque ouverture du projet dans Claude Code ou VS Code. Ces fichiers doivent être nettoyés avant de considérer l’environnement sain.

L’article Snyk détaille l’ensemble des étapes de vérification, de nettoyage et de durcissement (rotation des secrets, blocage DNS des domaines C2, audit des workflows pull_request_target, configuration OIDC) : Analyse complète et guide de remédiation.

Pour aller plus loin

Qu’est-ce qu’une attaque Supply-Chain ? Plutôt que de s’attaquer directement à une cible, l’attaquant compromet un outil ou une dépendance que la cible utilise. Ici, les développeurs qui installent @tanstack/react-router font confiance à TanStack : c’est cette chaîne de confiance qui est détournée.

Qu’est-ce que l’OIDC trusted publishing ? Un mécanisme permettant à un workflow GitHub Actions de publier un paquet npm sans stocker de token permanent comme secret. Le runner obtient un jeton d’identité éphémère auprès de GitHub, que npm accepte si le dépôt et le workflow correspondent à la configuration déclarée. Plus sûr qu’un token statique, mais pas infaillible si le workflow ou les permissions sont mal configurés.

Qu’est-ce que la provenance SLSA ? SLSA (Supply-chain Levels for Software Artifacts) est un cadre de sécurité définissant des niveaux de garantie sur l’origine d’un paquet. Une provenance SLSA de niveau 3 certifie qu’un paquet a été construit par un pipeline CI précis identifié et reproductible, à partir d’une source contrôlée. Elle ne garantit pas que le code compilé était sain et non malveillant.

Qu’est-ce que l’empoisonnement de cache GitHub Actions ? GitHub Actions peut mettre en cache certains répertoires entre les runs pour accélérer les builds. Si un workflow mal isolé permet à du code non fiable d’écrire dans ce cache, le contenu empoisonné pourra être restauré lors d’un run ultérieur, éventuellement exécuté avec davantage de privilèges.

Sources

Postmortem officiel TanStack · Analyse Snyk · GHSA-g7cv-rxg3-hmpx · CVE-2026-45321 sur CVE.org