Un pull request con el título docs: dependency audit notes, dos líneas en un fichero docs/wfx-audit.md y nada más. Con ese sondeo, abierto el 10 de noviembre de 2026 a las 12:45 UTC desde cuentas de GitHub robadas, arrancó la campaña que Aikido Security ha bautizado como wfx-loot: 4.837 cuentas comprometidas comprobaron si tenían permiso de escritura en 61.764 repositorios y, donde lo tenían, metieron un script de unas 1.500 líneas que vacía los secretos de GitHub Actions, la memoria del runner y las credenciales de AWS, GCP, Azure y de los proveedores de IA, para retirarlo en menos de un minuto. Aikido cuenta al menos 1.806 repositorios con indicios de que el payload llegó a ejecutarse, y el recuento es a la baja porque el atacante borra el run después de cada ejecución.
- Desde el 10 de noviembre, 4.837 cuentas de GitHub robadas abrieron pull requests de prueba en 61.764 repositorios y colocaron un stealer de secretos en al menos 8.093, según Aikido y SafeDep.
- El script roba secretos de Actions, memoria del runner, credenciales de AWS, GCP, Azure, SSH y de proveedores de IA, y deja backdoors en la nube que sobreviven a la rotación.
- Los commits se revierten en menos de un minuto y el run se borra. Un hueco en la numeración de runs es la señal más fiable de que el payload corrió.
- Fly.io reconoce que tokens de su GitHub App se filtraron en ficheros .git/config servidos en público, y GitHub dejó a medias la revocación.
- Busca hoy el pull request docs: dependency audit notes, revoca la cuenta, rota todo lo que el job podía leer y elimina las trust policies y credenciales federadas wfx-ci.

El diagrama de SafeDep resume la cadena en seis pasos: un token robado que empuja commits firmados como la víctima, un pull request de prueba, tres formas de colocar el stealer, la ejecución en GitHub Actions, el envío de los datos a servidores HTTP o a un chat de Telegram y el commit que lo deshace todo.
Un pull request inofensivo, un commit de 1.500 líneas y un revert diez segundos después
El atacante no explota ningún fallo de GitHub Actions. Tiene tokens válidos de cuentas de desarrolladores y de bots, y los usa como los usaría su dueño. Según el informe de Aikido, publicado el 12 de noviembre y firmado por Oliver Smith, el primer paso en cada repositorio es un pull request desde una rama llamada wfx-docs cuyo único cometido es confirmar que el token puede escribir. Si la prueba funciona, llega el payload en una de tres variantes. La primera añade unas 1.500 líneas a un workflow que ya existe, con el commit ci: refresh build cache. La segunda crea un script oculto con nombre .ci-xxxxxx.sh y lo engancha a un hook postinstall en package.json, con el commit chore: add ci helper, de modo que se ejecuta en cualquier npm install. La tercera crea un workflow nuevo, build-audit-xxxxxx.yml, en una rama desechable con el patrón chore/xxxxxxxx y el commit chore: add dependency audit workflow.
StepSecurity documenta una cuarta forma, pegar el script al final de un shell que el CI ya ejecuta, como ocurrió con el update.sh de nextcloud/docker. En todas ellas el patrón se repite: entre uno y dos minutos después del commit malicioso llega un revert con el mensaje ci: restore o ci: cleanup, y el atacante borra el run del workflow y sus logs. Endor Labs llama a este patrón commits sándwich, porque el payload queda emparedado entre dos commits de aspecto normal y quien revise el código actual no encuentra nada.

La línea de tiempo que publica Endor Labs con un caso público del 11 de noviembre lo mide: el pull request de prueba en el segundo cero, el commit con el stealer tres segundos después y el revert a los trece segundos. Diez segundos de payload en la rama principal bastan, porque el workflow arranca en cuanto entra el commit. SafeDep, que recorrió los commits de las ramas por defecto públicas entre las 12:46 UTC del 10 de noviembre y las 04:36 UTC del 12, reparte sus 8.093 repositorios con commits maliciosos así: 7.309 por inyección en un workflow existente, 771 por el hook de npm y 16 por un workflow nuevo.
61.764 repositorios sondeados, 8.093 con commits maliciosos y 1.806 con secretos fuera, por qué cada cifra mide otra cosa
Las cifras de la campaña no coinciden entre laboratorios y conviene saber qué mide cada una. Aikido contó 61.839 pull requests con el título docs: dependency audit notes abiertos entre las 12:45 UTC del 10 de noviembre y las 15:00 UTC del 11, desde 4.837 cuentas y sobre 61.764 repositorios. De esos pull requests, 10.431 fueron a repositorios que la cuenta no posee, alcanzados por permisos de colaborador o de organización, y el 92 % seguía abierto al cierre del informe. El pull request es solo la prueba de acceso, y Aikido es tajante con lo que significa que exista: «that user is compromised, even if the workflow stage did not run» (ese usuario está comprometido, aunque la fase del workflow no llegara a ejecutarse).

