Ce qui se passe
Trois paquets npm qui reprennent le code de mathjs sous un autre nom
embarquent un implant d'accès à distance : mathmain, mathsbase et
math-universe. SafeDep a repéré le premier le 17 septembre 2026, sans
parvenir à lire la charge : elle est chiffrée, et la clé n'est écrite nulle part
dans le paquet. C'est JFrog qui a retrouvé l'entrée déclenchant le
déchiffrement, le 21 septembre ; SafeDep a reproduit le résultat le jour même
et publié l'analyse complète.
Versions piégées et versions saines :
| Paquet | Avec le chargeur | Sans |
|---|---|---|
mathmain |
1.0.0 |
— |
mathsbase |
1.0.1 |
1.0.0, 1.0.2 |
math-universe |
1.0.0, 1.0.1, 1.0.2 |
— |
Le piège de mathsbase mérite d'être noté : le 17 septembre, npm servait
1.0.2 comme version par défaut, qui est propre. Un contrôle limité à la
dernière version aurait conclu à tort que le paquet était sain.
Pourquoi ça échappe aux outils habituels
Aucun script d'installation, et un require ordinaire ne déclenche rien. Le
chargeur est glissé à la fin de lusolve(), la fonction qui résout un système
linéaire : une fois le calcul fini, un appel ajouté passe le facteur
triangulaire inférieur L à une fonction isGraph(), rangée parmi les tests de
type de lib/cjs/utils/is.js. Celle-ci convertit L en JSON et s'en sert comme
mot de passe — scrypt pour dériver une clé de 256 bits, puis AES-256-GCM pour
déchiffrer. Si le mot de passe est faux, l'authentification GCM échoue et rien
n'est écrit. S'il est bon, le fichier déchiffré est écrit sur le disque, puis
chargé.
Le déclencheur est une matrice de Pascal 3×3 :
lusolve([[1, 1, 1], [1, 2, 3], [1, 3, 6]], b);
// L = [[1,0,0],[1,1,0],[1,0.5,1]] → c'est le mot de passePasser directement L comme matrice fonctionne aussi, puisque sa propre
décomposition redonne L.
Ce que fait l'implant
graph.js, première étape, collecte des informations sur la machine, génère une
paire de clés X25519, lit un contrat sur le réseau de test Base Sepolia et
rend compte via Slack et Telegram. bignumber/type.js est une copie
d'ethers 5.7.2. fraction.js interroge Slack toutes les 10 secondes,
reconstitue le code envoyé par morceaux dans lib/cjs/utils/subwatcher, puis le
lance en processus Node détaché. SafeDep n'a pas récupéré ce code livré à
distance : on ignore ce que l'opérateur a exécuté.
Ce qu'on ne sait pas
Les compteurs npm annoncent, du 12 au 18 septembre, 605 157 téléchargements
pour mathmain, 1 923 059 pour mathsbase et 569 730 pour
math-universe. SafeDep les tient pour peu fiables — npm a aussi affiché zéro
téléchargement sur tout le registre le 17 — et n'a trouvé aucun projet
public qui appelle lusolve() avec la matrice piège. Le chargeur n'apparaît
pas non plus dans les dépôts GitHub liés (tinystar8/mathsbase,
mathubio/math-universe) : il n'existe que dans les archives publiées sur npm.
Comment vérifier
npm ls mathmain mathsbase math-universe --all
grep -nwE 'mathmain|mathsbase|math-universe' package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/nullEmpreintes SHA-256 des archives npm :
mathmain@1.0.0:1723a0df210ac61281a504f3a07ec3605d20151631e0635cc344cacc71019135mathsbase@1.0.1:03e13cdedd9c33e6fed25092b1ec7dcf11cc5962c0fbb6b3e90ba95bfec1b034math-universe@1.0.0:7e5e1bcdc6a7b0e3437269a236b49ef2be4f77081c7de5130d503c103fd6be69math-universe@1.0.1:bfe772e7ee044fd6f0bdf53e83f44aad7c9ee1925baf0cf4d884a312aa9ba50emath-universe@1.0.2:4eb1d59df7dc80dbe3ec154481e61e8824422037092c188f0cc146b543615a66
Fichiers du chargeur, identiques dans les cinq versions :
lib/cjs/utils/event.js (ab66c98e8ed5235feb963ec8845765f62f5f26b1c58c266c409767e53bcb5ccd)
et lib/cjs/utils/is.js
(5d9e952c51875d2b897eedc22b002b94ab21c8004d513bc99ce3a885f8a01dae).
Infrastructure de commande : contrats 0xac0bfC4C48A679b667732128278EACBA1c191894
(Base Sepolia) et 0xE390863Dac96a7118C71227C2b099B50cF602D31 (Ethereum
Sepolia), clé de projet Alchemy D2-TbkB2m05WXSnSDOCDI commune aux deux.
Une correspondance prouve la présence du paquet, pas son exécution. Un fichier
subwatcher dans lib/cjs/utils/, en revanche, signale une machine où le
chargeur a tourné.
Source : Why Does an npm Math Library Need an Encrypted Loader?, SafeDep, publié le 18 septembre 2026 et complété le 21 avec le déchiffrement retrouvé par JFrog.