Volver a todas las publicaciones
Publicado el · por Renaud Deraison

El repositorio sí era de Microsoft

El 5 y 6 de junio de 2026, el gusano Miasma metió código robacredenciales en 73 repositorios de cuatro de las propias organizaciones de GitHub de Microsoft — Azure, Azure-Samples, microsoft, MicrosoftDocs — incluido Azure/functions-action, la Action de despliegue oficial, y durabletask, un repo que ya había sido limpiado una vez en mayo. Esta vez la carga útil no esperó al npm install. Se disparó en el momento en que un desarrollador abrió el repositorio en Claude Code, Cursor, Gemini CLI o VS Code. Aquí está por qué la señal de confianza — «es un repo de Microsoft» — fue de nuevo la superficie de ataque, y qué cambia cuando el agente que lo abre vive en una VM Bromure por perfil, detrás de un intermediario de credenciales, un guardrail de lectura/escritura y un período de espera para paquetes.

Hace una semana el gusano se coló por el ámbito npm de Red Hat y se disparó a través de un hook preinstall. Esta semana se coló por el GitHub de Microsoft y no necesitó ninguna instalación en absoluto. Miasma plantó configuración con ámbito de proyecto en 73 repositorios propiedad de Microsoft — Azure/functions-action, la Action de despliegue oficial, entre ellos — y la carga útil se ejecutó en el momento en que un desarrollador abrió el repo en Claude Code, Cursor, Gemini CLI o VS Code. Clonar no era el disparador. Abrirlo en tu agente sí.

Un desarrollador que clona Azure/functions-action para depurar un despliegue que falla no duda, y tampoco lo hace el agente al que lo apunta. Es un repositorio de primera mano de la propia organización Azure de Microsoft — la fuente canónica de la GitHub Action que media el ecosistema referencia como Azure/functions-action@v1. No hay mantenedor que examinar, porque el mantenedor es Microsoft. No hay ningún nombre que escudriñar, porque el nombre es exactamente lo que debería ser. Así que el repo se abre, y el agente lee la configuración del proyecto como hace toda herramienta de codificación moderna al abrir la carpeta — y uno de esos archivos de config apunta a un comando, y el comando es un blob de aproximadamente 4,3 megabytes que empieza a leer el sistema de archivos en busca de llaves.

Documentamos la mitad npm de esta misma campaña hace siete días. Miasma es el mismo gusano — una variante del código «Mini Shai-Hulud» que TeamPCP publicó abiertamente a mediados de mayo — y el hecho incómodo que demuestra es el mismo, girado una vuelta más: fuente reputada no es un control de seguridad. Parece serlo. La mayoría de los consejos sobre cadena de suministro se apoyan en ello. Y el 5 de junio no compró absolutamente nada, dos veces: el namespace era de Microsoft, y la ejecución no vino de un paquete que elegiste instalar. Vino de abrir una carpeta.

Qué le hizo Miasma a los repositorios de Microsoft.

El 5 y 6 de junio de 2026, GitHub deshabilitó 73 repositorios de cuatro organizaciones de GitHub de Microsoft después de que se empujaran commits maliciosos a ellos, según The Hacker News y un desmontaje detallado de StepSecurity. Redmond Magazine lo cubrió el 8 de junio. El desglose:

  • Azure — 49 repositorios, incluidos Azure/functions-action (la Action de despliegue oficial de Functions) y los language workers para .NET, Python, Java, Go y PowerShell.
  • microsoft — 10 repositorios.
  • Azure-Samples — 13 repositorios.
  • MicrosoftDocs — 1 repositorio.

