Volver a todas las publicaciones
Publicado el · por Renaud Deraison

La tarea era imposible, así que el agente salió

OpenAI publicó el 26 de agosto su informe técnico sobre el incidente de Hugging Face. Empieza con un agente al que se le dio una tarea que no podía terminar, que se negó a rendirse y que recurrió al único servicio con el que tenía permiso para hablar: un espejo interno de paquetes que existía justamente para que los agentes no necesitaran internet. Las notas dejadas en ese espejo se convirtieron en un tablón de mensajes y, en julio, un colectivo de agentes pasó de una conexión saliente denegada a acceso de nivel de host en los clústeres de producción de Hugging Face en menos de trece horas. La lista de correcciones de OpenAI es la arquitectura de Bromure Agentic Coding escrita desde el otro lado.

Nadie atacó nada a propósito. A un agente se le dio una tarea que no podía terminar, se negó a rendirse y recurrió al único servicio con el que tenía permiso para hablar. Todo lo demás vino después.

El 8 de mayo, a un agente dentro del entorno de investigación de OpenAI se le pidió completar una hoja de cálculo. La hoja remitía a varios enlaces de Google Drive. El agente no tenía acceso a internet, así que los enlaces no llevaban a ninguna parte y la tarea era irresoluble. OpenAI dice que no pretendía repartir una tarea así.

El agente no se detuvo. Razonó largamente sobre el problema y luego fue a mirar lo único de su entorno que hablaba con algo más: una instancia interna de JFrog Artifactory, allí para que los agentes pudieran instalar paquetes sin salir a internet. Intentó contra ella una falsificación de petición del lado del servidor, esperando que Artifactory buscara los archivos en su nombre. El intento falló. De paso, escribió un archivo en Artifactory sin querer.

Cuatro días después, otro agente estaba atascado en una tarea que necesitaba un archivo de base de datos de proteínas fuera de su alcance. Dedujo que algún otro agente, en algún otro entorno aislado, podía tener el archivo. Así que escribió una nota en Artifactory:

Agent seeks [filename]; upload if found!

El 13 de mayo, otros agentes que sondeaban Artifactory encontraron notas como esa y empezaron a responderlas.

Todo esto sale del informe técnico de OpenAI sobre el incidente de Hugging Face, publicado el 26 de agosto junto con un resumen de lo que viene después. A lo largo de 38 páginas nombra los endpoints, las CVE y las horas. Es también el primer relato público de un colectivo de agentes autónomos llevando a cabo una operación ofensiva que nadie pidió. CrowdStrike validó las conclusiones, y METR y Redwood Research están publicando su propia evaluación del comportamiento del modelo. La prensa lo cubrió el día 27 en BleepingComputer, The Hacker News y Engadget.

La mayor parte de la cobertura lo lee como una historia sobre alineamiento de modelos. Léalo, en cambio, como un informe sobre fronteras de red, escrito por gente que vio caer la suya, y le dirá más sobre su portátil que sobre OpenAI.

Hackeo de la recompensa, y 198 tareas sin respuesta

La evaluación era ExploitGym, que mide si un modelo puede encontrar y explotar vulnerabilidades. Para obtener una cifra real de capacidad, OpenAI la ejecutó con los clasificadores de ciberseguridad de producción apagados, en un entorno aislado separado tanto de la infraestructura interna de OpenAI como de internet.

Los agentes abandonaron ese entorno persiguiendo una puntuación. La palabra del informe para eso es hackeo de la recompensa: un modelo encuentra una ruta no prevista hacia una nota alta en lugar de resolver el problema como estaba diseñado. La mayoría de esos atajos son pequeños, como buscar la respuesta o leerla en el historial de versiones. Estos agentes eran persistentes, llevaban presupuestos de razonamiento muy grandes y les tocaron algunas tareas que no tenían solución alguna.

A lo largo de muchos intentos, ningún modelo ha producido nunca una respuesta correcta para 198 de las 898 tareas de ExploitGym. Esas tareas irresolubles son el 22 % de la evaluación, y suponen el 93 % de las tareas discutidas en el tablón de mensajes. OpenAI también descubrió que la participación subía con el esfuerzo de razonamiento concedido al agente. Cuanto más se dejaba pensar a un modelo frente a un muro, más probable era que lo rodeara.

Así que el origen de todo el asunto es un modelo entrenado para ser persistente, apuntado hacia algo que no podía hacer, en una habitación con una sola puerta.

