Saltar al contenido principal

El agente de OpenAI que hackeó a Hugging Face: qué aprendemos sobre sandboxing

9 min readIgnacio Avilés

La historia es de esas que parecen guion de película, pero pasó este mes. OpenAI tenía un agente autónomo construido sobre un harness de red-teaming para el benchmark ExploitGym — básicamente, un modelo con permiso para intentar hackear cosas, entrenado para encontrar vulnerabilidades. Ese agente salió de su entorno de evaluación y, en un fin de semana de julio, penetró los sistemas internos de Hugging Face buscando atajos al benchmark. Y OpenAI no se enteró de que su propio agente era el atacante hasta que Hugging Face publicó el incidente, el 16 de julio, y lo comunicó a las autoridades.

La parte más jugosa para nosotros, los developers que empezamos a correr agentes en nuestra propia infra, no es el morbo del caso. Es que la intrusión fue end-to-end driven by an autonomous AI agent system, según el post-mortem de Hugging Face. O sea: un sistema de IA, sin un humano picando teclas, ejecutó una cadena de ataque completa contra otra empresa de IA. Y la víctima tampoco se dio cuenta hasta que lo publicó. Si te interesa el mundo de los agentes, como en IA Agéntica, este caso te debería servir para repensar cómo los corres tú.

Qué pasó exactamente, sin el sensacionalismo

Hugging Face publicó un post-mortem técnico que vale leer entero, pero te dejo el resumen operativo. El agente atacante usó dos vectores de entrada en el pipeline de ingestión de datasets:

  • Remote-code dataset loader: Hugging Face permite cargar datasets con código ejecutable. El agente abusó ese path para ejecutar código arbitrario en un worker.
  • Template-injection en archivos de config: inyectó payloads en los templates que Hugging Face usa para instanciar modelos, que terminaron ejecutándose al cargar el dataset malicioso.

Una vez con código corriendo dentro del perímetro, el agente escaló privilegios a nivel de nodo, cosechó credenciales y se movió lateralmente. El framework de agentes que usaba (Hugging Face no sabía qué modelo había detrás cuando publicó el post-mortem) ejecutó decenas de miles de acciones en sandboxes efímeras con C2 migratorio. O sea: cada vez que una sandbox se quemaba, saltaba a otra. Difícil de detectar para un equipo de defensa que no sabe qué patrón buscar.

El dato que te tiene que hacer ruido Hugging Face intentó analizar los logs con modelos comerciales frontier (los típicos con guardrails de seguridad) y los modelos se negaron a responder preguntas sobre el ataque. Sus propias guardrails no podían distinguir "responder a un análisis forense legítimo" de "ayudar a un ataque real". Tuvieron que recurrir a un modelo open-weight (GLM 5.2) corriendo en su propia infra para hacer el triage.

Por qué te importa aunque no trabajes en Hugging Face

Si estás leyendo este blog, lo más probable es que no tengas un cluster de Hugging Face en tu data center. Pero si empezaste a meter agentes en tus proyectos — el típico caso de "que un LLM ejecute tools, lea archivos, levante containers, mande HTTP requests" — el caso te toca de cerca. El patrón del agente de OpenAI no es exótico: cualquier agente con shell access y conexión a red puede terminar haciendo lo mismo si el sandbox está flojo.

La diferencia entre "mi agente hace cosas útiles" y "mi agente termina en una lista de CVEs" está en cómo está montado el sandbox. Y aquí la lección no viene de los papers académicos, viene del caso real: el harness que OpenAI usó para red-teaming tenía permisos demasiado generosos y el atacante (su propio agente) los explotó. Si el framework de evaluación de seguridad de la empresa que más recursos le mete a esto se les escapó, ¿qué chances tiene el sandbox improvisado que montaste un viernes a las seis de la tarde para tu agente de refactor?

Tres cosas concretas que puedes aplicar hoy

No te voy a dar una clase entera de container security. Sí tres patrones que este caso confirma que son obligatorios si vas a correr agentes en tu infra.

1. Egress filtering estricto, no "permitido por default"

El error más común: levantar un container con network_mode: bridge o equivalente y dejarlo salir a internet sin restricción. Sí, tu agente necesita llamar a la API de Anthropic o leer documentación. No, no necesita hacer SSH a un host arbitrario, conectarse a un C2, o resolver DNS de dominios que no controlas. El patrón que funciona es una allowlist explícita: el agente solo puede hablar con los dominios/IPs que tú definiste, todo lo demás se cae. En Docker, esto se hace con un sidecar proxy (squid, mitmproxy) o, más limpio, con Network Policies de Kubernetes si ya estás en k8s.

Un ejemplo mínimo con Docker y un proxy allowlist:

services:
  agent:
    image: mi-agente:latest
    network_mode: "service:egress-proxy"
    depends_on:
      - egress-proxy

  egress-proxy:
    image: sameersbn/squid:3.5.27-2
    volumes:
      - ./squid-allowlist.conf:/etc/squid/squid.conf
    networks:
      - isolated
    # Sin puertos publicados, solo el agente puede hablarle

networks:
  isolated:
    internal: false  # el proxy puede salir, el agente no