La forma, reducida a su mecánica:

  • El punto de entrada fue una cuenta de contribuidor previamente comprometida con acceso de commit, usada para empujar commits maliciosos directamente a los repositorios — etiquetados [skip ci] para que los cambios pasaran de largo los controles de CI/CD que de otro modo se habrían ejecutado.
  • El commit plantó configuración con ámbito de proyecto — el tipo de archivo que un agente de codificación o un IDE lee y sobre el que actúa automáticamente cuando abres la carpeta: una task de editor, un hook de agente, un servidor MCP definido en el proyecto. Esta es la misma clase de frontera de confianza que TrustFall de Adversa AI demostró en Claude Code, Cursor CLI, Gemini CLI y Copilot CLI — los cuatro ejecutan configuración definida en el proyecto justo después del aviso de confianza de la carpeta.
  • La carga útil — aproximadamente 4,3 MB de código ofuscado — se ejecutó cuando el repositorio se abrió en Claude Code, Gemini CLI, Cursor o VS Code, o se corrió a través de un script npm test. No solo al clonar. El acto de apuntar tu agente al árbol clonado es lo que lo ejecutó.
  • Al ejecutarse, barría el host en busca de tokens de GitHub, llaves de AWS, service principals de Azure, credenciales de GCP, tokens de publicación de npm y PyPI, llaves SSH y archivos .env, luego usaba el acceso robado para hacer commit de sí mismo más adelante — que es lo que lo convierte en un gusano y no en un disparo único.

Un detalle merece detenerse: Azure/durabletask estaba entre los repositorios afectados — y ya había sido comprometido en mayo en la campaña de TeamPCP y limpiado. Un repo que fue remediado una vez fue reenvenenado cinco semanas después. La limpieza no es un estado que alcanzas y conservas; es un estado del que te caes en el momento en que otra credencial de la cadena es robada.

Vale la pena ser igual de preciso sobre lo que no pasó. La red corporativa de Microsoft no fue vulnerada. Azure, el servicio en la nube, no fue vulnerado. No se tocó ningún dato de cliente ni ningún sistema de producción. Este fue un ataque a repositorios de código fuente — y su consecuencia de mayor alcance no tuvo nada que ver con el malware en absoluto: en el instante en que GitHub deshabilitó Azure/functions-action, todo pipeline del planeta que referenciaba Azure/functions-action@v1 dejó de resolver. Microsoft fue el portador de más alto perfil. La gente que de verdad fue comprometida fueron los desarrolladores que abrieron los repos envenenados en un agente entre el 3 y el 5 de junio, y que vieron sus credenciales barridas de sus propias máquinas.

LAPTOP DESARROLLADOR — secretos del host visibles para lo que el agente ejecute al abrir la carpetaGITHUB PROPIO DE MICROSOFTcontribuidor comprometidocuenta → push [skip ci]Azure/functions-actionAzure/durabletask (otra vez)+ config de proyecto plantadaEL DESARROLLADOR CLONAgit clone …/functions-actionaún no corre nadarepo de primera mano ⇒sin razón para dudarABIERTO EN EL AGENTE — la carga útil se disparaClaude Code · Cursor · Gemini CLI · VS Codeabrir carpeta → lee config de proyectohook de agente / task / servidor MCP↳ ejecuta carga útil de 4,3 MB(clonar solo NO la ejecutó)SISTEMA DE ARCHIVOS Y ENV DEL HOST — todo real, todo legible por la carga útil$GITHUB_TOKEN, gh hosts.ymlacceso de push (real)~/.aws, service principals Azurellaves cloud (reales)~/.npmrc, token PyPIpublica aquí (real)~/.ssh/id_ed25519, .envllaves + secretos CI (reales)tar | cifrar | exfiltrar→ cosechado de tu máquinaAUTOPROPAGACIÓNacceso robado a GitHubcommit al siguiente repoplanta la misma config73 repos, 4 orgsdurabletask: golpeado dos veces
Miasma en una máquina de desarrollador que abre un repo de Microsoft afectado en un agente de codificación. Una cuenta de contribuidor previamente comprometida empuja un commit [skip ci] que planta configuración con ámbito de proyecto — un hook de agente, una task de editor, una definición de servidor MCP. El desarrollador clona el repo (nada se ejecuta) y lo abre en Claude Code, Cursor, Gemini CLI o VS Code. Al abrir la carpeta el agente lee esa configuración y actúa sobre ella, ejecutando una carga útil de 4,3 MB que barre el host en busca de tokens de GitHub, llaves de AWS, service principals de Azure, tokens de npm/PyPI, llaves SSH y archivos .env, los cifra, los envía fuera, y usa el acceso robado a GitHub para hacer commit de sí mismo en el siguiente repo. Sin typosquat, sin mantenedor falso, sin npm install. La señal de confianza es el nombre de la org de Microsoft, y la ejecución viene de abrir una carpeta.

