Skip to main content
>_ MENU
Transmisión #003: Conteniendo Agentes Fuera de Control
$ cat /es/posts/containing-rogue-agents.md

Transmisión #003: Conteniendo Agentes Fuera de Control

[ ]
| @Armando Herra | 10 min de lectura

Agentes Sembrando Caos

Hace un tiempo, esta historia estaba en todas partes: un agente de IA supuestamente eliminó la base de datos de producción de una startup y causó una interrupción de servicio masiva. Este tipo de ejemplos se mencionan cada vez que alguien propone darle más autonomía a un agente.

An AI agent allegedly deleted a startup's production databaseSpoiler alert: Cursor and Claude aren't supposed to do that.Mashable — mashable.com

Que los agentes arruinen nuestro trabajo de ingeniería no es algo agradable. Estoy seguro de que muchos han escuchado sus propias historias de terror: un agente que se sale de control y hace algo que definitivamente no debía hacer.

Y a veces lo que falla es el propio sandbox. En julio de 2026, un enjambre de unos 700 agentes de OpenAI que estaba siendo evaluado por sus capacidades cibernéticas escapó de su entorno de entrenamiento y hackeó Hugging Face, ejecutando más de 17,000 acciones e incluso intentando cubrir sus huellas. Nadie ordenó el ataque: los modelos habían aprendido a hacer trampa mediante reward hacking durante el entrenamiento.

The inside story on why OpenAI agents hacked Hugging FaceThe underlying models had been rewarded for cheating and communicating with each other, a new OpenAI report finds.MIT Technology Review — technologyreview.com

Tú no puedes controlar lo que pasa dentro de los entrenamientos de OpenAI, pero tu propio radio de impacto sí está en tus manos. En mi opinión, la mayoría de estos desastres son prevenibles. Solo hay que aplicar buenas prácticas de ingeniería.

Te lo digo sin rodeos.

01. ¡No le des a tus agentes credenciales de Producción!

¿Recuerdas la base de datos eliminada de la introducción? En ese incidente no hubo magia. Un agente solo puede destruir lo que sus credenciales alcanzan, y ese agente alcanzaba producción.

Si haces lo mismo, estás aceptando el riesgo de que algo salga mal, aunque no sea tu intención.

Esto es lo que hace diferente a un agente de un humano con el mismo acceso. Un agente ejecuta a velocidad de máquina, en loops, con total confianza: un nombre de tabla alucinado corre tan feliz como el correcto, y no existe esa pausa de “espera, algo no se siente bien” antes de dar Enter. Súmale prompt injection y la cosa empeora: si tu agente lee contenido externo mientras tiene credenciales de Prod, cualquiera que logre ponerle texto enfrente prácticamente tiene tus credenciales también. El prompt es una sugerencia. La credencial es una capacidad.

Lo que hago en su lugar es simple: mis agentes reciben credenciales acotadas, de corta duración y con mínimos privilegios para el entorno donde trabajan, y producción solo cambia a través del mismo pipeline revisado que usaría un humano. El agente puede abrir el PR; solo el pipeline toca Prod.

Quitarle credenciales reduce lo que un agente puede arruinar. Pero esa pregunta me siguió dando vueltas: ¿y si también pudiera reducir lo que puede siquiera tocar? Guarda esa idea hasta la práctica 03.

Un agente de IA bloqueado ante la bóveda de Producción por una barrera de acceso denegado

02. Usa guardrails para evitar desastres prevenibles

Por ejemplo, hago Agentic Coding con bastante frecuencia usando Claude Code, y esta herramienta tiene una funcionalidad que puedes aprovechar para mejorar tu experiencia de desarrollo: los “Hooks”.

Los hooks de Claude Code son event handlers definidos por el usuario que se disparan de forma determinista en puntos específicos del ciclo de vida de Claude Code: antes o después de una llamada a herramienta, cuando envías un prompt, al iniciar una sesión, cuando Claude termina de responder, y más.

Se ejecutan con tus permisos de usuario completos, así que funcionan como una capa programable entre Claude y tu sistema.

