La clave borrada seguía en el historial
El 8 de octubre, una campaña llamada GhostAction usó dos cuentas de mantenedores secuestradas para hacer commit de un workflow llamado security-audit.yml en 346 repositorios, entre ellos athenadriver de Uber y pyxel, con 18.000 estrellas. El 9 de octubre, Socket contaba más de 500 cuentas y decenas de miles de repositorios. El workflow ejecuta git log -p --all y busca con grep en cada commit jamás hecho claves de Anthropic, OpenAI, OpenRouter, GitHub y AWS, incluidas las que se borraron un commit después. En Bromure Agentic Coding, el agente solo tiene sustitutos, así que las claves que escribe en el historial de un repositorio no valen nada para quien las encuentre.
Borrar una clave de un archivo cuesta un commit. El commit anterior guarda la clave mientras exista el repositorio.
A las 13:40 UTC del jueves 8 de octubre, la cuenta de GitHub de Takashi
Kitao, autor del motor de juegos pyxel, empezó a hacer commit de un archivo
llamado .github/workflows/security-audit.yml. Cuatro minutos después estaba
en 27 de sus repositorios. A las 21:10 UTC, la cuenta henrywoo, autor
original de athenadriver de Uber, empezó el mismo barrido. A las 21:26 había
llegado a 318 repositorios, entre ellos 279 forks y el propio
uber/athenadriver, donde la cuenta tiene permiso de escritura.
Socket publicó su
análisis el 9 de
octubre y atribuyó los commits a GhostAction, una campaña de robo de
credenciales vista por primera vez en septiembre de 2025. Al final de ese
día, la actualización de Socket contaba más de 500 cuentas de GitHub que
habían hecho commit del workflow en “decenas de miles de repositorios” desde
el 7 de octubre. The Hacker
News
y StepSecurity cubrieron la misma ola. Socket comprobó que el workflow ya se
había ejecutado en repositorios afectados, y que seguía en las ramas por
defecto de kitao/pyxel y uber/athenadriver cuando se publicó el artículo.
Qué hace security-audit.yml
El workflow se llama “Security Audit”. Se ejecuta con workflow_dispatch y
con cada push a cualquier rama o tag, sin filtro. Su paso de checkout usa
fetch-depth: 0, que trae todas las ramas, todos los tags y el historial
completo. Después, un único paso hace dos trabajos.
El primero va a por los secretos de GitHub Actions del repositorio. Antes de
hacer commit del workflow, el atacante leyó los workflows existentes del
repositorio y escribió en la carga útil los nombres de los secretos que usan.
En pyxel eran CARGO_REGISTRY_TOKEN, PERSONAL_ACCESS_TOKEN, PYPI_PASSWORD
y PYPI_USERNAME.
El segundo es nuevo en esta ola. El paso busca con grep 13 patrones de
credenciales en el árbol de trabajo, luego ejecuta git log -p --all y busca
los mismos patrones en hasta 200.000 líneas de historial:
- Servicios de IA: Anthropic (
sk-ant-), claves de proyecto de OpenAI (sk-proj-), OpenRouter (sk-or-) - Control de versiones: tokens clásicos de GitHub (
ghp_), tokens de grano fino (github_pat_), tokens de GitLab (glpat-) - AWS: IDs de clave de acceso, IDs de clave temporal, claves secretas y tokens de sesión. El paso conserva además dos líneas de contexto alrededor de cada ID de clave, así que la clave secreta que se hizo commit a su lado también se va.
- SaaS y nube: claves de API de Google, tokens de Slack, claves de SendGrid
Todo sale en un único POST de curl por HTTP sin cifrar a 193.32.204.199.
Cada ejecución informa del nombre del repositorio, haya encontrado algo o no.
Socket señala a quién va dirigido. Dos de los repositorios de henrywoo son
pyllama y chatllama, proyectos de aprendizaje automático cuyos
colaboradores y autores de forks “probablemente tienen este tipo de claves”.
Una clave de Anthropic u OpenAI se puede revender como inferencia facturable,
y cada prompt que pasa por ella pasa por las manos del comprador.
El historial lo guarda todo
El consejo de remediación de Socket tiene una frase que debería preocupar a
cualquiera que haya limpiado una clave filtrada editando un archivo: “Rota
cada credencial encontrada en cualquier commit, esté o no todavía en el árbol
actual”. Una clave eliminada en el commit siguiente sigue en el commit
anterior. git log -p --all lee los dos.
Los agentes de código engordan ese historial deprisa. El informe State of Secrets Sprawl 2026 de GitGuardian encontró que los commits públicos coescritos por Claude Code filtraban secretos en un 3,2 % de los casos, frente a una base del 1,5 % en todo el GitHub público. El mismo informe contó 1.275.105 secretos filtrados ligados a servicios de IA en 2025, un 81 % más que el año anterior.
El patrón le resultará familiar a cualquiera que haya visto a un agente hacer
pasar un test de integración que fallaba. El agente tiene la clave en su
entorno. La escribe en un archivo de configuración, un fixture de test o un
.env que todavía no está ignorado, porque así pasa el test. Hace commit. Más
tarde, el desarrollador o el agente mueve la clave a una variable de entorno y
vuelve a hacer commit. El árbol parece limpio. El historial tiene la clave,
cada fork tiene el historial, y un workflow con fetch-depth: 0 lo lee.
El token que escribió el workflow
Socket no determinó cómo se tomaron las dos cuentas. The Hacker News señala
la fuente más probable: un personal access token sacado de registros de
infostealers o de volcados de credenciales. Los registros de infostealers
salen de máquinas de desarrolladores. En un portátil, el token de GitHub está
en ~/.config/gh/hosts.yml, en ~/.git-credentials y a menudo en
GH_TOKEN, legible por todo lo que se ejecute como el desarrollador,
incluido lo que un agente de código instale o ejecute.
Basta un token. Con él, el atacante puede listar los repositorios en los que
la cuenta puede escribir, leer sus workflows para sacar los nombres de los
secretos y hacer commit en cada uno. El barrido de henrywoo cubrió 318
repositorios en 16 minutos.
El mismo repositorio, escrito desde una máquina Bromure
En Bromure Agentic Coding, el agente se ejecuta dentro de una VM Linux en tu Mac, y cada petición HTTP que hace la VM atraviesa un proxy en el host. Tu clave de Anthropic, tu token de GitHub y tus claves de AWS se quedan cifrados en el Mac. Ahora suelta GhostAction sobre un repositorio que escribió una máquina Bromure, y sobre el desarrollador que lo escribió.
El historial guarda sustitutos
Dentro de la VM, cada credencial es un sustituto con la forma de la real:
ANTHROPIC_API_KEY es sk-ant-api03-brm-…, una clave de OpenRouter es
sk-or-v1-brm-…, GH_TOKEN y ~/.git-credentials contienen ghp_ más 36
caracteres. Cada uno se deriva del valor real y de una sal propia de la
instalación con HKDF-SHA256, y no se puede convertir de vuelta en él. El proxy
pone el valor real en el cable, y solo para una petición al host al que
pertenece la credencial.
Así que, cuando el agente escribe su clave en config/test.py para que pase
el test, escribe lo que tiene. Cuando hace commit, el commit contiene
sk-ant-api03-brm-…. Cuando llega más tarde el commit de limpieza, el
historial conserva ese mismo sustituto.
El grep de GhostAction lo encuentra. sk-ant- coincide con
sk-ant-api03-brm-, y ghp_ coincide con un token de GitHub sustituto. El
POST a 193.32.204.199 se los lleva, y el comprador los prueba. Anthropic
rechaza la clave y GitHub rechaza el token, porque lo único que los convierte
en credenciales válidas es el proxy de tu Mac, para peticiones que salen de
tu VM. El consejo de Socket de rotar cada clave encontrada en cualquier
commit tiene una respuesta corta para este repositorio: ninguna de esas
claves funcionó nunca fuera de tu Mac, y los sustitutos de los proveedores de
IA llevan -brm- en el nombre, así que puedes localizarlos al escanear el
historial.
El token del portátil también es un sustituto
La otra mitad de la campaña es la propia cuenta. Ejecuta tus agentes en una
máquina Bromure y el token de GitHub que usan se queda en el Mac. Todo lo que
se ejecute en la VM, sea el script de instalación de un paquete, un stealer o
una inyección de prompt, encuentra un sustituto ghp_ en
~/.git-credentials y GH_TOKEN. Un atacante que lo reutilice desde su
propia máquina recibe un 401 de GitHub.
El stealer además tiene que enviar lo que se llevó a algún sitio, y esa
petición atraviesa el proxy. Un sustituto tiene un único destino legítimo, el
host para el que se generó. El proxy examina cada petición saliente,
cabeceras y cuerpo, en busca de cualquier sustituto que vaya a otra parte. Un
token ghp_ camino del servidor de un stealer recibe un HTTP 451, no se
reenvía nada, la VM se pausa, y en el Mac se abre This environment may have
been compromised con una vista previa del token, el nombre de la
credencial, el host al que pertenece y el host al que se envió.
Un push de workflow espera un clic
Los gusanos de esta familia no siempre esperan a un comprador. La carga útil
de tensorlake la semana pasada
hacía commit de archivos en los repositorios de sus víctimas directamente
desde la máquina del desarrollador. Las máquinas Bromure nuevas usan Prompt
before write para cada forja git. Un push es una escritura, así que hacer
commit de security-audit.yml en un repositorio desde la VM se detiene en un
diálogo de macOS, Allow write on "github.com" from workspace "…"?, que
muestra POST /owner/repo.git/git-receive-pack tal como pasó por el cable. El
barrido de henrywoo hizo 318 de esas escrituras en 16 minutos. Desde una
máquina Bromure, la primera te espera, y tú no le pediste a tu agente que
añadiera una auditoría de seguridad a 318 repositorios.
Cada uso del token queda registrado
La Security Timeline registra cada sustitución de credencial como una
fila Credential brokering: qué token, qué host, qué máquina, a qué hora.
Cuando necesites saber si tu token de GitHub hizo push de algo el 8 de
octubre entre las 21:10 y las 21:26, la respuesta está en tu Mac, en un
registro que el agente no puede editar. Con el trazado activado,
bromure-cli trace leaks lista cada petición en la que un token apareció en
el host equivocado.
Limpiar después de una clave
El consejo habitual tras filtrar una clave es rotarla, reescribir el
historial con git filter-repo, hacer force-push y esperar que ningún fork
ni espejo haya copiado antes los commits viejos. GhostAction muestra por qué
esa última parte falla. Socket contó 279 forks en un solo espacio de nombres,
cada uno con el historial y, ahora, con el workflow. Una clave que llegó a un
commit público está perdida, muestre lo que muestre el árbol actual.
Una máquina Bromure cambia lo que llega al commit en primer lugar. El agente
puede ser tan descuidado con las claves como dicen las cifras de GitGuardian
que son los agentes. Puede pegar una clave en un fixture, hacer commit de un
.env y olvidarse. Nunca tuvo una clave que funcionara, así que nada de ese
historial puede hacerte daño.
El commit contiene un sustituto
Los agentes de la VM solo ven sustitutos con la forma de las claves
reales. Una clave escrita en un archivo y con commit es una cadena
-brm- que solo funciona a través del proxy de tu Mac.
La cosecha recibe 401
Los patrones de GhostAction encuentran los sustitutos y los envían. Anthropic, OpenAI y GitHub los rechazan en cualquier sitio fuera de tu Mac.
Un token robado detiene la máquina
El token de GitHub de la VM también es un sustituto. Enviado a cualquier sitio que no sea GitHub, recibe un 451, la VM se pausa y una alerta nombra el token y el host.
Los push esperan un clic
Prompt before write retiene cada push desde la VM en un diálogo de macOS que muestra la petición, y la Security Timeline registra cada uso del token.
El próximo grep
Es la tercera ola de GhostAction en trece meses. La primera, en septiembre de 2025, se llevó 3.325 secretos de CI. La segunda, del 31 de agosto al 30 de septiembre de este año, fue a por 2.577 secretos en 772 repositorios. Esta ha añadido el historial completo y tres patrones de proveedores de IA. La próxima ola añadirá más patrones, y el historial que lee no hace más que crecer.
Ejecuta tus agentes donde las claves que tocan sean sustitutos, y el historial que dejan atrás no guardará nada que un comprador pueda usar. Para saber más sobre cómo funcionan los sustitutos y qué pasa cuando uno se filtra, lee El agente filtró su propia clave y La clave nunca caducó.