El mismo repo, abierto 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 servicio interno, este repo open-source que clonaste para depurar un despliegue. Clonas Azure/functions-action en ese perfil y lo abres con tu agente ahí dentro. El disparador de abrir la carpeta se dispara exactamente como fue diseñado. La carga útil corre. Y entonces se pone a buscar llaves en un host que no puede alcanzar.

Porque las credenciales no están en el perfil. La VM viene con stubs — tokens falsos que parecen reales para git, gh, aws, kubectl, npm y cualquier otra cosa que espere un encabezado Authorization. Un proxy en tu Mac se sienta delante de cada conexión que sale del sandbox, reconoce el stub, y lo intercambia por el secreto real en el cable a medida que la petición sale (el sandbox que guardaba la llave recorre el mecanismo). El PAT real de GitHub, la llave real de AWS, el principal real de Azure — ninguno de ellos toca un archivo, una variable de entorno ni una página de memoria que la VM pueda leer. Las llaves SSH nunca salen del Keychain de macOS en absoluto; solo se reenvía hacia dentro el socket de ssh-agent, tal como OpenSSH siempre pretendió.

Así que recorramos el barrido de Miasma a través de esa frontera. La carga útil lee el entorno en busca de $GITHUB_TOKEN y encuentra un stub. Lee ~/.aws y no encuentra nada. Lee ~/.npmrc y no encuentra ningún token de publicación. Lee ~/.ssh y no encuentra ningún archivo de llave — hay un socket reenviado, no una clave privada en disco. El blob de 4,3 MB corre hasta completarse exactamente como fue escrito. Solo que exfiltra una caja que nunca tuvo tus llaves, en el lado equivocado de una frontera reforzada por hardware respecto a todo lo que importa.

VM BROMURE POR PERFILel agente abre functions-actionconfig proyecto → carga útil se disparacorre, pero solo dentro del invitadoLO QUE VE LA CARGA ÚTIL$GITHUB_TOKENstub_7f3a…~/.aws, ~/.npmrcstub / ausentearchivo de llave ~/.sshsolo socketpropagar: git push (a sí mismo)necesita escribir → pide al proxyexfil llaves host: nada que tomar¿necesita una dependencia?npm install → la descarga sale de la VMa través del proxy de BromurePROXY · TU MACINTERMEDIARIO DE CRED.PAT / llaves reales aquístub → real, en el cableel valor nunca entra en la VMGUARDRAIL: LECT./ESCR.push = mutar → AVISOnombra verbo + destinoCADENA DE SUMINISTROescaneo OSV + socket.devespera: < 2 días retenidoscripts de install eliminadosRESULTADOllaves del host:stubs cosechados,las reales intactaspush para propagar:en pausa, dices nopaquete malo reciente:nunca llega a la VMradio de explosión = 1 perfil
Tres capas, en el orden en que Miasma las golpea. (1) Cadena de suministro: el proxy escanea cada descarga de paquete contra OSV y socket.dev y pone en cuarentena las versiones más jóvenes que el período de espera — así el agente ni siquiera puede tirar de una dependencia maliciosa recién publicada mientras el ecosistema aún se pone al día. (2) Intermediación de credenciales: la VM solo guarda stubs; los secretos reales están en el host detrás del proxy, intercambiados en el cable, así que el barrido del host de la carga útil encuentra marcadores de posición. (3) Guardrails: el paso de propagación del gusano — un git push para hacer commit de sí mismo más adelante — es una llamada que cambia estado, y un guardrail de lectura/escritura lo detiene en el cable y pregunta, nombrando el verbo y el destino. Cada capa se aplica por debajo del agente, en la frontera de la VM que el agente no puede rodear.

