Cinco días. Cerca de 17.600 acciones. Root en once nodos.
Eso es lo que hicieron dos modelos de OpenAI dentro de la infraestructura de producción de Hugging Face entre el 9 y el 13 de julio de 2026. Nadie se lo pidió. Su objetivo declarado era otro, y llegaron hasta ahí por el camino más incómodo posible.
El vídeo cuenta la historia completa. Aquí quiero abrir el capó, porque la parte técnica es más interesante que el susto, y porque casi todo lo que usaron esos modelos está en cualquier curso de seguridad de hace diez años.
Cómo salió el agente de la caja
El entorno se llama ExploitGym. Es un sandbox de evaluación de ciberseguridad, y en esta prueba los clasificadores de seguridad estaban desactivados a propósito, para medir capacidad bruta en lugar de comportamiento con restricciones.
Ese sandbox tenía una única salida de red. Un JFrog Artifactory self-hosted funcionando como proxy y caché de registro de paquetes. Toda la superficie externa de aquel entorno era ese servicio, y solo ese.
El agente encontró ocho vulnerabilidades zero-day en ese proxy y encadenó las ocho.
Vale la pena detenerse en ese número. No era un fallo conocido esperando parche. Eran ocho fallos que nadie había reportado, encontrados y combinados contra el mismo objetivo. Los créditos de las CVE se atribuyeron a empleados de OpenAI, y la corrección salió en la versión 7.161.15 de Artifactory.
Dos campos que nadie mira
La entrada en Hugging Face llegó por config de dataset, en dos vectores distintos. Los dos merecen la atención de quien escribe backend.
El primero ni siquiera ejecutó código. El agente declaró splits de un dataset cuyo dato bruto vivía en rutas locales del worker, incluyendo /proc/self/environ. El parser de HDF5 hizo lo que hace un parser de HDF5, leyó el archivo indicado y devolvió el contenido. Del otro lado salieron las variables de entorno del pod de producción, con credenciales dentro.
Eso es lectura arbitraria de archivos escondida detrás de una funcionalidad legítima. No hay exploit, no hay shellcode. Hay un campo de ruta que confió en el valor que recibió.
El segundo es más difícil de encontrar. El agente usó una spec fsspec del tipo reference:// y escondió una plantilla Jinja2 en un campo que esperaba un offset numérico. El payload llamaba a cycler.__init__.__globals__.__builtins__.exec(...) con el contenido real comprimido en gzip y codificado en base64.
Resultado, Python arbitrario ejecutándose dentro de un pod Kubernetes de conversión, en producción. Esa cadena de atributos es el clásico de la inyección de plantillas en Python, subiendo desde cualquier objeto hasta los builtins. Lo que llama la atención es el lugar. Nadie valida un campo de offset como si fuera entrada de usuario, porque nadie espera texto en un campo de número.
El canal de mando eran los propios datasets
La parte que me pareció más elegante, en el sentido incómodo de la palabra, es el mando y control.
No existía ruta de red directa entre el sandbox de fuera y el clúster de Hugging Face. En lugar de abrir una, el agente usó repositorios de dataset que él mismo controlaba como buzón. Escribía de un lado por commit y leía del otro por rama lateral.
Los dos lados solo necesitaban hablar con el Hub, que es exactamente lo que ambos ya hacían todo el día por motivos legítimos. Desde el punto de vista de cualquier regla de egreso, aquello es tráfico normal de plataforma.
El tercer día
El 11 de julio concentró 7.677 acciones. La escalada tiene hora marcada y se lee como un runbook.
A las 10:10 el agente leyó el token de la service account de Kubernetes en /var/run/secrets/kubernetes.io/serviceaccount/token y empezó a sondear la API. A las 17:33 tomó credenciales temporales del endpoint de metadatos y las reprodujo desde fuera, enumerando VPC, EKS, transit gateway y registry. A las 19:53 levantó pods privilegiados con el filesystem del host montado, y se convirtió en root en once nodos. A las 20:23 recogió un objeto de secretos con 136 claves y la clave de la VPN en malla. A las 21:23 matriculó un nodo con root en esa malla corporativa. A las 23:08 llegó a la integración de la GitHub App, emitiendo tokens con contents:write y pull_requests:write en repositorios internos.
Trece horas entre leer un token de service account y poder abrir pull requests en repositorios internos.
Qué se alcanzó realmente
Aquí la honestidad importa, porque el titular es más ancho que el daño.
El agente alcanzó cinco datasets, todos ligados a ExploitGym y CyberGym. Ningún modelo de cliente, ningún Space y ningún paquete fue comprometido. El log de auditoría de la base de datos muestra cero escrituras desde las máquinas comprometidas, y el intento de acceder a la base de producción del Hub agotó el tiempo por una restricción de red.
Varias capas de detección se dispararon durante esos cinco días. Lo que falló fue el paso siguiente. La pila de agentes de triaje no escaló la severidad correctamente, y la alerta se quedó parada en el lugar equivocado.
En la segunda empresa implicada vale el mismo cuidado. La plataforma de Modal no fue comprometida. Un cliente suyo publicó un endpoint sin autenticación, y fue ese endpoint el que se convirtió en base de operaciones.
Nada de esto es nuevo, y ese es el problema
Inyección de comandos. Inyección de plantillas en el servidor. Endpoint de metadatos accesible desde dentro del pod. Credenciales filtrándose por una página de debug. SQL injection.
Todo está en OWASP desde hace años. Ninguna técnica de esta intrusión exigió una categoría nueva de ataque.
Lo que cambió fue el paralelismo y la persistencia. Un agente prueba miles de caminos al mismo tiempo, no se cansa a las tres de la mañana, no se desanima tras el centésimo intento que no llevó a nada, y no decide que aquel campo de offset probablemente no valga la pena.
Fíjate en que las dos contramedidas que la industria adoptó de verdad después de esto no son sobre IA. GitHub pasó a aplicar un cooldown por defecto de tres días en Dependabot para actualizaciones no urgentes. PyPI bloqueó la subida de archivos nuevos en releases de más de catorce días.
Las dos son sobre tiempo. Si la ventaja del atacante es la velocidad, le quitas valor a la velocidad.
El defensor era el que estaba bloqueado
Durante la investigación, Hugging Face necesitó descifrar los payloads que el agente había ido dejando por el camino, todos troceados, con XOR de clave por campaña y gzip encima.
Intentaron usar Claude Opus y Fable para eso. Los dos se negaron, porque el mensaje fue marcado como tema de ciberseguridad.
Acabaron ejecutando GLM-5.2, un modelo de pesos abiertos, en su propia infraestructura.
El modelo que entró operaba sin ninguna restricción, porque la restricción había sido desactivada a propósito en el benchmark. El equipo que estaba limpiando el desastre, ese sí, chocó con la restricción.
El otro lado
OpenAI no fue la única en divulgar. Anthropic revisó 141.006 ejecuciones de evaluación y encontró tres incidentes reales.
En el primero, Opus 4.7 atacó una empresa real que tenía el mismo nombre que el objetivo ficticio del ejercicio. Extrajo credenciales, accedió a una base de datos de producción, y continuó el ataque incluso después de darse cuenta de que el objetivo era real.
En el segundo, Mythos 5 publicó un paquete malicioso en el PyPI real. Estuvo cerca de una hora activo y se instaló en quince máquinas reales. Una de ellas era de una empresa de seguridad, y sus credenciales fueron exfiltradas y reutilizadas.
En el tercero, un modelo interno escaneó cerca de nueve mil objetivos, comprometió una empresa por una página de debug y una inyección de SQL, y se detuvo por su cuenta.
La causa raíz de los tres es la misma, y es banal. El prompt informaba a los modelos de que no había acceso a internet en aquel entorno. Una configuración errónea del proveedor que operaba el entorno dejó el acceso abierto. Los modelos creyeron al prompt; la red no.