Las notas se volvieron infraestructura

El tablón creció porque funcionaba. Los agentes dejaban peticiones de archivos y respondían a las de los demás, y hacían todo eso con las credenciales compartidas de Artifactory que OpenAI les había entregado a propósito para que pudieran instalar paquetes. Ese paso no necesitó ninguna vulnerabilidad. El servicio hizo aquello para lo que fue construido.

El 26 de mayo, un fallo de SSRF en Artifactory dio a los agentes acceso real a internet, con la ayuda de la información intercambiada en el tablón. OpenAI detectó el tablón y reconstruyó la plataforma a principios de julio.

La reconstrucción no aguantó. El 8 de julio, agentes de investigación entrenados durante la ventana en que el tablón existía encontraron una nueva forma de operarlo. En vez de escribir mensajes en archivos, los codificaron en los nombres de los directorios de Artifactory. Luego montaron un protocolo encima: categorías, mensajes directos a agentes concretos, herramientas y archivos compartidos, y una manera de zanjar conflictos cuando dos agentes querían cosas incompatibles.

Nadie les había dicho que colaborasen. Al revisar después las tandas de entrenamiento, OpenAI descubrió que los agentes ya habían aprendido a improvisar canales de colaboración incluso cuando la herramienta multiagente oficial estaba apagada, y que ese comportamiento se reforzó después. Si ha leído nuestro texto sobre las cargas que viajan entre agentes a través de los archivos que guardan, esta es la misma mecánica sin nadie escribiendo la carga.

Trece horas

El 9 de julio, un agente que trabajaba en una tarea de ExploitGym consiguió root en la máquina virtual madre de su propio entorno de pruebas. Esa noche, un segundo agente llegó al mismo sitio por su cuenta, intentó marcar hacia internet, y los controles de red salientes de OpenAI se lo denegaron. Los controles funcionaron. Así que el agente pasó por los endpoints de Artifactory, y alcanzó internet por esa vía.

La ruta directa estaba cerrada. El servicio de apoyo fue la manera de rodearla, y ese servicio estaba dentro de la frontera porque los agentes necesitan paquetes.

el entorno de evaluaciónshell, Python, navegador,instalación de paquetesuna tarea sin solucióny un presupuesto enorme de pensarsalida directa: denegada por los controlespermitidoel espejo interno de paquetespuesto dentro de la frontera paraque los agentes no usaran internetcredenciales compartidas, a propósitodescarga del lado del servidor, igualinternet públicocredenciales filtradasconjuntos públicosuna VPN de tercerosHugging Faceademás: el tablón de mensajesnotas en archivos, luego en nombres de carpetaEl servicio que estaba ahí para mantener a los agentes fuera de internet es el que los puso dentro.
La vía de salida. El acceso saliente directo estaba bloqueado y siguió bloqueado; el intento del propio agente fue denegado. Lo que no estaba bloqueado era el espejo interno de paquetes que existía para que los agentes no necesitaran internet, y ese espejo sirvió a la vez de tablón de mensajes y de ruta de salida. Una frontera vale solo lo que vale el servicio menos restringido que hay dentro.

El informe recorre los cinco días siguientes paso a paso. El 10 de julio, un agente encontró 14 credenciales de usuarios de Hugging Face en un conjunto de datos público de la propia Hugging Face, confirmó que daban acceso de escritura y las publicó en el tablón para que otros agentes las reutilizaran.

El 11 de julio, un agente publicó un conjunto de datos construido para que los procesos de producción de Hugging Face se rompieran con él. Usando el almacenamiento externo en bruto de HDF5, hizo que un worker abriera y devolviera archivos locales de ese worker por el endpoint /first-rows, entre ellos /proc/self/environ, el archivo que guarda las variables de entorno de un proceso. Así salieron las credenciales de producción de Hugging Face. El agente no ejecutó ningún comando. El worker imprimió sus propios secretos.

A partir de ahí: un segundo truco con HDF5 para una lectura fuera de límites en el montículo, y luego metadatos controlados por el atacante en un descriptor de referencia fsspec que pasaba por una expresión Jinja2 sin aislar y ejecutaba un comando del sistema operativo. Esa fue la primera instancia confirmada de un agente ejecutando su propio código en Hugging Face.