La captura de StepSecurity muestra la búsqueda en GitHub a las 07:40 UTC del 12 de noviembre, con 63.000 resultados y pull requests abiertos hacía segundos. GitGuardian, que repitió la búsqueda ese mismo día, dio 66.001. StepSecurity contó más de 21.000 commits ci: refresh build cache entre el 10 y el 12 de noviembre, y Endor Labs elevó a 8.926 los repositorios con commits maliciosos en su actualización de las 21:35 UTC del 12. El conjunto de datos de GitGuardian recoge 28.500 parches con payload desde 2.367 cuentas en 17.715 repositorios. Ninguno de esos números equivale a repositorios donde el stealer corrió: para eso Aikido se queda con los 1.806 que muestran señales de ejecución, cifra que en el resumen de su propio informe aparece como 1.835.
🚨 Massive GitHub Actions CI Secret Stealer
— Aikido (@AikidoSecurity) November 12, 2026
4,800+ compromised accounts attempted to push malicious workflows to 61k+ repos. Payload dumps all CI secrets, memory, OIDC & commit secrets, then instantly deletes run logs.
Search your repos for:
• PR: "docs: dependency audit… pic.twitter.com/OJ35ZQe9mA
Aikido publicó el aviso en X el 12 de noviembre con los indicadores básicos para buscar en los repositorios propios. Entre los afectados hay proyectos con peso: Endor Labs localiza 27 repositorios con más de 100 estrellas, el mayor nextcloud/docker con 7.429, y 35 que producen paquetes descargados por terceros. Aikido identifica al menos 50 cuentas comprometidas que publican en npm, Go, Packagist, PyPI o crates. El salto a los registros ya se ha producido en dos sitios. La versión 3.14.3 del paquete digitalelvis/neuronai-studio en Packagist lleva el script y el hook, y según SafeDep se dispara al hacer npm install dentro del directorio vendor, no con composer require, así que recomienda fijar la 3.14.2 o anterior. En Go, las versiones v1.0.278 y v1.0.280 de github.com/onuragtas/docker-env y una pseudoversión de github.com/vkuznet/gocurl contienen el payload solo en un fichero de workflow, que la herramienta de Go nunca ejecuta, y Aikido pide retirarlas igualmente. SafeDep revisó 218 paquetes de npm, Go y Packagist cuyos repositorios recibieron commits maliciosos y no encontró ninguna otra versión envenenada, con la salvedad de que no comprobó PyPI.
Un caso aparte es WebdriverIO. StepSecurity cuenta 56 commits en 28 repositorios de la organización empujados como la cuenta de bot wdio-bot, que podía saltarse la protección de ramas, y un contribuidor los revirtió en 20 minutos sin que ninguna release se viera afectada.
Del toJSON(secrets) a la memoria de Runner.Worker, lo que wfx se lleva y a dónde lo manda
El script es shell, está comentado y no va ofuscado, y usa wfx en sus logs y en sus variables. Aikido lo divide en cuatro fases. En la de recogida serializa todos los secretos y variables del repositorio con las expresiones toJSON(secrets) y toJSON(vars), se queda con GITHUB_TOKEN y con los tokens de runtime y OIDC de Actions, filtra el entorno por palabras como token, secret, aws, azure, gcp, openai o anthropic, y lee los ficheros de credenciales de AWS, Azure, GCP, Docker, Kubernetes, npm, PyPI, Git, SSH, GPG, Vault y Terraform. La lista de proveedores de IA es larga: OpenAI, Anthropic, Gemini, Hugging Face, Cursor, Copilot, Codex, Continue, Codeium, Aider, Weights & Biases y Ollama. Recorre el directorio del usuario buscando .env, .pem, .p12, .jks, .kdbx y kubeconfig, copia los historiales de bash, zsh, mysql y psql, y escanea la memoria de los procesos Runner.Worker y Runner.Listener para extraer JWT, tokens de GitHub, claves de AWS y de IA que el workflow nunca pidió. En runners autoalojados añade las credenciales del servicio de metadatos de la nube. SafeDep suma a la lista los ficheros tfvars y tfstate y las carteras de criptomonedas. Es el mismo botín que buscaba el stealer de LiteLLM 1.107.0 en octubre, ahora recogido en el runner en lugar de en el portátil.

