Volver a todas las publicaciones
Publicado el · por Renaud Deraison

El paquete sí era de Red Hat

Entre finales de mayo y el 1 de junio de 2026, un gusano llamado Miasma metió código robacredenciales en 32 paquetes bajo el ámbito npm @redhat-cloud-services — el propio namespace de Red Hat, ~117.000 descargas semanales, firmado por el pipeline de publicación real de Red Hat. No había ningún typosquat que detectar ni ningún mantenedor desconocido que señalar. La señal de confianza era el nombre del proveedor en el ámbito, y el nombre del proveedor es exactamente aquello con lo que el atacante se coló. Aquí está por qué «prefiere editores reputados» dejó de ser una defensa, y qué cambia cuando el agente que ejecuta la instalación vive en una VM Bromure por perfil.

Los ataques de cadena de suministro que documentamos en mayo tenían una pista. Una URL de Git donde debería ir un rango de versiones. Un runtime de Bun apareciendo de la nada. Una dependencia opcional que fallaba a propósito. Miasma no tiene ninguna pista. Los paquetes eran @redhat-cloud-services/sources-client y treinta y uno de sus hermanos — genuinamente de Red Hat, bajo el propio ámbito npm de Red Hat, publicados por el propio pipeline de Red Hat con una firma válida. Nada estaba falsificado. El atacante no necesitaba falsificar nada. El nombre en el ámbito era todo el exploit.

Un agente de codificación que resuelve @redhat-cloud-services/vulnerabilities-client no duda, y tú tampoco lo harías. Es una dependencia de primera mano de uno de los mayores proveedores empresariales de la industria. No hay mantenedor que examinar, porque el mantenedor es Red Hat. No hay ningún nombre que escudriñar en busca de una letra transpuesta, porque el nombre está escrito exactamente como debería estar. Cada heurística que un desarrollador cuidadoso o un agente cuidadoso aplica antes de ejecutar npm install da verde. Así que la instalación corre, y un hook preinstall se dispara, y el hook es un blob de 4,2 megabytes de JavaScript ofuscado que empieza a leer el sistema de archivos en busca de llaves.

Todo el incidente es una demostración de un hecho incómodo: editor reputado no es un control de seguridad. Parece serlo. La mayoría de los consejos sobre cadena de suministro de los últimos tres años se apoyan en él. Y el 29 de mayo no compró absolutamente nada.

Qué hizo Miasma.

La campaña — la cadena Miasma: The Spreading Blight aparece por primera vez en un commit fechado el 29 de mayo de 2026, según OX Security — fue detectada por Aikido y OX Security y posteriormente analizada por Socket, JFrog, Wiz, ReversingLabs, Microsoft y otros. BleepingComputer y The Hacker News la cubrieron ambos el 1 de junio.

La forma, reducida a su mecánica:

  • El punto de entrada fue una cuenta de GitHub comprometida de un empleado de Red Hat, usada para empujar commits maliciosos a los repositorios fuente de @redhat-cloud-services.
  • Un workflow de GitHub Actions llevaba un script _index.js que se autenticaba en el endpoint de publicación confiable de npm usando un token OIDC — el mismo mecanismo sin llaves que npm ahora recomienda por encima de los tokens de publicación de larga duración. Desde el lado de npm, la CI de Red Hat publicó los paquetes de Red Hat. La firma era real.
  • Los paquetes publicados llevaban un hook "preinstall": "node index.js" y una carga útil ofuscada de aproximadamente 4,2 MB.
  • Al instalarse, la carga útil barría en busca de secretos de GitHub Actions, credenciales de AWS, credenciales de Google Cloud, service principals de Azure, tokens de HashiCorp Vault, tokens de cuenta de servicio de Kubernetes, tokens de publicación de npm y PyPI, llaves SSH, credenciales de Docker, llaves GPG y archivos .env — luego cifraba y exfiltraba todo lo que encontrara.
  • Se autopropagaba usando el acceso robado para hacer commits a través de la API de GitHub, leyendo archivos action.yml por GraphQL y escribiendo nuevos workflows de vuelta mediante mutaciones, de modo que los cambios aparecían, en las propias palabras de Red Hat en el log de commits, verified y signed.