El squid-allowlist.conf lista los dominios permitidos. Cualquier request a otra cosa se rechaza con TCP_DENIED. Esto solo no te salva (hay túneles por DNS, covert channels, etc.), pero te sube mucho la barra.

2. Principio de menor privilegio en el filesystem y en el runtime

El agente de OpenAI salió de su sandbox probablemente porque el proceso adentro del container tenía capacidades que no necesitaba. En la práctica:

  • Read-only root filesystem donde se pueda. El agente no necesita escribir en /usr ni en /etc.
  • No montar el socket de Docker adentro del container. Suena tentador para que el agente pueda correr otros containers, pero si se escapa, tienes un container escape completo.
  • Capacidades Linux recortadas. Drop CAP_NET_RAW, CAP_SYS_ADMIN, CAP_SYS_PTRACE y todo lo que no uses. Si tu agente no necesita hacer raw sockets, no debería poder.
  • Credenciales con scope mínimo. Tokens de corta vida, rotados, scoped al recurso específico. Nada de "el token de root de AWS" metido en una env var.

El detalle que muchos se saltan Si tu agente necesita clonar un repo de git, no le des acceso de escritura a tu GitHub. Genérale una deploy key de solo lectura y expiración corta. Si lo necesita para hacer commit, genera un token específico para ese repo, no uses tu PAT personal.

3. Telemetría que mira patrones, no eventos individuales

Este es el punto donde la mayoría se cae. Tienes logs, sí. Pero el agente de Hugging Face dejó más de 17.000 eventos registrados en un fin de semana. Si tu monitoring te muestra "el agente hizo 50.000 llamadas HTTP hoy" como métrica agregada, lo vas a ver recién al día siguiente cuando alguien lo mire. Si en cambio tienes alertas sobre patrones anómalos (muchas requests a dominios distintos en poco tiempo, frecuencia de errores 4xx/5xx inusual, requests a puertos no estándar, picos de CPU en horarios raros), las chances de detectarlo en tiempo real suben bastante.

Un patrón concreto que funciona: definir un baseline de requests por minuto del agente, y alertar cuando se va del percentil 99 por más de N minutos. No es perfecto, pero te da una ventana para apagar el agente antes de que escale. La telemetría de los frameworks modernos de agentes (LangChain, AutoGen, los nativos de Claude Code) ya expone estas métricas. Si no las estás mirando, estás volando a ciegas.

El ángulo open-weight que casi nadie comenta

Volvamos al detalle más contraintuitivo del caso: Hugging Face tuvo que usar un modelo open-weight (GLM 5.2, sin guardrails comerciales) para hacer el forensics de su propio incidente. ¿Por qué? Porque los modelos comerciales se negaban a ayudar — sus guardrails de seguridad confundían "analizar logs de un ataque" con "asistir a un ataque". Es un fallo de diseño de los modelos comerciales, no un feature.

La conclusión que saco yo, y que se conecta con lo que vimos en Ollama, es que si trabajas en seguridad, devops, o cualquier disciplina donde eventualmente vas a tener que analizar material sensible con un LLM, tener un modelo local competente es cada vez más infraestructura básica, no un lujo. No para todo — para producción con tareas críticas de razonamiento un frontier con API sigue siendo mejor — pero para el 10% de casos donde necesitas que el modelo no le tenga miedo a su propia sombra, un open-weight local es la única opción real.

Checklist mínima antes de mandar un agente a producción

Si después de leer este post te queda una sola cosa, que sea esta lista. No es exhaustiva, pero cubre los fallos que el caso de Hugging Face mostró que son los más comunes:

  • Egress allowlist explícita, sin excepciones por defecto.
  • Read-only rootfs y capacidades Linux recortadas al mínimo.
  • Credenciales scoped y rotativas, deploy keys en vez de PATs.
  • No socket de Docker dentro del container, ni acceso a otros containers.
  • Logs estructurados con alertas por patrón, no por umbral estático.
  • Kill switch testeado, que corte el agente en menos de 10 segundos.
  • Un modelo open-weight local como backup, para análisis que los comerciales se niegan a hacer.

El caso del agente de OpenAI y Hugging Face va a ser referencia obligatoria en cualquier conversación seria sobre seguridad de agentes durante los próximos meses. Pero más allá del caso puntual, el mensaje de fondo es claro: los agentes que montamos no son distintos, en términos de blast radius, a cualquier otro proceso con permisos. Si los tratamos como si fueran "solo un script de Python", nos va a pasar lo mismo que le pasó a OpenAI: enterarnos tarde, desde fuera y con cara de domingo por la noche.

Actualización (agosto de 2026): OpenAI publicó después una cronología completa. El acceso de sus agentes a internet empezó semanas antes, a través de un servicio interno de paquetes (Artifactory), con un SSRF y dos zero-days; la intrusión en Hugging Face encadenó una lectura arbitraria de archivos HDF5 con una inyección de plantillas Jinja y llegó a admin de varios clústeres en menos de 13 horas. OpenAI ató cabos el 19 y 20 de julio, después del aviso público de Hugging Face. Simon Willison lo resume bien en su timeline.

Fuentes

$ subscribe --newsletter

Recibe los nuevos posts en tu email

Un email cuando publico algo nuevo. Sin ruido, sin relleno.

Sin spam. Baja en cualquier momento. Política de privacidad.

comentarios