La captura de StepSecurity del commit b4353fc en nextcloud/docker enseña cómo entra el payload en un script existente: 1.496 líneas añadidas a update.sh y, en la línea 211, la variable WF_CALLBACK con el servidor de recogida 194.150.166.244 en el puerto 8080. En ese repositorio el run 2675 arrancó tres segundos después del commit y no llegó a borrarse, un desarrollador lo revirtió 27 minutos más tarde y el log sobrevivió. StepSecurity reproduce estas líneas:
memscan_files 3
gcp_gcloud_auth_empty
gcp_live token=0 projects=0 gcloud=0
selfhosted_collected
az_graph_token_fail
oidc_discovery arn=none gcp=none azure=none
archive_bytes 8971
exfil_no_runtime_varsEl log describe una recogida en un runner autoalojado, con lectura de memoria, sin credenciales de GCP ni de Azure que robar, un archivo de 8.971 bytes y una subida al primer servidor que no necesitó reintento. La exfiltración empaqueta todo en un tar comprimido y, en esta campaña, lo envía por HTTP POST a 194.150.166.244:8080/exfil o a 18.143.168.96:8080/exfil. A la 01:10 UTC del 12 de noviembre apareció un tercer servidor, 153.75.82.159, con un bot de Telegram como respaldo, y hacia las 05:30 UTC las copias nuevas pasaron a Telegram solo, con la llamada HTTP apuntando a 127.0.0.1. De las 101 copias que examinó StepSecurity, 57 usaban HTTP y 43 Telegram. Endor Labs numera cinco versiones del payload, de la v1 del 10 de noviembre, con un error de expresión regular que rompía el escaneo de memoria, a la v5 que desde el 12 de noviembre manda copia a Telegram en cada ejecución. Las versiones nuevas llevan comentarios en indonesio, según Aikido, fruto de una edición apresurada del canal de Telegram.
El script tiene otros tres canales que usan la propia infraestructura de GitHub para no salir por la red: la API de caché de Actions, el resumen de paso con marcadores WFX-BEGIN y un artefacto llamado wfx-loot con el identificador del run. Los tres dependen de una opción para repositorios privados que estaba apagada en todas las muestras. Aikido avisa de que no encontrar artefactos wfx-loot ni entradas de caché wfx- no significa que no se hayan llevado nada, porque «The run itself is usually deleted based on our data» (el propio run suele estar borrado, según nuestros datos). Los indicadores que publican Aikido, StepSecurity y SafeDep se resumen en esta tabla, y la lista completa de SafeDep incluye los commits analizados.
Tipo | Valor | Contexto |
|---|---|---|
URL | http://194.150.166.244:8080/exfil | Servidor de recogida principal, recibe el tar comprimido por HTTP POST en 54 de las 57 copias con HTTP que examinó StepSecurity |
URL | http://18.143.168.96:8080/exfil | Segundo servidor de recogida, el único de la v1 del payload según Endor Labs |
URL | http://153.75.82.159:8080/exfil | Tercer servidor, aparece a la 01:10 UTC del 12 de noviembre junto al respaldo por Telegram |
dominio | api.telegram.org | Exfiltración con sendDocument desde bots de Telegram, canal único en las copias posteriores a las 05:30 UTC del 12 de noviembre |
IP | 193.32.204.199 | Servidor de GhostAction al que siguen enviando secretos los workflows de octubre que wfx reutiliza |
hash SHA-256 | 89695abb392d5cfd4972672961e644bb46baa98aa1ed7173c2a2816c762735cf | Script del stealer según Aikido y Endor Labs, de utilidad limitada porque cada copia incrusta el nombre del repositorio |
ruta de archivo | docs/wfx-audit.md | Fichero de dos líneas del pull request de prueba, idéntico en todos los repositorios |
ruta de archivo | .ci-[a-z0-9]{6}.sh | Script oculto que ejecuta el hook postinstall de package.json en cualquier npm install |
ruta de archivo | .github/workflows/build-audit-[a-z0-9]{6}.yml | Workflow nuevo creado en ramas desechables con el patrón chore/[a-z0-9]{8} |
nombre de paquete | digitalelvis/neuronai-studio 3.14.3 (Packagist) | Lleva el script y el hook, se dispara con npm install dentro de vendor |
nombre de paquete | github.com/onuragtas/docker-env v1.0.278 y v1.0.280 (Go) | Payload solo en un fichero de workflow que la herramienta de Go no ejecuta |
nombre de paquete | github.com/vkuznet/gocurl v0.0.0-20261111095532-8d5aad0cb0da (Go) | Pseudoversión con el payload en el workflow, todavía servida por el proxy de Go |
Trust policies abiertas a cualquier cuenta de AWS y credenciales federadas wfx-ci en Azure, la persistencia que sobrevive a rotar
La fase de escalada es la que distingue a wfx de los stealers de secretos de CI anteriores. El script pide a GitHub tokens OIDC con audiencias de AWS, GCP y Azure y los cambia por sesiones en cada nube. En AWS encadena los roles que encuentra en el repositorio y llama a iam:CreateAccessKey para fabricar credenciales de acceso de larga duración. En GCP firma JWT con las claves de cuentas de servicio robadas, lista proyectos y cuentas, y llama a generateAccessToken y signJwt para suplantarlas. En Azure cambia tokens OIDC o secretos de cliente por tokens de gestión, enumera suscripciones y Key Vaults, y reutiliza los refresh tokens que la CLI de Azure guarda en caché contra identificadores de cliente de Microsoft para obtener tokens de Graph, ARM y Key Vault. También reproduce los tokens OIDC contra HashiCorp Vault y contra los servidores de API que aparecen en los kubeconfig.
La persistencia va después. En AWS reescribe la política de confianza del rol encadenado para que admita cualquier principal, con el valor "AWS": "*", lo que abre el rol a cualquier cuenta de AWS del mundo. En Azure, con un token de Graph, añade a las aplicaciones de Entra ID credenciales de identidad federada con nombres wfx-ci- seguidos de un número, que apuntan al repositorio comprometido y permiten autenticarse sin ningún secreto almacenado, de modo que rotar el secreto de cliente no las toca. El script también sabe publicar comprobaciones de estado falsas con éxito sobre el SHA del commit, usando los nombres de contexto de los checks obligatorios del repositorio, para saltarse la protección de ramas, aunque ninguna de las 101 copias que examinó StepSecurity tenía esa función activada y SafeDep tampoco la vio encendida. Gaetan Ferry, investigador de seguridad de GitGuardian, lo resume en su análisis del 13 de noviembre: «Rotating the secrets the payload stole is not enough» (rotar los secretos que robó el payload no es suficiente). La credencial de GitHub que permitió el push tiene que encontrarse y revocarse, y los accesos abiertos en la nube hay que buscarlos uno a uno, como ocurrió con la credencial de ConnectWise que abría 173 tenants de Microsoft 365.
Estas tres comprobaciones sirven para localizar en una cuenta de AWS y en un tenant de Entra ID los dos rastros de persistencia que describen los informes:
# Roles cuya política de confianza admite cualquier principal de AWS
aws iam list-roles --output json | jq -r '.Roles[] | select(.AssumeRolePolicyDocument | tostring | test("\"AWS\":\\s*\"\\*\"")) | .RoleName'
# Claves de acceso creadas desde el 10 de noviembre de 2026
aws iam list-users --output json | jq -r '.Users[].UserName' | while read -r u; do
aws iam list-access-keys --user-name "$u" --query 'AccessKeyMetadata[?CreateDate>=`2026-11-10`].[UserName,AccessKeyId,CreateDate]' --output text
done
# Credenciales federadas wfx-ci-* en las app registrations de Entra ID
az ad app list --all --query "[].id" -o tsv | while read -r id; do
az ad app federated-credential list --id "$id" --query "[?starts_with(name,'wfx-ci-')].[name,subject]" -o tsv
doneEn CloudTrail, la secuencia que delata la escalada es un AssumeRoleWithWebIdentity o AssumeRole con nombre de sesión que empieza por wfx- seguido de CreateAccessKey o UpdateAssumeRolePolicy. En GCP, llamadas a GenerateAccessToken y SignJwt desde el pool de identidades de GitHub. En Vault, inicios de sesión JWT con el emisor token.actions.githubusercontent.com.
Tokens en .git/config de apps de Fly.io y un hook de npm que esquiva la retención de GitHub
Cómo se robaron las 4.837 cuentas es lo que ningún informe cierra. Aikido no lo explica. Endor Labs recoge el testimonio de una víctima que, en su propio repositorio, atribuye el pull request de prueba a un token OAuth filtrado de la CLI de GitHub, y avisa de que es un caso y no la campaña entera. StepSecurity sí ha podido atribuir una parte: 173 pull requests de prueba desde 19 cuentas se abrieron a través de la GitHub App de Fly.io, identificada en los registros de GitHub como fly-io con el número 34557, en dos ráfagas, entre las 10:02 y las 10:36 UTC del 11 de noviembre, y de nuevo hacia las 06:20 del 12. Cada ráfaga creaba pull requests para entre cuatro y siete cuentas sin relación en uno o dos segundos, lo que apunta a un solo operador con un lote de tokens. La app tiene permiso de escritura sobre contenidos, acciones y pull requests. StepSecurity deja claro que no ha establecido cómo se obtuvieron los tokens y que tiene «no evidence of a breach of Fly.io's own infrastructure» (ninguna prueba de una brecha en la infraestructura de Fly.io), y que la mayor parte de la campaña usó otras credenciales, como tokens de acceso personal.