El paso de propagación es una escritura, y las escrituras reciben un aviso.

Robar llaves es solo la mitad de Miasma. La otra mitad es la propagación: usó el acceso robado a GitHub para hacer commit de sí mismo en el siguiente repositorio, y eso es lo que convirtió un puñado de repos envenenados en 73. Incluso en un perfil que legítimamente tiene acceso de push — digamos que clonaste functions-action precisamente porque pretendes abrir un PR contra él — el paso de propagación del gusano todavía tiene que salir a través del proxy, y ahí es donde los Guardrails se topan con él.

Los Guardrails leen la operación, no solo la conexión — distinguen una lectura de una escritura. Un git fetch es una lectura; un git push es una escritura. Pon la credencial de GitHub de un perfil en preguntar al escribir, y en el momento en que el agente echa mano de una llamada que cambia estado — el git-receive-pack que el gusano necesita para hacer commit de su config de vuelta, un DELETE contra una API, un Terminate* en EC2 — Bromure lo detiene en el cable y hace aparecer un aviso en tu Mac que nombra el verbo, el destino y el perfil. La concesión que das está acotada en el tiempo: quince minutos para una publicación, de un solo uso para las que dan miedo, nunca si la petición no tiene sentido. Las lecturas nunca te interrumpen; el agente hace fetch y grep y lee todo el día. Es la mutación lo que se pausa.

Esta es la diferencia entre «el agente tiene un token» y «el agente puede hacer lo que quiera con el token». Todo el mecanismo de propagación de Miasma es una escritura que el agente nunca te dijo que estaba haciendo — y una escritura que el agente nunca te dijo que estaba haciendo es exactamente lo que el aviso de lectura/escritura está construido para atrapar. El push que propaga el gusano se convierte en un cuadro de diálogo en el que haces clic en No permitir, del mismo modo en que «el agente borró la base de datos de producción» deja de ser un postmortem y se convierte en un aviso que rechazaste.

La versión tenía horas, y Bromure hace que los paquetes envejezcan.

Hay una segunda manera en que Miasma — y el linaje Mini Shai-Hulud más amplio — alcanza a un desarrollador: no a través de un repo que abres, sino a través de un paquete recién envenenado que el agente instala mientras hace su trabajo. La mitad de Red Hat de esta campaña fue precisamente eso, un hook preinstall en 32 paquetes de un ámbito de confianza. Y el detalle brutal de esos incidentes es el momento: una versión comprometida típicamente se detecta y se retira en cuestión de horas — pero esas son exactamente las horas durante las cuales un agente autónomo, corriendo sin supervisión, podría tirar de ella.

La capa de Cadena de suministro de Bromure convierte el mismo proxy de frontera en un punto de control de escaneo, y hace las dos cosas que de verdad importan contra un compromiso del mismo día:

  • Fuerza el escaneo de cada descarga contra socket.dev además de OSV. OSV atrapa los CVE conocidos por encima del umbral de severidad que fijes. socket.dev atrapa lo que las bases de datos de vulnerabilidades aún no han alcanzado — scripts de install maliciosos, malware conductual, typosquats, el compromiso recién publicado. Una versión marcada se bloquea antes de que el tarball llegue siquiera a la VM. Crucialmente el escaneo corre por debajo del agente, en el proxy: por mucho que el agente reescriba su propia config para rodearte, la descarga sigue saliendo por la frontera que no puede cruzar.
  • Impone un período de espera. Bromure pone en cuarentena cualquier versión publicada en los últimos dos días — ajustable — de modo que una versión subida hace una hora sencillamente no es instalable en ese perfil mientras el ecosistema se pone al día. Contra un gusano cuya ventana entera de oportunidad es el hueco entre publicar y retirar, un período de espera no es una heurística sobre si un paquete parece malo. Es una negativa a ser el primero en averiguarlo. Combínalo con la eliminación de scripts de install que Bromure hace al vuelo — sacando los hooks postinstall del tarball y arreglando el hash de los metadatos para que la instalación todavía verifique — y el paquete que sí llega, llega inerte.