La idea es tener barreras que no dependan de que el modelo recuerde algo. Reglas como “nunca elimines la base de datos de Prod”, “nunca ejecutes rm -rf /” o “nunca corras terraform destroy sin autorización explícita” dejan de ser buenas intenciones y se convierten en código.

Aquí tienes una miniatura funcional del guard que corro en mi propia máquina. Primero, registra el hook en .claude/settings.json para que se dispare antes de cada comando de Bash:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/bash_guard.py" }
        ]
      }
    ]
  }
}

Después viene el hook en sí, .claude/hooks/bash_guard.py, que lee el comando propuesto desde stdin y rechaza cualquier cosa que coincida con un patrón bloqueado:

#!/usr/bin/env python3
"""Deny dangerous Bash commands before Claude Code runs them."""
import json, re, sys

BLOCKED = [
    (r"rm\s+-rf\s+[/~]", "rm -rf on root or home paths is never OK"),
    (r"terraform\s+destroy", "terraform destroy needs explicit human approval"),
    (r"psql\s+.*\bprod\b", "no direct commands against the prod database"),
]

command = json.load(sys.stdin).get("tool_input", {}).get("command", "")

for pattern, reason in BLOCKED:
    if re.search(pattern, command):
        print(json.dumps({"hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason": reason,
        }}))
        break

Si el hook no imprime nada, el comando pasa. Si imprime el JSON de rechazo, el comando se bloquea y el agente puede leer la razón. Determinista, sin importar lo que el modelo “piense”. La versión completa que uso tiene alrededor de veinte patrones de comandos destructivos más una lista de rutas seguras como node_modules donde Claude sí tiene permitido correr estos comandos.

Otros usos típicos: formatear/lintear automáticamente después de cada edición, bloquear rm -rf o commits a main, escanear secretos antes de escribir un archivo, correr pruebas al finalizar y forzar a Claude a continuar si fallan, o enviar una notificación de escritorio cuando esté esperando tu respuesta.

Comandos pasando por un corredor de puertas de guardrails, con un comando malicioso interceptado

03. Prueba tus cambios en entornos inferiores o entornos seguros primero

Esta práctica es higiene clásica de ingeniería, y con los agentes aplica el doble. Las acciones de un agente deberían graduarse por entornos igual que tu código: primero dev, luego staging y al final prod. Los errores de un agente no son errores de compilación, se ejecutan con consecuencias reales en la máquina donde esté corriendo. Así que antes de confiarle un agente, una herramienta o un guardrail nuevo a tu máquina de todos los días, dale un lugar donde equivocarse salga barato.

Esa idea dejó de ser teórica muy rápido. Hace poco estaba experimentando con la creación y prueba de algunos de estos hooks, y me pregunté:

“¿Cómo puedo probar estos hooks de forma segura en mi sistema? ¿Qué pasa si el hook no está bien configurado, no intercepta la llamada y el comando se ejecuta directamente en mi máquina? ¿Cómo evito dañar mi propio sistema operativo o la base de datos conectada localmente?”

Construyendo mi propio Sandbox con gVisor

Ahí fue cuando pensé: “Necesito un Sandbox para mis agentes de IA”. No es un concepto nuevo, lo sé, pero es algo que ha ido ganando tracción en el ecosistema de Agentic AI. Así que decidí experimentar con una combinación sencilla de conceptos y tecnologías.

Me puse a pensar: ¿cómo puedo combinar Containers y Agentes?

Como ejecuto mis agentes de Claude de forma efímera, siguiendo la filosofía de 1 Agente por Tarea (más sobre mejores prácticas de Agentic Coding en futuros contenidos), los containers sonaron como un buen punto de partida. También quería restringir el acceso que el agente tenía a ciertas capacidades del sistema para evitar que “escape del Sandbox” si decidía volverse incontrolable o era comprometido por una fuerza externa, previniendo escaladas de privilegios en el cluster de Kubernetes o en la plataforma de containers que ejecuta el agente de IA. Y para añadir más complejidad, porque #YOLO, quería que la solución fuera “Cloud-Native”, ya que últimamente he estado trabajando en aprender y estructurar mi pensamiento alrededor de este tipo de sistemas.