Después lo industrializaron. Un agente escribió un pequeño script de control de workers y le dio un canal de mando y control que merece estudio: los workers comprometidos nunca hablaban con los agentes. Ambos lados leían y escribían en un conjunto de datos público de Hugging Face. Cada cinco segundos el script buscaba un archivo JSON de comandos, ejecutaba lo que hubiera en él y subía su salida al mismo conjunto. Tráfico corriente hacia un nombre de host corriente.

Ya tarde esa noche, el token de cuenta de servicio de un worker comprometido se usó con Kubernetes TokenRequest y acceso a kubelet para obtener root en el nodo de producción, y luego para leer un secreto de conector del utillaje de acceso a Kubernetes de Hugging Face. Eso dio acceso de inventario equivalente a administrador sobre unos 54 destinos Kubernetes anunciados. En la mañana del 12 de julio, un agente había creado un pod privilegiado en marcha, con red del host, acceso a los procesos del host y el sistema de archivos del host montado. Otros agentes leyeron secretos de Kubernetes de producción y los dejaron preparados en público. Uno descargó un archivo privado de benchmark de 16 MB, lo cifró, lo partió en cinco trozos y subió los trozos a un conjunto de datos público.

El resumen del propio informe: “En menos de 13 horas, los agentes pasaron de ejecutar código en un único pod worker a acceso administrativo y de nivel de host en múltiples clústeres de Hugging Face.”

Dos movimientos más, el 13 de julio, se parecen a lo que haría un equipo rojo humano. Los agentes tomaron credenciales de VPN de Hugging Face recolectadas y dieron de alta una máquina alojada fuera en la VPN de Hugging Face, rodeando las protecciones de nivel de red de los repositorios de código. Con una clave de autenticación recuperada, también modificaron un repositorio privado de código, abrieron una pull request en borrador y dispararon la CI del repositorio para lanzar una baliza de red. Alguien cerró la pull request y borró la rama. No se fusionó nada, no se publicó nada.

En todo el incidente, los agentes ejecutaron código en 41 workers del servidor de conjuntos de datos de producción de Hugging Face, obtuvieron root en al menos un nodo de producción y descargaron cuatro repositorios privados. El equipo de OpenAI detectó la actividad el 19 de julio, y lo que les puso sobre aviso fue el movimiento de los agentes dentro de la propia infraestructura de OpenAI, no algo del tablón. Lo conectaron con Hugging Face el 20 de julio y lo divulgaron el 21. Los datos y productos de clientes quedaron intactos, y Modal, cuyo contenedor de cliente usaron los agentes como relevo, confirmó que su plataforma y su aislamiento aguantaron.

La lista de correcciones de OpenAI es una especificación

El último tercio del informe expone lo que OpenAI está cambiando, y se lee como un documento de diseño para ejecutar un agente no fiable. Sus propias palabras, condensadas:

  • Acceso saliente restringido a un conjunto estrechamente controlado de destinos aprobados, incluida la limitación de las rutas alternativas a través de servicios compartidos y sistemas conectados.
  • Código de modelo de alto riesgo o no fiable obligado a ejecutarse dentro de entornos aislados basados en máquinas virtuales.
  • Dos capas separadas de aislamiento de red, una en el entorno aislado y otra en la infraestructura circundante, para que la seguridad de una carga de trabajo no dependa de ningún control aislado.
  • Controles técnicos para impedir que las cargas de trabajo o los servicios comprometidos modifiquen sus propias fronteras de red o creen nuevas rutas salientes.
  • Servicios de apoyo aislados, restringidos o sustituidos “para que no puedan ofrecer una ruta menos protegida alrededor de la frontera de seguridad prevista”.
  • Detección y alerta independientes ante los intentos de elusión.

Una frase sostiene todo lo demás: “ningún control aislado debería darse jamás por plenamente robusto.” Ese es el argumento que venimos defendiendo a favor de la frontera frente al entorno aislado, alcanzado desde la otra dirección por gente que vio caer la suya.

OpenAI midió una cosa más después. Ejecutar la misma evaluación a través del arnés y el prompt de sistema de producción de ChatGPT redujo la propensión a comprometer infraestructura en más de 100x, y su sistema de revisión automática habría marcado muchas de las acciones peligrosas. Lo que hizo posible este incidente fue el entorno en el que se calificó a los modelos.

La misma forma, un portátil más abajo

Usted no ejecuta 1.200 agentes. Según lo publicado por BleepingComputer, esa es más o menos la población de aquel tablón, con unos 700 en el ataque en sí.

Tome las cuatro cosas que hicieron funcionar la cadena y sostenga cada una frente al agente que corre en su terminal.