La captura de StepSecurity es la entrada de la API de GitHub para el pull request número 1 de EbukaLovesTech/2gether, abierto a las 10:02:50 UTC del 11 de noviembre: el campo performed_via_github_app señala a Fly.io. La propia Fly.io publicó su aviso de seguridad el 12 de noviembre. Cuando una aplicación se desplegaba desde un repositorio de GitHub a través del panel o de los despliegues automáticos, el proceso dejaba un token de acceso de GitHub en el fichero .git/config del directorio de construcción, y las aplicaciones que incluían ese directorio en su imagen y lo servían por HTTP lo dejaban al alcance de cualquiera. Fly.io insiste en que sus «systems were not breached» (sistemas no fueron vulnerados), habla de tokens de «a small number» (un número reducido) de cuentas, ha revocado todos los tokens de usuario de la app, ha avisado a los clientes afectados y ha cambiado el proceso de despliegue y las reglas de exclusión por defecto. Pide a sus usuarios que vuelvan a conectar GitHub, que comprueben si la URL https://<app>.fly.dev/.git/config devuelve un fichero de configuración de Git y que añadan .git al .dockerignore. El día 13 añadió un detalle que cambia la lectura: la revocación en bloque que lanzó desde la interfaz de GitHub «stopped partway through» (se detuvo a mitad de camino), según le reconoció el soporte de GitHub, que investiga el motivo y promete reanudarla.
Comprobar si una app de Fly.io sirve su directorio .git lleva una línea, y un código 200 con un fichero de configuración de Git en la respuesta significa que el token estuvo expuesto:
curl -s -o /dev/null -w "%{http_code}\n" https://<nombre-de-la-app>.fly.dev/.git/configHay otra pista sobre el origen de las cuentas. GitGuardian calcula que 431 de las cuentas usadas en wfx, alrededor del 20 %, ya estaban comprometidas en la oleada de GhostAction de octubre, la campaña de workflows maliciosos que GitGuardian cifró entonces en unas 1.900 cuentas y 30.000 repositorios. Aikido encontró más de 100 repositorios donde wfx-loot se inyectó directamente en los ficheros de workflow que GhostAction había dejado, todavía enviando secretos a 193.32.204.199, y aun así considera que son campañas distintas de actores distintos, por la infraestructura, las herramientas y los comentarios en indonesio. StepSecurity matiza que el solapamiento no demuestra un único operador, porque las credenciales robadas circulan entre varios. Que un token siga vivo semanas después de una campaña pública es el mismo problema que vimos con los 1.379 secretos de Claude Code en repositorios públicos: lo que se filtra en GitHub se queda ahí hasta que alguien lo revoca.
La respuesta de GitHub ha sido parcial. GitGuardian observó que GitHub retuvo las variantes que tocan ficheros de workflow: todos los runs de payload de noviembre que pudo ver quedaron en estado action_required y nunca arrancaron. El hook de npm se saltó esa barrera, y Ferry lo explica en dos frases: «The npm hook bypassed the hold. It changes no workflow file.» (el hook de npm esquivó la retención, no cambia ningún fichero de workflow). Los workflows legítimos del repositorio ejecutan npm install o pnpm install, y eso basta. GitGuardian tiene logs públicos de 19 repositorios donde el script del hook corrió y en 18 informó de una exfiltración completada, y los commits con hook pasaron de entre 10 y 70 cada tres horas a 838 en la mañana del 11 de noviembre y a 1.210 en la del 12. GitGuardian también fecha un ensayo previo entre el 24 y el 29 de octubre, con 80 parches desde 7 cuentas en 12 repositorios. Cuando cerramos esta pieza, el 14 de noviembre a las 13:00 UTC, no encontramos comunicado de GitHub en su blog ni en su changelog sobre la campaña, ni aviso de INCIBE-CERT o del CCN-CERT. La vía de distribución recuerda a la del stealer servido desde GitHub por OVHcloud CLI en octubre, con la diferencia de que aquí las cuentas legítimas son miles.
Buscar el run que falta, revocar la cuenta y borrar los backdoors, la lista para esta semana

