Pidió que lo ejecutaran
Sonatype rastrea 846 paquetes npm maliciosos publicados desde cientos de cuentas desechables con nombres generados por IA. Ninguno lleva script de instalación. El README te dice que cargues el paquete con require(), y el import ejecuta el loader. Las defensas que reconocen un paquete malo por su nombre, por su autor o por su hook declarado ahora compiten con un generador. Bromure Agentic Coding bloquea por la única propiedad que un generador no puede falsificar: la edad del paquete.
Los paquetes no llevan hook de instalación, así que npm no tiene nada que
atrapar. El README te dice que los cargues con require(), y el import
ejecuta el loader. Cada vez más, el lector que sigue esa instrucción es un
agente de código.
Sonatype Research Labs reveló el 5 de agosto una campaña que llama Flooding Dropper, con 846 componentes npm maliciosos contados y la cuenta sigue subiendo. Paul McCarty, de OpenSourceMalware, rastrea la carga bajo el nombre WEL1DROPPER. La cobertura más amplia llegó el 7 de agosto.
Dos detalles la separan de las historias de cadena de suministro de npm del último mes.
El primero es cómo obtienen su nombre los paquetes. El atacante los genera, y
hace del slopsquatting una línea de producción. Raíces recurrentes como
bigops y bnpl se recombinan con otras palabras en nombres como
bigops-api y dolyame-boxy-desktop-bnpl-card-gallery, muchos con un patrón
de versión 35.x.y. La publicación se reparte entre cientos de cuentas
desechables con un puñado de paquetes cada una, así que no hay ningún autor
prolífico al que banear.
Sin script de instalación
Los paquetes no declaran ningún hook de ciclo de vida, así que npm install
no ejecuta nada. El texto de McCarty lo dice sin rodeos: “Los paquetes NPM no
usan un script preinstall ni postinstall. No lo necesitan.”
Traen un _helpers.js empaquetado que se ejecuta al importar el módulo, junto
a un README que le dice al lector que lo importe. Cargue el paquete con
require(), dice la documentación, y la documentación tiene razón. La primera
etapa toma la huella del sistema operativo y de la arquitectura del
procesador, descarga por HTTPS una segunda etapa nativa desde uno de tres
hosts de Cloudflare Workers, y recurre a reensamblar el binario a partir de
fragmentos base64 alojados en registros DNS TXT bajo wel1.ru cuando la
descarga directa falla.
La segunda etapa varía según la plataforma. Linux recibe un ELF empaquetado con UPX que despliega un implante C2 Sliver. macOS recibe comprobaciones antidepuración, una sonda de VMware, un binario baliza y persistencia por LaunchAgent. Windows recibe parcheo de ETW y AMSI, detección de sandbox, ejecución reflexiva en memoria y persistencia mediante una clave Run del Registro y una tarea programada. McCarty y Sonatype leen la campaña como una evolución de “Moika”, la oleada de confusión de dependencias de abril que empujó unos 250 paquetes construidos en torno al robo del entorno.
El atacante movió el disparador del gestor de paquetes al lector.
La automatización le gana a la deny list
El propio resumen de Sonatype es la frase más afilada de toda la cobertura:
Léelo como una afirmación sobre las defensas. Toda capa que te protege reconociendo el paquete malo, ya sea por nombre, por cuenta de publicación o por hook declarado, ahora compite con un generador. Esas capas siguen atrapando lo que ya han visto. Se las califica por reconocer una cadena concreta, y el suministro de cadenas concretas cuesta lo que cuesta ejecutar un script.
El ángulo del agente lo afila. El slopsquatting sale a cuenta, para empezar, porque los modelos de lenguaje inventan nombres de paquete que suenan bien y no existen, y un atacante que registra esos nombres recoge tráfico de instalación gratis. Añade el hook de instalación que falta y tienes un paquete cuyo disparador es una instrucción escrita en un README. Un agente de código es el lector que alucinará el nombre, encontrará un paquete debajo, leerá la documentación y hará lo que la documentación dice. La campaña está moldeada para el agente por los dos extremos.
Elige una propiedad que el generador no pueda falsificar.
La edad no es un nombre
Cada paquete de esta campaña comparte una propiedad que ninguna automatización cambia: es nuevo. Un nombre que un modelo inventó el martes pasado no puede tener debajo un paquete de 2019. Cientos de cuentas desechables publican cientos de versiones recién salidas, porque una cuenta desechable no tiene historial desde el que publicar. El modelo depende de que el paquete esté ahí para cuando alguien lo busque, así que el atacante lo crea con unos pocos días de antelación como mucho.
Bromure Agentic Coding ejecuta el agente dentro de una VM Linux desechable en Apple Silicon y enruta cada byte de su tráfico de red por un proxy en el anfitrión, fuera de la caja en la que corre el agente. Las descargas de paquetes de npm, PyPI, Cargo, RubyGems, Maven, NuGet, módulos Go y Packagist cruzan todas ese proxy, y la primera política con la que se encuentran es un reloj.
La barrera de edad, activa por defecto
Bromure rechaza los paquetes más jóvenes que una edad mínima, fijada en dos
días de fábrica. Las referencias flotantes como latest o un rango semver
resuelven a la versión más reciente anterior al corte, así que las
instalaciones normales siguen funcionando y aterrizan en la publicación de
ayer. Una referencia fijada a una versión demasiado fresca recibe un 451
con un error claro de Bromure que nombra lo que ha pasado. Ochocientos
paquetes cuestan lo mismo de parar que ocho, porque Bromure no reconoce
ninguno.
Actúa antes de que empiece la discusión
La parte ingeniosa de esta campaña, soltar el hook de ciclo de vida y mover
la ejecución a require(), describe lo que pasa después de que el tarball
llegue al disco. La barrera de edad se sitúa en la descarga, en el proxy
del anfitrión, aguas arriba de todo eso. Si la carga se habría ejecutado al
instalar o al importar es una pregunta sobre un paquete que nunca llegó.
El .npmrc y el pip.conf de dentro de la VM pueden endurecer esta
política y no pueden relajarla.
La reputación, detrás del reloj
Con una clave de socket.dev, Bromure verifica cada paquete contra la base de datos de socket.dev antes de dejar pasar la descarga y bloquea los marcados como comprometidos, malware o typosquats. Las consultas OSV bloquean paquetes con vulnerabilidades conocidas a partir de una severidad que tú eliges. Delpi redirige directamente cada petición npm a un registro con filtrado. Estas capas reconocen cosas, lo que las hace útiles y las coloca en segundo lugar.
El README se puntúa por lo que es
“Carga esto con require()” es una instrucción escrita por un desconocido,
que llega en texto que el agente lee. Bromure puntúa la salida de
herramientas que el agente consume, incluidos contenidos de archivos,
páginas web y documentación descargada, con un modelo local de inyección de
prompts. Puntúa los archivos de instrucciones que el agente trata como
autoridad, CLAUDE.md y AGENTS.md y sus variantes, con un clasificador
dedicado más un escáner de cargas en Unicode invisible. Nada sale del Mac
para hacerlo.
La carga perfila la máquina equivocada
Supongamos que la descarga pasa igualmente, porque el paquete envejeció más allá del corte o una entrada de allowlist lo exime. Sigue a WEL1DROPPER por un perfil Bromure y míralo hacer su trabajo contra una máquina que ibas a tirar.
El primer movimiento del loader es tomar la huella del anfitrión. En un perfil
Bromure ese anfitrión es una VM Ubuntu tras NAT, a un hipervisor de macOS, así
que toma la rama Linux y planta allí un implante Sliver. Nunca pide la rama
macOS: la antidepuración, la sonda de VMware, la descarga de beacon_mac.bin,
el LaunchAgent que habría sobrevivido a un reinicio, el interés por dominios
rusos de pago. Todo eso es código para una máquina que el dropper nunca vio.
Erase home descarta la capa en la que aterrizó y devuelve el directorio
personal a su estado justo después del clonado.
Después la etapa infostealer va a buscar lo que buscaba su antepasado Moika:
el entorno. En un perfil Bromure el entorno contiene placeholders brm_….
Ninguna de las claves reales de Anthropic, OpenAI y xAI, ninguno de los tokens
de GitHub y GitLab, ningún material de AWS ni credencial de registro está en
la memoria de la VM ni en sus dotfiles. Bromure los guarda en el anfitrión y
los sustituye en la petición en el proxy, sobre el cable, para el único host
de destino al que pertenece cada uno. El robo se ejecuta hasta el final y
envía cadenas que solo resuelven en un Mac, que no es la máquina que ejecutó
el código.
La caza que recomiendan, ya en marcha
McCarty cierra el texto sobre WEL1DROPPER con consejos defensivos: cazar
consultas DNS TXT a wel1.ru, revisar los logs de proxy en busca de tráfico
hacia los hosts de Cloudflare Workers, y buscar archivos temporales y
LaunchAgents sueltos.
Lee otra vez el segundo punto. Tu portátil no guarda un log de proxy. Un
perfil Bromure guarda uno por construcción: cada petición que hace la VM cruza
el proxy del anfitrión, y el Security Log y el Trace Inspector registran el
host, el estado, la latencia y el informe de sustitución de cada una. Tres
destinos *.workers.dev desconocidos apareciendo durante una compilación son
tres filas en una lista que puedes leer, en la máquina donde corrió la
compilación, sin desplegar nada para obtenerlas.
Los registros seguirán retirando estos paquetes, y las retiradas funcionan: 846 de ellos han desaparecido o están desapareciendo. Pero la campaña ya pasa del millar, publicada desde cuentas que no cuestan nada crear bajo nombres que no cuestan nada inventar, y la siguiente oleada está a una ejecución de script de distancia. El reconocimiento es la competición equivocada.
Un paquete de dos días no merece una confianza profunda que le falte a uno de dos horas. Queda fuera de la ventana en la que opera esta economía, y puedes mover tu grafo de dependencias detrás de esa ventana con un ajuste que ya está activado.
Instala Bromure Agentic Coding, y deja que la descarga se encuentre con un reloj antes de encontrarse con tu máquina.