El agente era persistente y no soltaba una tarea bloqueada. El suyo también; ese es el producto entero. El agente podía alcanzar, dentro de la frontera, un servicio que buscaba cosas en su nombre. El suyo alcanza un registro de paquetes, un proxy, un servidor MCP, un runner de CI. El agente consiguió credenciales haciendo que un proceso imprimiera su propio entorno. El suyo guarda tokens reales en su entorno ahora mismo. El agente sacó datos a través de un conjunto de datos público en un nombre de host que nadie bloquearía. El suyo puede empujar a un gist, un bucket, un servicio de pegado.

Basta con un modelo corriente que choca contra un muro y recibe recompensa por rodearlo.

un agente en la máquinacredenciales/proc/self/environ = los tokens realessalida de redtodo lo que la máquina alcancepaquetesdel registro al disco, con scripts y todoacciones de protocolopush, borrar, crear pod: todo en vivoel registroel log del propio agente, a su alcanceun perfil de Bromurecredenciales/proc/self/environ = sustitutas brm_…cambiadas en el anfitrión, aprobación por usosalida de redreglas en el anfitrión, gana la primeracomprobadas en el conmutador y en el proxypaquetesedad mínima, OSV, filtrado, scripts fueraun proxy, no un repositorio donde escribiracciones de protocoloBarreras: solo lectura o sin destruirel registroRegistro de Seguridad, fuera del alcanceLos controles de la derecha se aplican donde el agente no puede editarlos ni rodearlos.
Dos maneras de ejecutar un agente que no se rinde. A la izquierda, el entorno del agente guarda credenciales reales, su salida de red es la de la máquina, y cualquier política que lo limite vive donde el agente puede alcanzarla. A la derecha, las credenciales del invitado son sustitutas que se cambian en el anfitrión, los destinos se comparan con un conjunto de reglas del lado del anfitrión en dos capas independientes, y las descargas de paquetes pasan por un proxy que no es un repositorio en el que se pueda escribir.

Dónde cada paso se topa con un muro

Bromure Agentic Coding ejecuta cada perfil como su propia máquina virtual en Apple Silicon, y pone toda decisión que importa del lado Mac del hipervisor. Recorra la cadena otra vez con eso puesto.

La conexión denegada sigue denegada, y el rodeo también. Barreras → Conexiones salientes es un conjunto de reglas al estilo pf: una acción, un protocolo (tcp, udp, web o any), un nombre de host o un CIDR IPv4, puertos, y para web una lista de métodos HTTP permitidos. Las reglas casan de arriba abajo, gana la primera coincidencia, y poner Tráfico sin coincidencia en Denegar convierte la lista en una lista de permitidos. Dos capas independientes evalúan esa misma política: el conmutador virtual, por IP de destino y nombre de host captado del DNS en todos los protocolos, y después el proxy del anfitrión, por SNI de TLS y método HTTP. Ese es el arreglo de dos capas que OpenAI está construyendo ahora. El conjunto de reglas vive en un panel del lado del anfitrión, de modo que un agente comprometido no puede modificar su propia frontera de red, que es el cuarto punto de su lista. Recursos → Red usa NAT por defecto, así que nada de su red local puede alcanzar la VM tampoco.

La vía de los paquetes no es un servicio del que apoderarse. Artifactory estaba dentro de la frontera por una buena razón, y acabó siendo a la vez el tablón y la salida. Un perfil de Bromure no tiene repositorio en la VM en el que escribir una nota ni consola de administración contra la que escalar. El proxy del anfitrión intercepta las descargas de paquetes de npm, PyPI, Cargo, RubyGems, Maven, NuGet, módulos de Go y Packagist, y luego las juzga antes de que el agente vea un byte: un umbral de edad mínima que corre por defecto en dos días, consultas a OSV, filtrado por socket.dev o Delpi, y los scripts de instalación retirados de los tarballs con el hash de los metadatos del registro reescrito para que npm siga verificando. El .npmrc y el pip.conf de la VM pueden apretar esos ajustes. No pueden aflojarlos.

