Dos campañas maliciosas en npm salieron a la luz con tres semanas de diferencia. Si lees los titulares, se funden en un mismo mensaje: los paquetes de npm son peligrosos. Si lees los informes, discrepan justo en el punto que decide si un desarrollador llega a tener alguna oportunidad de pararlos.
Caso uno: el gusano que necesita tu confianza
Un gusano que roba credenciales apareció primero en keyv@6.0.0 y se extendió más allá de los espacios de nombres Keyv y Cacheable, hasta cientos de paquetes, el 4 de agosto de 2026.
La versión maliciosa añadió node setup.mjs como comando preinstall y dejó intacto el código compilado de la biblioteca. La primera fase comprueba si está el entorno Bun, lo descarga si hace falta y pasa el control a un bundle compilado de 727.680 bytes. Según el análisis de SafeDep, ese bundle recolecta material de GitHub, npm, la nube, Vault, Kubernetes, bases de datos y claves privadas, lee la memoria del runner de GitHub Actions y lleva su propia maquinaria de publicación en npm, que es como sigue extendiéndose.
El repositorio conservaba además ganchos separados para Claude Code y Visual Studio Code. Importan, y también importa la matización que el informe añade sobre ellos: «pueden ejecutar la carga una vez que un usuario confía en el espacio de trabajo o autoriza la configuración del proyecto».
Hay una puerta. Alguien tiene que cruzarla.
Caso dos: el que no pregunta
El 21 de agosto, TrendAI informó de catorce paquetes npm troyanizados que se hacían pasar por utilidades de calendario y rachas. Funcionan: la funcionalidad prometida está ahí, y eso es lo que hace que nadie los desinstale.
Por debajo, el archivo de entrada actúa como cargador de una puerta trasera para Linux disfrazada de acelerador matemático nativo. El nombre del archivo cambia de un paquete a otro (math-core.bin, calc-math.dat, calc-cache.bin y otros), pero el contenido es la misma baliza.
El mecanismo es donde esto se separa del caso uno. En palabras de los investigadores: «Cuando el módulo se carga, localiza el binario incluido, lo marca como ejecutable y lo lanza como proceso en segundo plano desacoplado. No hace falta ninguna llamada a una función de gancho de instalación; una sola importación en cualquier punto del grafo de dependencias, incluso transitiva, basta para ejecutar la carga».
Ningún gancho de instalación. Ninguna pregunta de confianza. Ninguna función exportada que alguien tenga que llamar. Una importación tres niveles más abajo, en una dependencia que nunca elegiste, es suficiente.
Por qué la diferencia es la noticia
Los dos son ataques a la cadena de suministro de npm. Meterlos en un mismo aviso pierde la única parte que cambia lo que uno hace.
El caso uno lo frena una decisión humana. npm 12 bloquea por defecto los scripts de ciclo de vida de dependencias no aprobadas, y los ganchos del editor necesitan que alguien confíe en el espacio de trabajo. Los clientes npm antiguos y otras vías de instalación que permiten esos scripts siguen expuestos, así que la protección es real y condicional a la vez.
El caso dos no tiene ese paso. La carga se ejecuta al importar, lo que significa que las defensas que cuentan son las que no dependen de que nadie se dé cuenta de nada: fijar versiones de dependencias, disciplina con el lockfile y saber qué hay realmente en tu árbol.
Quien se lleve de aquí un «hay que tener cuidado con los paquetes de npm» se ha saltado la mitad que le dice dónde invertir el esfuerzo.
Las cifras de alcance no coinciden, y el informe lo dice
Aquí es donde la historia se cita mal, así que va el cuadro completo.
SafeDep verificó 353 versiones envenenadas en 79 nombres de paquete. Su propia monitorización situaba la huella más amplia en 442 versiones sobre 353 nombres. Aikido informó después de al menos 868 paquetes en 1.381 versiones.
Tres recuentos, tres fuentes, y el informe es explícito: «ninguno de los dos totales más amplios era reproducible de forma independiente a partir de una lista pública completa en el momento de cierre».
Añade una segunda advertencia que pesa más que la discrepancia: esos totales «cuentan artefactos de paquete maliciosos, no sistemas víctima. Establecen la escala de la campaña, pero no muestran cuántas máquinas instalaron o ejecutaron la carga».
Así que si ves una cifra única y segura sobre esta campaña, está elegida de un conjunto de tres que no concuerdan, y en cualquiera de los casos cuenta paquetes, no infecciones.
El paso de remediación que va contra el instinto
Es la frase más útil de los dos informes, y se pasa por alto con facilidad.
La carga instala un vigilante de revocación de credenciales. SafeDep recomienda a quien responda al incidente que elimine ese vigilante antes de rotar los tokens y claves expuestos, porque la revocación es lo que lo dispara: rotar primero puede ejecutar un manejador local puesto ahí por el atacante.
La reacción instintiva ante un robo de credenciales es rotarlo todo de inmediato. Aquí ese instinto activa la trampa.
La valoración de Socket sobre a quién le toca actuar es amplia: «cualquier estación de trabajo o runner que haya ejecutado una versión afectada debe tratarse como credencialmente expuesto».
Por qué una lista de paquetes no te va a salvar
El informe señala que el registro cambiaba demasiado deprisa como para sostener una lista fija de toda la campaña. A las pocas horas de los primeros hallazgos, versiones anteriores habían vuelto a marcarse como latest en al menos nueve paquetes del conjunto inicial de SafeDep.
La consecuencia práctica: las comprobaciones de exposición tienen que usar nombres exactos de paquete, versiones resueltas y lockfiles, no una lista guardada de etiquetas actuales. Una lista de bloqueo a nivel de espacio de nombres corre el doble riesgo de dejar fuera versiones envenenadas y de marcar versiones que nunca lo estuvieron.
La forma es conocida. Una lista que parece autorizada, que ya está desactualizada cuando alguien la lee, y que da respuestas seguras en ambas direcciones.
Qué llevarse de aquí
Averigua si tus vías de instalación ejecutan scripts de ciclo de vida. Si estás en una versión de npm que los bloquea por defecto, el caso uno tiene una puerta delante. Si no lo estás, no la tiene.
Ten claro hasta dónde llega una importación transitiva. Al caso dos le basta con que se cargue un módulo en algún punto del grafo, lo que convierte la profundidad de dependencias en una propiedad de seguridad y no en una cuestión de orden.
Y si alguna vez respondes a un incidente de robo de credenciales, lee el orden de remediación antes de rotar. Esta vez, el primer movimiento evidente era el que dispara la carga.
Fuentes
- The Hacker News, «Keyv-Linked npm Worm Poisons Hundreds of Packages, Plants Claude Code and VS Code Hooks», 4 de agosto de 2026: https://thehackernews.com/2026/08/keyv-linked-npm-worm-poisons-hundreds.html (consultado el 26 de agosto de 2026). Origen del inicio en keyv@6.0.0, el mecanismo preinstall y el tamaño de la carga, el material recolectado, las tres cifras de alcance en conflicto y las salvedades que el propio informe hace sobre ellas, la matización sobre los ganchos del editor, el orden de remediación con el vigilante de revocación, la valoración de Socket y la nota sobre las etiquetas cambiantes del registro.
- The Hacker News, «14 Trojanized npm Packages Drop RedC2 4.0 Linux Backdoor With AI-Assisted C2», 21 de agosto de 2026: https://thehackernews.com/2026/08/14-trojanized-npm-packages-drop-redc2.html (consultado el 26 de agosto de 2026). Origen de los catorce paquetes, el mecanismo del cargador, los distintos nombres de archivo del binario y la identificación de la carga como la baliza RedShell para Linux.
- Ambos informes existen únicamente en inglés: las citas de este artículo son traducción propia del original, no citas literales en español.