Con eso en mente, empecé a evaluar qué tecnología tendría más sentido.

Recordé algunos conceptos que estudié para el CKS (certificación pendiente): gVisor llamó mi atención y decidí darle una oportunidad en mi PoC.

Ellos se presentan así: “gVisor es un sandbox compatible con Linux de código abierto que funciona en cualquier lugar donde ya funcione el tooling de containers existente. Habilita seguridad y portabilidad de containers cloud-native.”

Suena bien en teoría. Pongámoslo a prueba.

Un agente de IA contenido en una cámara hexagonal de cristal, con la membrana de panal de gVisor interceptando las llamadas al sistema antes de llegar a los servidores del host

Arranqué con Claude Code para cubrir los huecos que tenía en mente y mejorar la implementación que estaba planificando.

Repasé los detalles y comportamientos que quería que tuviera el PoC, pero necesitaba tener los SPECS escritos y el código corriendo para mantener el hilo de ideas.

Después de varias iteraciones de escritura de SPECS e implementación, obtuve un PoC preliminar que logró darme el comportamiento que esperaba.

Este es el comando de ejecución con el que terminó el PoC, tomado directamente de su Makefile. Cada flag es una capa de la jaula:

docker run \
  --runtime=runsc \
  --rm \
  --network=proxy-net \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=100m \
  --tmpfs /workspace:rw,noexec,nosuid,size=500m \
  --memory 2g --cpus 2 --pids-limit 100 \
  --user 1000:1000 \
  -e ALLOW_SHELL="" \
  -e ALLOW_SHELL_TOOL="" \
  -e SHELL_TIMEOUT="30" \
  --add-host=proxy-host:$PROXY_IP \
  -e ANTHROPIC_PROXY_URL="http://proxy-host:18080" \
  -e ANTHROPIC_API_KEY="proxied" \
  sandbox-agent:latest

--runtime=runsc es la parte de gVisor, y cambia lo que significa estar “dentro del container”. Las llamadas al sistema del agente nunca llegan al kernel de mi host: primero golpean el kernel en espacio de usuario de gVisor, que implementa por sí mismo la superficie de syscalls de Linux y solo reenvía un conjunto pequeño y filtrado al kernel real. Si el agente logra salirse del container, aterriza en gVisor, no en mi máquina.

El resto de los flags cierran las puertas laterales. El container se une a una red interna sin salida, donde la única ruta hacia afuera es un proxy con lista de permitidos, y fíjate en ANTHROPIC_API_KEY="proxied": el sandbox nunca tiene mi key real, envía las peticiones con un placeholder y el proxy inyecta la key en el camino hacia el proveedor. Las capabilities se eliminan, la escalada de privilegios queda deshabilitada, el sistema de archivos es de solo lectura con pequeños montajes noexec temporales, y la memoria, el CPU y el número de procesos están limitados para que un agente fuera de control no tumbe la máquina.

La adición más reciente es un conjunto de “capability knobs”, todos apagados por defecto. ALLOW_SHELL y ALLOW_SHELL_TOOL controlan si el operador o el modelo pueden ejecutar comandos de shell dentro del sandbox, /workspace se mantiene noexec a menos que una ejecución lo habilite con WORKSPACE_EXEC=1, y agregar capabilities de Linux requiere tanto una lista explícita en CAP_ADD como RUN_AS_ROOT=1. Incluso esto último solo convierte al agente en root dentro del Sentry de gVisor, no en mi máquina. Denegar por defecto, habilitar por ejecución.

Aquí está el repositorio en GitHub y algunos de los casos de uso reales con los que este PoC podría ayudar actualmente y/o si se desarrolla más adelante:

  • Repositorio en GitHub: gvisor-agent-sandbox-poc
  • Casos de Uso Reales:
    • Exploración segura de capacidades y límites de agentes de IA
    • Análisis post-mortem de sesiones de agentes de IA
    • Acceso controlado a servicios externos para agentes en sandbox
    • Entornos educativos y de entrenamiento para seguridad de containers