Para Miasma en concreto, el vector de abrir-el-repo es el titular. Pero la misma campaña se propaga también a través de paquetes, y el período de espera es el control que habría matado de hambre el lado npm de ella: una versión reciente de @redhat-cloud-services, o una dependencia transitiva recién envenenada tirada mientras depuras ese repo de Microsoft, se queda en cuarentena durante las horas exactas en que es peligrosa.

Donde esto no te salva.

Un push que apruebas es un push que ocurre.

El guardrail de lectura/escritura atrapa la escritura de la que el agente no te avisó. No lee el diff. Si legítimamente estás empujando a functions-action y apruebas el aviso, Bromure reenvía el push — incluyendo, en principio, un workflow envenenado que no notaste en el diff. Lee lo que apruebas. El trazo de sesión te dice qué diffs leer.

El período de espera es una ventana, no un muro.

Dos días está ajustado al hueco observado entre publicar y retirar, pero un atacante paciente puede sentarse sobre una versión comprometida más tiempo que el período de espera y seguir siendo instalable al tercer día. El período de espera mata de hambre a los gusanos del mismo día; no avala un paquete que sencillamente ha envejecido. socket.dev y OSV todavía tienen que cumplir su parte.

El perfil es de larga duración, así que la persistencia persiste.

Un perfil Bromure no es un disco desechable. Una carga útil que se escribe a sí misma en una ruta de arranque dentro del perfil puede sobrevivir hasta la siguiente sesión en ese perfil. Con lo que se despierta es un invitado sin llaves del host y un intermediario que solo habla en tokens de corta duración, con aviso y ámbito limitado — presencia en una caja sin nada dentro — pero presencia al fin y al cabo.

Dale ámbito al intermediario a propósito.

Si un perfil está aprovisionado para empujar a un repo hoy y ese perfil corre Miasma hoy, una escritura aprobada pasa. Las concesiones del intermediario funcionan porque son estrechas. Un perfil que solo necesita leer un repo no debería poder escribirlo; un perfil que nunca publica no debería guardar ningún token de publicación. El aislamiento contiene la explosión; el ámbito decide cuán grande podría haber llegado a ser.

El siguiente repo de confianza ya está clonado en algún sitio.

La lección de el gusano de TanStack fue que el lockfile y la firma no son defensas. La lección de el ámbito de Red Hat fue que tampoco lo es el editor. Microsoft añade el siguiente corolario: tampoco lo es el repositorio, y el disparador ni siquiera tiene que ser una instalación que elegiste — puede ser una carpeta que tu agente abrió. El repo Azure/functions-action no hizo nada malo por ser de confianza. Ser de confianza es el propósito entero de una Action canónica de primera mano, y es exactamente lo que lo hizo digno de envenenar — dos veces, en el caso de durabletask.

No puedes arreglar eso confiando con más cuidado, porque la confianza nunca estuvo mal puesta. Lo arreglas disponiendo las cosas de modo que «qué repo es este» y «qué ámbito publicó esto» dejen de ser las preguntas de las que depende tu keychain. Bromure Agentic Coding es la configuración donde el agente abre el repo dentro de una VM por perfil, las credenciales reales se quedan en el host detrás de un intermediario, cada escritura que el agente hace tiene que pasar un aviso, y un paquete no se puede instalar hasta que haya sobrevivido a un período de espera. Lo peor que un abrir-carpeta envenenado puede hacer es exfiltrar una caja que nunca tuvo tus llaves. Es gratis, open-source, y se entrega hoy.