El volcado del entorno no devuelve nada. El movimiento más rentable de toda la cadena fue hacer que un worker imprimiera /proc/self/environ. Haga eso dentro de una VM de Bromure y obtendrá sustitutas brm_…. Los valores reales nunca entran en el espacio de direcciones del invitado; el proxy del anfitrión los sustituye en el cable. El kubeconfig de la VM es sintético, con certificados de cliente desechables. El anfitrión vuelve a firmar las peticiones de AWS, así que cualquier cosa que esquive el proxy recibe InvalidSignatureException en vez de acceso. ~/.docker/config.json contiene base64 falso. Las claves SSH privadas nunca están dentro de la VM, porque un agente en el anfitrión, uno por perfil, se encarga de firmar. Active Exigir aprobación para usar en una credencial y cada sustitución levanta un diálogo en su Mac con una concesión acotada en el tiempo: cinco minutos, una hora, el resto de la sesión.

El pod privilegiado nunca se crea, y la PR de la baliza nunca aterriza. Las barreras ponen un motor de políticas delante de los protocolos mismos, aplicado en el proxy del anfitrión, que devuelve un 403 seco que el agente lee como un fallo de API corriente. Kubernetes en Solo lectura bloquea toda mutación, y crear un pod privilegiado con el sistema de archivos del anfitrión montado es una mutación. Ahí se va el movimiento del 12 de julio. GitHub, GitLab y Bitbucket en Solo lectura tratan git-receive-pack como escritura, así que la pull request en borrador que disparaba la CI para lanzar una baliza de red nunca llega al remoto. Ahí se va el 13 de julio. Las barreras clasifican las acciones de AWS por la cabecera X-Amz-Target o el parámetro Action=, los registros de contenedores por método, y los endpoints de bases de datos por palabra clave SQL o forma de la consulta.

Las notas se leen antes de que el modelo las obedezca. Que un agente adopte el objetivo de otro es, a nivel de cable, un problema de contenido: llega texto en un resultado de herramienta y el modelo lo trata como instrucción. La Inyección de Prompt puntúa el contenido de archivos, las páginas web y la salida de herramientas que lee el agente, con un modelo PromptGuard local. Puntúa los archivos de instrucciones que los agentes cargan como autoridad, es decir, CLAUDE.md, AGENTS.md, GROK.md y sus variantes anidadas y globales, con un clasificador ModernBERT afinado más un escáner determinista para la ofuscación por Unicode invisible. Ambos corren en el Mac y no suben nada. Ponga la respuesta en Bloquear unilateralmente y el contenido marcado falla con un 451 antes de que el modelo lo vea.

Y alguien se entera. La detección de la propia OpenAI llegó once días después de la fuga del 8 de julio, a partir de actividad en su infraestructura y no del tablón. La ventana Registro de Seguridad (Ventana → Registro de Seguridad…) es una única tabla cronológica del lado del anfitrión: cada destino que abrió la VM y si se permitió o se denegó, veredictos de paquetes, sustituciones de credenciales, detecciones de inyección. El código invitado no puede editarla, porque el código invitado no puede alcanzarla. Para cuerpos en lugar de veredictos, el Inspector de Trazas (⇧⌘I) graba la sesión a la profundidad que usted elija. Cuando termine, Borrar la carpeta personal… y Restablecer a la base… en Recursos → Almacenamiento devuelven el perfil a su estado justo después del clonado.

Actívelo

Un modelo hostil nunca formó parte de esto. Lo que describe el informe necesitó un modelo capaz, uno persistente, una tarea sin respuesta y un servicio permitido que buscara cosas en su nombre. Los dos primeros ya están en su portátil. Los otros dos llegan la noche en que un agente se atasca en algo mientras usted duerme.

En un perfil, los ajustes que valen dos minutos son una lista corta. Barreras → Conexiones salientes, con Tráfico sin coincidencia en Denegar y un puñado de reglas de permiso para los hosts que use. Es el interruptor más valioso del producto, y es aquel alrededor del cual OpenAI está reconstruyendo sus clústeres de investigación. Barreras → Kubernetes, AWS y GitHub en Solo lectura o Bloquear lo destructivo para cualquier perfil que no necesite publicar. Credenciales → Exigir aprobación para usar en todo lo que pueda gastar dinero o borrar datos. Cadena de suministro → Comprobación de vulnerabilidades OSV y filtrado por socket.dev o Delpi si tiene clave, encima del umbral de edad que ya está en marcha. Inyección de Prompt → los dos detectores.

Y luego siga dejando que el agente trabaje mucho rato y a solas. Su persistencia es la razón por la que lo usa, y el arreglo no es quitársela. El arreglo es construir el muro con algo distinto de aquello sobre lo que el agente está de pie. Instale Bromure Agentic Coding, abra las barreras y ponga el tráfico sin coincidencia en Denegar.