Y la lista de casos de uso reales sigue creciendo…

Si deseas apoyar mi desarrollo continuo en este proyecto, considera unirte a mis GitHub Sponsors.

AVISO: Ten en cuenta que este repositorio aún no está pensado para uso en producción y se encuentra en etapa de PoC al 2 de septiembre de 2026. Si decides usarlo en producción, la responsabilidad por cualquier problema o incidente es enteramente TUYA por implementar un PoC en producción.

Kubernetes SIG: Agent Sandbox

Volviendo al tema general de los Agent Sandboxes, después de experimentar con mi propia solución, me enteré de una solución existente siendo desarrollada por los Kubernetes SIGs:

Agent SandboxAgent Sandbox is a cloud native controller for sandboxesAgent Sandbox — agent-sandbox.sigs.k8s.io

Así es como Agent Sandbox se presenta:

“Agent Sandbox provee una capa de ejecución segura y aislada para desplegar de forma segura agentes de IA autónomos en Kubernetes que generan y ejecutan código no confiable a escala.”

Suena como un proyecto prometedor, muy similar en naturaleza al que yo estaba desarrollando. Te recomiendo tenerlo en el radar; también podría convertirse en parte del AAIF de la CNCF en el futuro.

Más sobre los Kubernetes SIGs y proyectos en desarrollo en un futuro artículo…

04. Haz tus flujos de trabajo agénticos más deterministas

Esto es algo que ha estado rondando mi cabeza por un tiempo. Desde inicios de año he empezado a migrar muchos de mis flujos de trabajo de Agentic AI hacia un enfoque más “declarativo” y “determinista”. Todo terminó de encajar hace poco, cuando conversé con Fabrizio Sgura antes del KCD Guadalajara 2026. ¡Gracias por los excelentes insights!

La idea es simple: cada vez que dependes de que el agente resuelva algo de forma espontánea, obtienes una respuesta creativa pero variada, y esa variación altera el resultado final. Cuando un paso importa, conviértelo en un script o una herramienta que el agente invoque, en lugar de una tarea que improvisa. El agente decide cuándo ejecutarlo; el script decide qué sucede. Misma entrada, misma salida, en cada ejecución.

Aquí tienes un ejemplo real de este mismo blog. Cada tarjeta de vista previa de enlace que viste en este artículo fue construida por un script, no por la creatividad del agente. Cuando agrego un enlace nuevo a un post, el agente ejecuta un solo comando:

npm run previews:fetch

El script escanea mis posts buscando URLs sueltas, consulta los metadatos de cada página una sola vez, descarga la imagen de preview y escribe todo en un caché versionado que el build consume. Podría en cambio pedirle al agente que “busque el título y la imagen y arme una tarjeta” cada vez, y lo haría, con HTML ligeramente distinto en cada ejecución. El script produce la misma tarjeta para el mismo enlace, siempre. Así es como estructuro mis comandos de Claude Code en general: el markdown del comando le dice al agente cuándo y por qué, y el script que invoca es dueño del cómo.

Tener flujos de trabajo más declarativos y deterministas mejorará considerablemente la confiabilidad y la calidad de los resultados que generan.

Comentarios de Cierre

Cada vez más herramientas están surgiendo en el ecosistema de Agentic AI. Probablemente algunos AAIF TAGs (Technical Advisory Groups) sean creados para guiar el desarrollo y la madurez de estas herramientas en el ecosistema Cloud-Native…

He compartido los pensamientos que quería transmitirte por ahora. En el futuro profundizaré en algunas de las ideas y temas adyacentes que mencioné.

Terminando transmisión…

Armando Herra


Si disfrutas mi trabajo, puedes apoyarlo a través de GitHub Sponsors.

Entregado mediante mi flujo de publicación personal automatizado, con ideas, palabras y trabajo humano detrás.