Cambiaron la dirección, no el paquete
Durante catorce horas, el 31 de agosto de 2026, algunas peticiones a registry.coder.com las respondieron servidores que un atacante había añadido al propio pool de origen de Cloudflare de Coder. El hostname era correcto, el certificado validaba, los módulos tenían los nombres y las versiones de siempre, y el código que volvió salió a buscar claves SSH, tokens OIDC y contraseñas de bases de datos. Coder no tiene los registros del atacante, así que su aviso te pide que revises tu propio tráfico de salida. Esa es una pregunta que la mayoría de las máquinas que ejecutan un agente de programación no puede responder.
El hostname era el de Coder. El certificado validaba. El módulo llevaba el nombre y la versión que tu plantilla ya fijaba. Durante catorce horas, el 31 de agosto, algunos de los servidores que respondían eran de otra persona.
Una ingeniera de plataforma actualiza una plantilla de espacio de trabajo un
lunes por la mañana. terraform init descarga los módulos que la plantilla
nombra (Docker, Zed, aider) desde registry.coder.com, el mismo sitio del que
venían el mes pasado. Terraform imprime su muro de verde habitual y el build
pasa. Ninguna herramienta señala nada, ni esa mañana ni ese mes.
El 1 de septiembre, Coder publicó GHSA-vx42-ghc9-gw65, calificado como crítico con CVSS 9.0. En sus palabras: “Un actor malicioso no identificado obtuvo acceso a la infraestructura de Cloudflare de Coder y añadió direcciones IP no autorizadas al pool que usa el registro de módulos de Coder.” Entre las 07:35 y las 21:45 UTC del lunes 31 de agosto, algunas peticiones de módulos de Coder las sirvieron máquinas que controlaba el atacante. Coder construye entornos de desarrollo en la nube autoalojados; BleepingComputer menciona a Dropbox, Palantir, Square, Mercedes-Benz, KKR, EnBW, organismos del gobierno de EE. UU. y contratistas de defensa entre sus usuarios.
Nadie tocó el paquete
Todas las costumbres de cadena de suministro que tiene un equipo cuidadoso son comprobaciones del artefacto. Si el nombre está bien escrito. Si quien publica es quien dice ser. Si esta versión se publicó ayer. Si el hash del lockfile sigue cuadrando. Lee el diff antes de subir la versión.
El atacante cambió qué máquinas respondían cuando tu cliente preguntó por la
dirección, y dejó el artefacto en paz. No publicó ningún paquete, no se apoderó
de ninguna cuenta de mantenedor, no registró ningún nombre parecido y no editó
nada en el registro de Coder. El nombre resolvió como resuelve siempre. El
handshake TLS se completó contra un certificado válido para el hostname real. El
módulo se llamaba docker, y zed, y aider, porque esos son los módulos que
pide una plantilla de Coder, y llevaba el número de versión que la plantilla ya
fijaba.
Lo que volvió era distinto. Las copias envenenadas traían un script de shell
llamado dlp.sh, con su propio build para los módulos docker, aider,
rstudio-server, windows-rdp y zed, y su propio hash para cada uno en el aviso.
El aviso describe la carga útil como código “diseñado para identificar una serie
de credenciales y exfiltrarlas a un servidor remoto con un nombre de dominio
parecido a uno que Coder usa habitualmente.” Ese dominio es coder-infra[.]com,
registrado el 28 de agosto, tres días antes de que nadie lo usara, resolviendo a
199.91.220.205, con la llamada de vuelta en
http://www[.]coder-infra[.]com/cli/check.
La lista de lo que buscaba se lee como un inventario de la confianza de una plataforma de desarrollo: variables de entorno y secretos del provisionador, tokens OIDC de usuarios, claves SSH, credenciales de autenticación externa y variables de configuración de coderd, incluidas contraseñas de bases de datos y credenciales del proveedor de autenticación.
No hacía falta hacer nada raro para estar dentro de la ventana. Según el aviso: “Los usuarios que usan el registro principal de módulos de Coder (registry.coder.com) como origen de sus plantillas y que actualizaron componentes durante el periodo en el que este código estuvo disponible pueden haber instalado los paquetes maliciosos.” Crear una plantilla, subir la versión de una plantilla, lanzar un dry run o construir un espacio de trabajo con la caché de módulos apagada es trabajo normal de lunes.
“Revisa los registros de firewall, proxy, DNS y flujo de la VPC”
Ese es el paso cinco de la remediación de Coder, y la línea más instructiva del aviso. Los pasos del uno al cuatro son cosas con las que Coder puede ayudar: SQL para encontrar módulos cacheados de la ventana, SQL para purgarlos, versiones parcheadas (2.37.0, 2.36.4, 2.35.7 y 2.34.9, todas publicadas el 1 de septiembre) y una lista de credenciales que rotar. El paso cinco va más allá de todo eso, porque los servidores que dirían a quién le tocó son del atacante. Coder no puede verlos, y lo dice: no puede identificar de forma concluyente cada despliegue comprometido.
Así que la pregunta vuelve a ti, y es una pregunta sobre pruebas. ¿Algo en la
máquina que ejecutó ese módulo se conectó a coder-infra[.]com? Para una VPC de
producción, la mayoría de los equipos puede responder en menos de una hora,
porque alguien del equipo de plataforma ya es dueño de los registros de flujo.
Ahora hazte la misma pregunta sobre la máquina donde trabaja tu agente de
programación. Un agente ejecuta terraform init, npm install, make, un
build de contenedor, una batería de pruebas, un script que encontró en el
repositorio, un binario que descargó un módulo. Cada una de esas cosas abre
conexiones, y nadie las está anotando en un sitio donde pudieras buscarlas una
semana después. La transcripción del propio agente guarda las llamadas a
herramientas que hizo el modelo. No tiene ninguna fila para lo que un script de
shell dentro de un módulo de Terraform hizo con la red, porque el modelo nunca
vio que ocurriera.
No puedes comprar una respuesta al paso cinco a posteriori. O algo registró la petición cuando salía de la máquina, o no hay nada que buscar.
Lo que habría encontrado el script
Toma la carga útil en serio y déjala correr. Barre el entorno en busca de
credenciales, lee las claves SSH, se lleva los tokens OIDC y de autenticación
externa, recoge las variables de configuración que llevan dentro la contraseña
de la base de datos y lo publica todo en coder-infra[.]com.
Bromure Agentic Coding le da a cada espacio de trabajo su propia VM Ubuntu bajo el hipervisor de Apple, con un proxy en el host como única ruta del guest hacia la red. Ejecuta ahí el mismo barrido y vuelve con un juego completo de credenciales, todas falsas.
ANTHROPIC_API_KEY guarda un señuelo sk-ant-api03-brm-…, GH_TOKEN una
cadena ghp_ de la longitud correcta, y ~/.git-credentials,
~/.docker/config.json y ~/.kube/config guardan marcadores equivalentes. Las
formas se mantienen, así que gh, docker y kubectl las aceptan sin
rechistar. El proxy mantiene tus valores reales cifrados en tu Mac y los cambia
en el cable después de que la petición haya salido de la VM, solo cuando va
dirigida al host para el que se acuñó esa credencial. Ningún interruptor apaga
esto; así funcionan las credenciales en la aplicación.
Las claves SSH son el caso más limpio: no hay ninguna en la VM que leer.
SSH_AUTH_SOCK apunta a un puente sobre un socket virtual hasta un agente en el
host, y ese protocolo tiene una petición que significa “firma este reto” y
ninguna petición que signifique “dame la clave”. ls ~/.ssh/id_* vuelve vacío
mientras git push sigue funcionando.
AWS merece su propio párrafo, porque es contra AWS contra lo que corre
Terraform. El ~/.aws/config de la VM apunta a un helper que entrega tu ID de
clave de acceso real emparejado con una clave secreta falsa. Terraform, los
SDK de AWS, boto3 y la CLI de aws leen ese helper sin ninguna configuración,
luego firman sus peticiones con la falsa y producen una firma bien formada que
AWS rechazaría. El host quita esa firma y vuelve a firmar con el secreto real de
salida. Un script que rastrilla el entorno buscando material de AWS se va con un
ID de clave y cuarenta caracteres que no autentican nada.
Después tiene que enviar el botín a alguna parte, y ahí es donde está el
firewall de salida del espacio de trabajo. Pon Unmatched traffic en Deny y
la VM llega a los hosts que hayas listado y a nada más. La aplicación de la
regla ocurre dos veces, en dos sitios que el guest no puede alcanzar: el switch
de red virtual casa cada flujo por IP de destino y por hostname espiado en el
DNS, en todos los protocolos, y el proxy vuelve a casarlo por el nombre de
servidor TLS. Nadie puso un dominio de tres días en esa lista. Las ediciones de
reglas llegan a las sesiones en marcha sin reiniciar, así que puedes añadir
deny any coder-infra[.]com con el build todavía corriendo.
Si el tráfico sale igualmente, porque el espacio de trabajo corre en la postura
por defecto que permite todo o porque el destino parecía corriente, los señuelos
hacen su segundo trabajo. El proxy escanea cada petición de salida, cabeceras y
cuerpo, buscando una credencial falsa que va a un sitio para el que nunca se
acuñó. Una cadena sk-ant-api03-brm-… dirigida a coder-infra[.]com no es
ambigua. El proxy la bloquea con HTTP 451 antes de que un solo byte llegue al
destino, pausa la VM y levanta una alerta nombrando la credencial y los dos
hosts. Bromure marca el espacio de trabajo como comprometido, y no volverá a
arrancar hasta que borres su disco y su home.
Tienes el paso cinco respondido mientras ocurre, en vez de reconstruirlo a partir de registros una semana después.
Un comando responde al aviso
El proxy del host escribe un registro de metadatos para cada petición que
sale de la VM, sea cual sea el proceso que la hizo: hora, host, puerto,
método, ruta, estado, latencia y bytes medidos antes de cualquier cambio de
credencial. Pon el Session trace del espacio de trabajo en Activity
only y obtienes ese registro sin que se almacene ningún cuerpo de
petición. Después, bromure-cli trace hostnames my-workspace imprime cada
host distinto que ha contactado el espacio de trabajo, con recuentos. El
paso cinco de Coder, en una línea, para la máquina donde corrió el código.
Lo que decidieron los motores
La Security Timeline (Window → Security Timeline…) es la vista aparte de lo que los motores de Bromure decidieron: cada veredicto del firewall de salida, cada credencial que intermedió el proxy, cada bloqueo de cadena de suministro, con código de color y filtros. Una conexión rechazada a un dominio registrado hace tres días aparece ahí como una fila roja de Firewall mientras el build sigue corriendo, no como un grep por un mes de registros de flujo después de que el aviso de otra persona te diga dónde mirar.
El guest puede llegar a una sola cosa de tu Mac: una carpeta compartida, una
ventana viva de lectura y escritura a tu sistema de archivos real que el borrado
por compromiso deja en paz por diseño. Comparte el directorio del proyecto en
lugar de tu directorio home, y un script que va a la caza de id_rsa y .env
recorre un home de Linux lleno de checkouts y cachés de paquetes.
Cuando no puedes comprobar el origen, vigila la salida
Todas las comprobaciones de procedencia se reducen a un movimiento: comparar lo que llegó con algo en lo que ya confías. Un nombre, quien publica, una firma, un hash en un lockfile. El movimiento funciona mientras el atacante tenga que meter algo en el canal para que puedas compararlo. Un atacante que controla un tramo del canal no mete nada en él, y el pool de origen de Coder era un tramo del canal. El compromiso de BdThemes en agosto tenía la misma forma, donde un bucket de almacenamiento secuestrado cambió un feed JSON que descargaban 350.000 sitios WordPress, sin ninguna publicación y sin ningún archivo modificado.
Catorce horas son una jornada de trabajo. Coder lo detectó, entregó parches a la mañana siguiente, publicó hashes e indicadores y les dijo a sus lectores qué comprobar. Aun así, el aviso termina pidiéndole a cada lector que vaya a mirar su propio tráfico de salida, porque el registro del otro lado ya no existe.
Rota las claves que lista el aviso, purga los módulos cacheados, aplica el parche. Después decide a qué va a poder llegar el siguiente, porque el siguiente llegará por un canal del que no tenías ninguna razón para dudar. Instala Bromure Agentic Coding, dale al build su propia máquina, y ten la respuesta lista para cuando aparezca el aviso.