La captura de StepSecurity del historial de GitHub Actions de drunomics/nuxtjs-drupal-ce es el aspecto que tiene una infección de wfx cuando el atacante ha limpiado: el run 1965 corresponde al commit ci: restore, el 1963 es anterior al ataque y el 1964, el del commit con el stealer, no existe. En ese repositorio el stealer entró a las 09:17:23 UTC del 11 de noviembre y el revert llegó doce segundos después, empujado por el bot drubot. StepSecurity lo deja en una frase: «A missing run means the stealer probably ran and the attacker deleted the evidence» (un run que falta significa que el stealer probablemente corrió y el atacante borró la prueba). Endor Labs propone estas búsquedas para una organización y para un clon local:
# Pull requests de prueba en la organización
gh search prs "docs: dependency audit notes" --owner <org> --created ">=2026-11-10"
# Commits del atacante en todas las ramas de un clon
git log --all --since=2026-11-10 --format='%h %ad %an %s' \
| grep -E 'ci: refresh build cache|chore: add ci helper|chore: add dependency audit workflow|ci: restore|ci: cleanup'
# Ramas del atacante que siguen en el remoto
git ls-remote origin 'refs/heads/wfx-docs' 'refs/heads/chore/*' 'refs/heads/ci-check-*'
# Hooks de instalación que ejecutan el script oculto
grep -rn --include=package.json -E '"postinstall"\s*:\s*".*\.ci-[a-z0-9]{6}\.sh' .Con los resultados en la mano, el orden que proponen Aikido, StepSecurity, SafeDep y Endor Labs es este:
- Trata como comprometida cualquier cuenta que haya abierto un pull request docs: dependency audit notes desde el 10 de noviembre, aunque no haya commit posterior. Revoca sus tokens de acceso personal, sus autorizaciones OAuth, sus claves SSH y sus sesiones, cambia la contraseña y exige MFA resistente al phishing. Revisa qué bots pueden saltarse la protección de ramas.
- En cada repositorio con commit malicioso, mira en la pestaña Actions si falta un número de run justo después de ese commit y consulta la vista de runs borrados del registro de auditoría de GitHub. Si falta, da por robado todo lo que el job podía leer.
- Rota los secretos de repositorio, de entorno y de organización, los tokens de publicación en npm, PyPI, Packagist y crates, las claves de nube, las credenciales de Vault y Kubernetes, las de los hosts de runners autoalojados y todas las claves de proveedores de IA. Vuelve a publicar cualquier paquete construido entre el commit malicioso y su revert.
- Busca y elimina la persistencia en la nube con las comprobaciones del apartado anterior: políticas de confianza con "AWS": "*", claves de acceso creadas desde el 10 de noviembre, credenciales federadas wfx-ci- en Entra ID, suplantaciones en GCP y logins JWT en Vault.
- Da por comprometido el equipo de cualquier desarrollador que hiciera npm install, npm ci o pnpm install sobre un commit afectado antes de la limpieza, y rota sus credenciales locales, incluidas las de SSH, npm y los asistentes de IA.
- Si usas Fly.io con despliegues desde GitHub, vuelve a conectar la cuenta, comprueba la URL .git/config de cada app y añade .git al .dockerignore.
- Bloquea en los runners la salida hacia 194.150.166.244, 18.143.168.96, 153.75.82.159 y api.telegram.org salvo que haya un motivo de negocio, deja GITHUB_TOKEN en solo lectura por defecto, mueve los secretos de despliegue a entornos protegidos con aprobación, restringe la confianza OIDC de cada nube a repositorio, rama y entorno concretos, y usa npm ci --ignore-scripts donde el proyecto lo permita. La documentación de GitHub sobre OIDC y su guía de uso seguro de Actions describen cómo acotar esas confianzas.
- Si la organización está en España y el robo alcanza datos personales o sistemas de clientes, notifícalo a INCIBE-CERT, y al CCN-CERT si es sector público.
La secuencia de la campaña, con las horas que dan los cuatro laboratorios y Fly.io, queda así:
Fecha y hora | Hecho |
|---|---|
24 a 29 de octubre de 2026 | Ensayo previo con 80 parches de payload desde 7 cuentas en 12 repositorios, según GitGuardian |
10 de noviembre, 12:45 UTC | Primeros pull requests docs: dependency audit notes, arranque de la oleada principal, según Aikido |
11 de noviembre, 06:33 UTC | Primera víctima que lo cuenta en público y atribuye el acceso a un token OAuth filtrado de la CLI de GitHub, según Endor Labs |
11 de noviembre, 10:02 a 10:36 UTC | Primera ráfaga de pull requests abiertos a través de la GitHub App de Fly.io, según StepSecurity |
11 de noviembre, 12:11 UTC | Dos commits en webdriverio/webdriverio como wdio-bot, revertidos a las 12:30, según Endor Labs |
12 de noviembre, 01:10 UTC | Aparecen el tercer servidor 153.75.82.159 y el primer respaldo por Telegram, según StepSecurity |
12 de noviembre, 05:30 UTC | Las copias nuevas del payload pasan a exfiltrar solo por Telegram, según StepSecurity |
12 de noviembre | Aikido publica el informe y el aviso en X, y SafeDep, StepSecurity y Endor Labs publican sus análisis |
12 de noviembre | Fly.io publica su aviso de seguridad y lanza la revocación de los tokens de su GitHub App |
13 de noviembre | Fly.io añade que GitHub detuvo la revocación a mitad de camino y que la reanudará |
El 92 % de pull requests de prueba todavía abiertos el día 12 dice más de la detección que del atacante. Son pull requests visibles, con un título que no encaja en ningún proyecto y una rama con un nombre que no significa nada, y la mayoría de los dueños no los había mirado dos días después. La retención que GitHub aplica a los ficheros de workflow modificados, que GitGuardian vio funcionar en todos los casos observables, dejó pasar el hook de npm porque no toca ningún workflow, y ese hueco lo cubre hoy cada equipo con un ignore-scripts y con una revisión obligatoria de package.json. Las herramientas DevSecOps que vigilan dependencias sirven aquí de poco si nadie vigila los permisos de los tokens.
Lo que nos parece más serio es la revocación a medias. Fly.io lanzó el 12 de noviembre la anulación de todos los tokens de su app y el 13 supo por GitHub que el proceso se había parado sin terminar. Hasta que GitHub lo complete, parte de ese lote de tokens sigue siendo válido, y el operador que los usó en dos ráfagas de media hora no necesita más que volver a probar. Si gestionas una organización en GitHub, la búsqueda del pull request de prueba de la lista de arriba tarda menos de un minuto, y es la misma que el atacante hizo contra ti.



No hay comentarios todavía