En total, fueron afectados 32 paquetes en 96 versiones, paquetes con aproximadamente 117.000 descargas semanales, y la campaña más amplia tocó 309 repositorios de GitHub. La evaluación de Socket fue que esto es «efectivamente una campaña Mini Shai-Hulud: usa las mismas tácticas centrales de ejecución en tiempo de instalación, cosecha de credenciales, focalización de CI/CD, exfiltración cifrada y propagación potencial hacia abajo.» La declaración de Red Hat fue que «los paquetes están estrictamente limitados al desarrollo interno, y el código malicioso nunca se publicó para consumo de clientes» — lo cual es cierto, y también no es mucho consuelo para los desarrolladores y runners de CI que tiraron de @redhat-cloud-services/* como dependencia transitiva en la ventana antes de que los paquetes fueran retirados.

La defensa que todo el mundo recomienda es la que se rompió.

Escribimos sobre un gusano similar hace tres semanas — el compromiso de TanStack, donde la pista delatora era un script prepare colgando de una URL de Git fijada y un runtime de Bun que apareció de la nada. La lección honesta de aquel post fue: no confíes en el lockfile, no confíes en la firma. Miasma es la siguiente vuelta del mismo tornillo, y vale la pena ser preciso sobre qué es diferente, porque la diferencia es la cuestión entera.

El conjunto estándar de higiene de cadena de suministro tiene tres peldaños. Fija tus versiones. Verifica la procedencia. Prefiere editores reputados. Miasma atraviesa los tres de frente. Fijar no hace nada, porque las versiones maliciosas son las versiones publicadas. La procedencia no hace nada, porque la procedencia es válida — el flujo de publicación confiable OIDC genuinamente era la CI de Red Hat, y hacia abajo el gusano acuñó commits de workflow que el propio GitHub marcó como verified y signed. Y el tercer peldaño, prefiere editores reputados, no solo es derrotado aquí — es la superficie de ataque. La reputación del ámbito @redhat-cloud-services es la razón por la que los paquetes se tiran sin una segunda mirada. Cuanto más confiable es el namespace, más útil le resulta a quienquiera que se apodere de él.

No hay ninguna versión de «lee el paquete con más cuidado» que atrape esto. El paquete está bien. El paquete es de Red Hat. El problema es que la ejecución de código en tiempo de instalación con las credenciales ambientales del desarrollador es el contrato, y un nombre confiable no hace nada por cambiar lo que ese código puede tocar una vez que corre.

LAPTOP DESARROLLADOR / RUNNER CI — secretos del host visibles para lo que ejecute la instalaciónPIPELINE PROPIO DE RED HATcuenta de empleadoGitHub comprometida → pushGH Actions: publicación OIDCfirma: VÁLIDAprocedencia: realnpm: @redhat-cloud-servicessources-clientvulnerabilities-clientrbac-client … (32 pkgs)~117k descargas semanalespreinstall: node index.jsAGENTE DE CODIFICACIÓN — sin razón para dudartool: bashnpm i @redhat-cloud-services/sources-clientámbito de proveedor de primera mano ⇒ verde↳ preinstall corre node index.js↳ carga útil ofuscada de 4,2 MBSISTEMA DE ARCHIVOS Y ENV DEL HOST — todo real, todo legible por el hook preinstall~/.aws, GCP, SPNs de Azurellaves cloud (reales)tokens Vault, tokens SA kubeacceso al clúster (real)~/.npmrc, token PyPIpublica aquí (real)~/.ssh, keyring GPGfirma + push (reales)$GITHUB_TOKEN, archivos .envsecretos de CI (reales)tar | cifrar | exfiltrarAUTOPROPAGACIÓNacceso robado a GitHubleer action.yml (GraphQL)commit de workflows envenenadosaparecen como: verified, signed309 repos en la campañael siguiente ámbito hereda la confianza
Miasma en una máquina de desarrollador o un runner de CI que resuelve @redhat-cloud-services directamente. Una cuenta de empleado comprometida empuja a los repos de Red Hat; el flujo de publicación confiable OIDC firma las versiones maliciosas como genuinamente de Red Hat; npm las sirve con una firma válida. El agente instala una dependencia de primera mano sin razón para dudar. El hook preinstall ejecuta node index.js — un blob de 4,2 MB — que barre el host en busca de AWS, GCP, Azure, Vault, Kubernetes, tokens de npm/PyPI, llaves SSH y GPG, y archivos .env, los cifra, los envía fuera, y usa el acceso robado a GitHub para hacer commit de nuevos workflows envenenados que aparecen como verified y signed. Sin typosquat, sin mantenedor falso, sin truco de URL de Git. La señal de confianza es el namespace, y el namespace es real.

La misma instalación dentro de Bromure Agentic Coding.

Bromure Agentic Coding ejecuta tu agente de codificación dentro de una VM Linux por perfil — su propio kernel, su propio sistema de archivos, su propia pila de red, sobre el framework de Virtualización de Apple. Un perfil es un ámbito de trabajo coherente: este cliente, este producto interno, esta biblioteca open-source. El agente hace sus npm install ahí dentro, y el host — tu keychain real, tus credenciales cloud reales, tus llaves SSH reales — está al otro lado de una frontera reforzada por hardware que el hook preinstall no puede cruzar.

Las credenciales no viven en el perfil. Viven en el host, detrás de un intermediario de credenciales. Cuando el agente necesita empujar un commit o publicar un paquete, no lee un token del sistema de archivos del invitado — no hay ninguno que leer. Le pide al intermediario, por un socket de dominio Unix, que use una credencial en su nombre. El intermediario guarda la clave privada real de la GitHub App, acuña un token de instalación de corta duración con ámbito al repo en el que el agente ya estaba trabajando, y — para un perfil configurado para requerirlo — presenta un aviso de autorización que el desarrollador responde en el host antes de que la petición salga. El token se acuña y se adjunta a la petición saliente enteramente del lado host; nunca entra en la memoria ni en el disco del invitado. El principio, que es tan viejo como ssh-agent: intermedia el uso de la credencial, nunca su valor.

Así que recorramos el barrido de Miasma a través de esa frontera. node index.js lee ~/.aws/credentials y encuentra un stub o nada. Lee ~/.npmrc y no encuentra ningún token de publicación. Lee el entorno en busca de $GITHUB_TOKEN y no encuentra nada que robar — el token de instalación se acuña y se gasta en el host, nunca se escribe en el invitado, y el intermediario solo acuña uno cuando el desarrollador responde el aviso de autorización. El keyring GPG, la clave privada SSH, el token de Vault, el kubeconfig: del lado host, intermediados si están expuestos siquiera, ausentes si no. La carga útil de 4,2 MB corre hasta completarse exactamente como fue diseñada. Solo que exfiltra un invitado que nunca tuvo las llaves del desarrollador.

AGENTE EN EL HOSTnpm i @redhat-cloud-services/sources-clientpreinstall → node index.jslee el host directamenteCREDENCIALES REALES — leídas en texto plano~/.aws/credentialsAKIA… real~/.npmrc _authTokennpm_… real$GITHUB_TOKENghp_… real~/.ssh/id_ed25519clave privadaVault, SA kube, GPGtodo real→ cifrar + exfiltrar→ republicar, propagarAGENTE EN UNA VM BROMURE POR PERFILINVITADO — lo que el hook preinstall puede ver~/.aws, ~/.npmrcstub / ausente~/.ssh, GPG, Vaultno en disco¿necesitas empujar?→ pide al intermediario por socket Unixaviso de autorización → usado del lado host(«dame la llave» no es un verbo)HOST — el intermediario guarda las llaves realesclave privada GitHub Appacuña tokens de corta duración, del lado hostel valor nunca cruza la frontera
Izquierda: el agente corre en el host, así que el hook preinstall lee las llaves reales directamente — la cadena de Miasma se completa. Derecha: el agente corre en una VM Bromure por perfil. Las credenciales reales están en el host detrás de un intermediario. Cuando el agente legítimamente necesita empujar, le pide al intermediario por un socket Unix y el intermediario acuña un token de instalación de corta duración con ámbito al repo usado del lado host — sujeto a un aviso de autorización que el desarrollador responde en el host — y el token nunca aterriza en el invitado. El hook preinstall malicioso, leyendo el sistema de archivos y el entorno del invitado, encuentra stubs y ningún token en absoluto. El radio de explosión es un perfil, no el keychain entero del desarrollador.

El push que el proxy se niega a reenviar.

Robar la llave es solo la mitad de Miasma. La otra mitad es la propagación: usó el acceso robado a GitHub para hacer commit de workflows envenenados de vuelta a través de la API, y eso es lo que convirtió una mala instalación en 309 repositorios. El intermediario maneja el robo. Los Guardrails, añadidos en Bromure Agentic Coding 2.0, manejan el mal uso.

Los Guardrails son un motor de políticas del lado host que vive dentro del mismo proxy MITM por el que ya fluye el tráfico intermediado, de modo que un agente comprometido en el invitado no puede rodearlos. Cada petición se clasifica por lo que realmente le hace al recurso, y cada recurso — GitHub, AWS, Kubernetes, registros de Docker, DigitalOcean, GitLab, Bitbucket, bases de datos alojadas — puede ponerse en off, bloquear destructivos o solo lectura. Pon el guardrail de GitHub de un perfil en modo solo lectura y un git push — el git-receive-pack que el gusano necesita para escribir su workflow de vuelta — devuelve un 403 duro, mientras que git fetch sigue funcionando. Un DELETE contra la API de Kubernetes, un borrado de manifiesto en un registro, una llamada Terminate* a EC2: el mismo trato. El agente solo ve un fallo normal de la API. El paso de propagación de Miasma es una escritura que el proxy declina reenviar, haya llegado o no el agente cerca de una credencial real.

¿Y qué hay de la persistencia?

Aquí es donde el modelo por perfil es honesto sobre un costo. Miasma es un gusano; toda su ambición es regresar. En una fantasía de disco desechable, eso lo descartas — el disco se va después de la tarea. Un perfil Bromure es de larga duración, así que una carga útil que se escribe a sí misma en un script de arranque dentro del perfil puede sobrevivir hasta la siguiente sesión del agente en ese perfil. No vamos a pretender lo contrario.

Lo que esa persistencia hereda, sin embargo, es un invitado sin llaves del host y un intermediario que solo habla en tokens de corta duración y ámbito limitado. El gusano se despierta en el mismo sandbox en el que murió. Puede leer los mismos stubs. Puede pedirle al intermediario que use una credencial para el único repo para el que este perfil está autorizado — y el desarrollador todavía tiene que responder el aviso de autorización para que eso vaya a algún lado, con los Guardrails libres de rechazar el push de plano. No puede alcanzar el keychain del host, los otros perfiles, ni las credenciales cloud del desarrollador, porque esas nunca estuvieron dentro de la frontera para empezar. La persistencia compra presencia continuada en una caja sin nada dentro.

Y cada parte de eso — el preinstall disparándose, node index.js cargando un blob de varios megabytes, el archivo escrito en una ruta de arranque, el intento de salida — aterriza en el trazo de sesión a nivel de hipervisor. Cuando Aikido publica los indicadores a la mañana siguiente, la pregunta «¿este perfil llegó a ejecutar Miasma alguna vez?» es un grep, no una intervención de respuesta a incidentes.

Donde esto no te salva.

El ámbito del intermediario es todo el juego.

Si un perfil está aprovisionado hoy para publicar en tu ámbito npm, y ese perfil instala Miasma hoy, el intermediario lo dejará publicar. La intermediación funciona porque la concesión es estrecha y de corta duración. Un perfil que no necesita publicar no debería poder hacerlo. Dale ámbito a propósito.

No revisa el diff.

Miasma se propagó haciendo commit de workflows que aparecían verified y signed. Un guardrail de GitHub en solo lectura bloquea el push de plano — pero un perfil que legítimamente necesita empujar corre en modo bloquear-destructivos, y los Guardrails clasifican por método, no por lo que hay en el diff. Ni el aislamiento ni un guardrail a nivel de método impiden que a un agente lo convenzan de hacer commit de un workflow envenenado que tiene permitido empujar. Lee el diff. El trazo te dice qué diffs leer.

El portapapeles se comparte por defecto.

Bromure viene con el intercambio de portapapeles entre host e invitado activado, porque pegar un stack trace en un chat es algo que los humanos hacen todo el día. Para un perfil sensible, aísla el portapapeles. El control existe; simplemente no es el defecto.

El trazo es un log de auditoría, no un IDS.

El trazo de sesión registra el preinstall, el blob, la salida. No decide, por sí solo, que el destino es hostil. Captura lo suficiente para que, una vez que alguien nombre el indicador, tu respuesta esté a dos segundos de distancia.

El siguiente ámbito ya es de confianza.

La lección del gusano de TanStack fue que el lockfile y la firma no son defensas. Miasma añade el incómodo corolario: tampoco lo es el editor. El ámbito @redhat-cloud-services no hizo nada malo por ser de confianza — ser de confianza es el propósito entero de un namespace de proveedor, y es exactamente lo que lo hizo digno de atacar. La próxima campaña se colará en un ámbito en el que confías igual de mucho, firmado con igual validez, por un pipeline que genuinamente era del proveedor hasta el momento mismo en que dejó de serlo.

No puedes arreglar eso confiando con más cuidado. Lo arreglas disponiendo las cosas de modo que «qué ámbito publicó esto» deje de ser la pregunta de la que depende tu keychain. Bromure Agentic Coding es la configuración donde el agente hace sus instalaciones dentro de una VM por perfil, las credenciales reales se quedan en el host detrás de un intermediario, y lo peor que un hook preinstall puede hacer es exfiltrar una caja que nunca tuvo tus llaves. Es gratis, open-source, y se entrega hoy.