El gusano se volvió código abierto
En algún momento de la semana del 11 de mayo de 2026, las personas detrás de Shai-Hulud — el gusano autorreplicante de la cadena de suministro de npm que viene devorando cuentas de mantenedores desde septiembre de 2025 — filtraron su propio código fuente. Para el fin de semana, OX Security había encontrado cuatro paquetes typosquatted de npm publicados desde una sola cuenta, uno de los cuales es una copia casi literal del gusano filtrado, otro es un bot DDoS en Golang, y los otros dos son simples infostealers que envían claves SSH y billeteras cripto a C2 de saldo. El piso para crear forks de ataques a la cadena de suministro acaba de bajar muchísimo, y las personas con más probabilidad de instalar uno de estos paquetes ya no son humanas.
En algún momento de la semana pasada, las personas detrás de Shai-Hulud — el gusano de npm
que viene masticando cuentas de mantenedores desde septiembre
de 2025 — filtraron su propio código fuente. Para el fin de semana, una cuenta
de npm llamada deadcode09284814 había publicado cuatro typosquats
reutilizando ese código. Uno era el gusano, casi literal.
Uno era un bot DDoS en Golang. Dos eran simples infostealers que envían por POST tus claves SSH,
tu ~/.aws/credentials y tu bóveda de MetaMask a una
IP alquilada. Dos mil seiscientas setenta y ocho instalaciones
después, la pregunta ya no es "¿alguien va a convertir
la filtración en arma?". La pregunta es cuál de los agentes que están escribiendo
npm install en tu terminal esta tarde se topa con uno de ellos.
Hay algo que pasa, periódicamente, en el software ofensivo,
donde una herramienta cerrada termina volcada en un foro y la población de
personas capaces de ejecutarla pasa de "la única banda que la escribió" a "cualquier
adolescente con un VPS". Mimikatz. EternalBlue. El código fuente de Conti. Cada
vez, la capacidad no cambió — simplemente dejó de ser escasa.
El titular de la semana pasada, reportado por
BleepingComputer
sobre la base de la investigación de
OX Security,
es que Shai-Hulud — el gusano sobre el que
escribimos cuando se comió cuarenta y dos
paquetes @tanstack en seis minutos — se sumó a esa lista.
El fork no es teórico. Para el domingo, OX había documentado cuatro
paquetes maliciosos publicados desde una sola cuenta de npm llamada
deadcode09284814:
chalk-tempalte— un typosquat dechalk-template, que lleva una copia casi literal del código fuente filtrado de Shai-Hulud, incluyendo el comportamiento característico de Shai-Hulud de subir credenciales robadas como repositorios de GitHub públicos autogenerados. La lectura de OX: "el código del malware Shai-Hulud es una copia casi exacta del código fuente filtrado, sin técnicas de ofuscación", lo que sugiere un nuevo actor de amenazas que no se molestó siquiera en limar los números de serie.axois-utils— un typosquat deaxios-utils, que envía un payload en Golang al que OX llama Phantom Bot: floods HTTP, TCP, UDP y de reset, persistencia a través de la carpeta de Inicio de Windows y tareas programadas, y un C2 enb94b6bcfa27554.lhr.life. Tu máquina de desarrollo, reclutada.@deadcode09284814/axios-util— un typosquat distinto, un payload distinto: claves SSH, variables de entorno, credenciales de AWS/GCP/Azure, enviadas a80.200.28.28:2222. "Bastante directo", en palabras de OX.color-style-utils— un infostealer simple que captura tu IP, geolocalización y datos de billeteras cripto y los envía por POST aedcf8b03c84634.lhr.life.
Descargas semanales combinadas al momento del informe de OX: 2.678.
Hay dos historias enredadas aquí que merecen ser
desenredadas. La aburrida es que npm tiene typosquats. Los tiene;
siempre los ha tenido; siempre los tendrá, por la misma razón por la que hay perros
llamados Bench en el parque para perros: los nombres son baratos y el espacio de nombres
es plano. La interesante es que la barrera para ejecutar un
gusano de la clase de Shai-Hulud acaba de caer. Hasta la semana pasada, hacía falta
el utillaje de la banda original, la infraestructura de la banda original,
la disciplina de la banda original para no ser atrapados. Hoy,
hace falta un clon de GitHub de un repo público, un túnel lhr.life y
la paciencia para teclear npm publish. Los cuatro paquetes que encontró OX son,
colectivamente, la prueba.
Por qué el número de actores importa más que el número de paquetes.
Un único gusano sofisticado es, en cierto sentido, un adversario
tratable. Tiene tics. Tiene infraestructura. Tiene hábitos.
Se pueden escribir firmas de detección contra él. Aikido, Socket,
Snyk, Wiz — las tiendas de monitoreo de la cadena de suministro de npm que se lanzaron
sobre el incidente de @tanstack del 11 de mayo — lo atraparon en cuestión de horas,
precisamente porque venían vigilando a la misma familia durante
ocho meses.
Una familia de gusanos derivativos escritos por personas que descargaron
el código desde un sitio de pegado es una forma distinta. Cada uno exfiltrará
hacia un C2 diferente, incrustará una clave RSA diferente, elegirá una
combinación diferente de archivos para leer, y elegirá un
espacio distinto de typosquats donde vivir. Algunos serán cuidadosos; la mayoría serán
chapuceros de un modo que los hará caer rápido; uno de ellos, la
próxima vez que escribamos un post como este, será lo suficientemente sofisticado
como para que no lo atrapemos en una semana. El problema de detección de los defensores
se ensancha de "detectar a Shai-Hulud" a "detectar cualquier cosa
que quiera leer ~/.ssh/id_ed25519 desde dentro de un script prepare".
Eso es una superficie mucho, mucho más grande.
La forma del camino de instalación también cambió, y esta es la
parte que debería preocupar a cualquiera que esté ejecutando un agente de programación. Un
desarrollador humano que quisiera chalk-template habría, en 2024, leído
el nombre del paquete en un tutorial, lo habría tecleado y se habría dado cuenta del
typo cuando chalk-tempalte apareciera con doscientas descargas
y un desconocido como publicador. Un
agente de programación
al que se le pide "agregar algo de color a la salida de mi CLI" en 2026 va a instalar
lo que sea que el gestor de paquetes le devuelva. El agente no ve
el campo del publicador. El agente no nota que el README
tiene tres líneas. El agente está haciendo treinta npm install
esta hora porque el usuario está haciendo el trabajo de un pequeño equipo y
al agente le pagan por tarea, no por instalación.
Qué hace en realidad un clon de Shai-Hulud sin ofuscación.
El paquete chalk-tempalte está, escribe OX, "casi sin ningún
cambio" respecto del código fuente filtrado. Eso significa que aplica la misma
mecánica que recorrimos en
El gusano que se escribe a sí mismo en .claude,
con un nuevo matiz significativo: el canal de exfiltración
es el propio GitHub.
El truco característico de Shai-Hulud — preservado por el imitador porque
copiar es más fácil que reescribir — es que las credenciales robadas
no van a un servidor de recolección oculto. Van a un
repositorio público de GitHub recién creado, publicado usando un token de GitHub que
el malware acaba de robar a la víctima. Los secretos de la víctima quedan en un
repo público, propiedad de la propia cuenta de GitHub de la víctima, a la vista de cualquiera
en el mundo para que los rastree, hasta que alguien se da cuenta y el repo es
borrado. El manual estándar del defensor — bloquear el dominio de exfiltración,
buscar DNS saliente inusual — no atrapa esto, porque
el DNS saliente es api.github.com, con el que tu máquina de desarrollo
habla doscientas veces por hora de todas formas. El paquete de credenciales
sale de tu portátil disfrazado de git push.
Una vez que los secretos son públicos, cualquiera que mire la manguera de GitHub — y varias personas miran la manguera de GitHub para exactamente esta razón — puede recogerlos. El gusano no necesita mantener vivo su C2. GitHub es el C2.
Hay un detalle diminuto en esa imagen sobre el que vale la pena
detenerse. El clon de Shai-Hulud, chalk-tempalte, ni siquiera depende de
su propia infraestructura para recibir el botín. Usa la propia
identidad de GitHub de la víctima para publicar los propios secretos robados de la víctima
en un repositorio público de GitHub. El C2 en lhr.life es un
respaldo. El canal primario es git push. Para un defensor que mira
el egreso, esto es indistinguible del CI normal del desarrollador.
Para GitHub, es — hasta que alguien marque el repo — un proyecto
público legítimo propiedad de un usuario real. La exfiltración queda lavada a través
de la identidad de la víctima.
Lo que el agente está haciendo mientras vos hacés scroll.
Si tenés un agente de programación en tu terminal — Claude Code, Cursor's
CLI, Codex CLI, Aider, el que prefieras — hay una probabilidad no nula
de que, en el tiempo que tardaste en leer el párrafo anterior, el agente
haya corrido un npm install en tu nombre. Quizá dos. Los agentes de programación no
se detienen a admirar árboles de dependencias. La razón completa por la que compraste
uno es que no se detiene.
Los paquetes que atrapó OX son typosquats, una categoría de
error específicamente apta para la velocidad de máquina. Un humano que
tiene que tipear chalk-template va a poner las letras en el orden correcto
porque lo ha hecho cien veces. Un modelo que
ha ingerido cada post de Stack Overflow del planeta ha visto
chalk-template y chalk-tempalte en el mismo corpus de entrenamiento —
este último típicamente dentro de una captura de pantalla del error de otra persona —
y ante un prompt como "agregá salida con color a mi CLI",
a veces va a emitir el typo literalmente. El agente no se inmuta. El
gestor de paquetes no se inmuta. El script prepare se ejecuta.
Este no es un modo de fallo hipotético. Es el modo de fallo para el que la familia Shai-Hulud fue diseñada. El gusano original se propagaba robando tokens de mantenedores y usándolos para republicar más versiones comprometidas de paquetes legítimamente populares. Los imitadores no tienen todavía los tokens de los mantenedores; lo que tienen es el espacio de nombres de typosquats, en el cual los agentes son singularmente buenos para caer.
Dónde encaja Bromure en esta historia.
Bromure Agentic Coding es la configuración en
la que el agente de programación corre dentro de una VM Linux desechable por tarea,
con la carpeta del proyecto montada, el egreso intermediado y
las credenciales retenidas del lado del host macOS del hipervisor. Recorrimos
la arquitectura en detalle en el
análisis de Bitwarden CLI
y en el
análisis de @tanstack. Lo
que sigue es lo que específicamente le pasa a cada uno de estos cuatro
paquetes dentro de ese límite.
Recorré esto contra los cuatro paquetes, uno a uno, porque los detalles específicos son donde la arquitectura se gana el sueldo.
chalk-tempalte busca $GH_TOKEN.
La jugada característica del clon de Shai-Hulud es tomar el token de GitHub
del desarrollador y usarlo para crear un repositorio público en
la propia cuenta del desarrollador. Dentro de una VM Bromure, el
$GH_TOKEN que lee es un stub — una cadena sintácticamente válida que
empieza con ghp_ y existe exactamente por esta razón. La
primera acción del runner es POST /user/repos contra
api.github.com. El proxy de egreso del lado host reconoce
api.github.com como endpoint whitelisted, pero solo para las
operaciones que la tarea actual realmente pidió — git push al
repo en el que la tarea está trabajando, gh pr create contra ese mismo
repo, gh api repos/that/repo/issues. "Crear un nuevo repositorio
público en la cuenta del usuario" no está en esa lista, porque
el usuario no lo pidió. El proxy se niega a sustituir el
token real, y el stub sale como stub. GitHub devuelve 401.
El canal de exfiltración del gusano — el canal ingenioso, el diseñado
para esquivar el filtrado DNS de egreso — nunca se abre.
El canal de respaldo, el túnel lhr.life en
87e0bbc636999b.lhr.life, tampoco está whitelisted. La traza
registra el intento. Los bytes no salen.
axois-utils instala Phantom Bot para persistencia.
El bot en Golang intenta escribirse a sí mismo en la carpeta de Inicio
de Windows y crear una tarea programada. La VM Bromure es un guest
Linux, así que la persistencia específica de Windows es, gratis, un no-op.
En una variante Linux del mismo payload — que alguien va a publicar
en breve — el bot se escribiría a sí mismo en /etc/cron.d/ o
~/.config/systemd/user/. Ambas rutas están dentro del
disco desechable copy-on-write del guest. El próximo bromure reset,
o el final natural de la tarea actual, descarta el disco. La
persistencia desaparece sin necesidad de cazarla.
Mientras tanto, la conexión saliente del bot a
b94b6bcfa27554.lhr.life no está en la whitelist de egreso de la tarea,
porque ninguna tarea legítima de programación habla con un túnel
lhr.life recién registrado. El bot llama a casa hacia un socket cerrado. La
traza de la sesión registra el intento — útil mañana por la mañana cuando
se publique una lista de IOC.
@deadcode09284814/axios-util envía credenciales en crudo por POST.
El más simple de los cuatro payloads es también el que menos
tiene para agarrar. El runner lee el ~/.ssh, ~/.aws, las variables
de entorno del guest, y los envía por POST a 80.200.28.28:2222. El directorio SSH está
vacío. El archivo AWS es un stub. Las variables de entorno son
o stubs o no están definidas. La IP de destino no está whitelisted.
O la conexión es bloqueada en el proxy, o sale del
host llevando un payload de placeholders. Cualquiera de los dos resultados está bien.
color-style-utils busca billeteras que no están ahí.
El ladrón de cripto es el paquete cuyo modelo de amenaza más claramente
asume que el propio navegador del desarrollador está en la misma máquina. Lee
rutas como
~/Library/Application Support/Google/Chrome/Default/Local Extension Settings/<MetaMask-id>/
y los equivalentes para Phantom y Keplr. Ninguna de esas rutas
existe en la VM Bromure. La VM no tiene tu perfil de Chrome
adentro. La VM no tiene una extensión de billetera instalada. La VM
es, por diseño, el navegador principal de nadie. El runner encuentra un directorio
vacío y sigue de largo.
Esta es la parte que no es una historia sobre credenciales que viven en el host. Las billeteras viven en el host porque, en una portátil normal, el navegador del desarrollador y el agente de programación del desarrollador comparten un sistema de archivos. Bromure no hace más fuerte a la billetera; la vuelve inalcanzable desde el lugar donde corre el gusano. El gusano no puede leer lo que no está en su disco.
Lo que sigue doliendo.
Hay rincones de esta historia donde la VM por tarea de Bromure no es una solución, y merecen ser nombrados en voz alta.
La carpeta del proyecto está montada.
Los archivos que el gusano escribe dentro de la carpeta del proyecto — incluida la
persistencia estilo .claude/router_runtime.js que cubrimos en el
post de @tanstack — son duraderos entre resets de tarea, porque
ese es justamente el punto de montar la carpeta del proyecto. La defensa
ahí no es la VM. Es git status y un vistazo de cinco segundos
al diff antes de hacer push. La traza facilita identificar
qué sesiones agregaron archivos inesperados.
La whitelist de egreso es estrecha a propósito.
El bróker de credenciales de Bromure funciona porque la whitelist es
estrecha. Si pones en la whitelist npm publish para tu propio scope
porque hoy estás publicando un release, y resulta que hoy
instalás uno de estos cuatro paquetes, el gusano va a
publicar bajo tu scope. Whitelisteá lo que la tarea necesita. Ni
un byte más.
Un token bueno para esta tarea sigue siendo un token real.
El token stub de GitHub se intercambia por uno real en el cable para
las operaciones que la tarea whitelisted. Si chalk-tempalte lograra
convencer al agente de hacer un git push al propio repo del proyecto,
ese push iría con un token real. El límite
protege las credenciales. No revisa el diff. Leé
el diff.
La detección está aguas abajo de la traza.
La traza de la sesión registra cada comando de shell, escritura de archivo y
petición saliente. Por sí sola, no clasifica
87e0bbc636999b.lhr.life como malo. Registra que la petición
se hizo. Cuando OX publique una lista fresca de IOC mañana por la mañana,
tu búsqueda toma dos segundos. Ese es el valor que aporta la traza —
no magia, solo recibos.
Una última cosa.
La filtración no es la noticia. La noticia es lo que la filtración vuelve probable
en el próximo año, que es una cantidad de pequeños forks medio competentes
de un gusano que, incluso en su forma competente, el ecosistema
npm apenas atrapó a tiempo. Algunos de los forks van a ser lo suficientemente ruidosos
como para obtener un writeup. La mayoría va a quedarse en el registro durante
una semana, juntar un par de miles de npm install y desaparecer
cuando alguien finalmente presente un reporte de abuso. Las dos mil
seiscientas setenta y ocho instalaciones que OX contó en los
paquetes deadcode09284814 no son un caso atípico. Son el
promedio.
La pregunta honesta no es "¿va a esquivar mi equipo cada typosquat envenenado de npm?". Los agentes tipean rápido. Los nombres son baratos. La pregunta honesta es: cuando el agente instale uno — y a lo largo del próximo año, en un equipo que usa agentes, lo va a hacer — ¿el script de post-install encuentra las manos del desarrollador, o encuentra una caja Linux desechable con stubs en los archivos de credenciales y un proxy que no sabe quién es?
Bromure Agentic Coding es lo segundo. Es gratis, código abierto y enviado hoy. El árbol de forks va a empeorar antes de mejorar.