Seguridad de los AI agents: permisos, sandboxes y amenazas de MCP
Traducción automática Este artículo se tradujo automáticamente a partir de la versión original en inglés.
Actualización del artículo
Publicado originalmente el 20 de abril de 2026. Revisado y actualizado el 6 de septiembre de 2026. La actualización cubre nuevos controles de sandbox, intervenciones de seguridad de los proveedores y hallazgos de seguridad publicados, junto con sus limitaciones y enlaces a las fuentes.
La seguridad de un agent empieza cuando un modelo propone una acción y antes de que la máquina la ejecute. Decide qué comprobación tiene la última palabra antes de que la acción llegue a las credenciales, los archivos, las redes o un sistema externo.
El harness es el código que construye cada prompt, decide qué tool calls propuestas se ejecutan y devuelve los resultados al modelo. La mayoría de las comprobaciones deben estar ahí porque es el último punto barato en el que se puede detener un comando. Después de que se ejecute un comando, el sandbox, las credenciales que haya recibido y cualquier proceso de recuperación deben contener los daños. Algunos incidentes de este artículo ni siquiera llegan a un modelo.
La seguridad de los AI agents es más amplia que la seguridad de los LLM. Los primeros productos de guardrails inspeccionaban la entrada y la salida de una única llamada al modelo. Podían filtrar texto tóxico, ocultar datos personales, bloquear jailbreaks y rechazar respuestas fuera de tema. Ese límite era útil mientras el modelo solo podía devolver texto.
Los tool loops añadieron sistemas de archivos, shells, servidores de Model Context Protocol (MCP) y credenciales. Eso amplió el threat model: pasó del texto inseguro a las acciones inseguras. Los siete grupos de incidentes siguientes abarcan la prompt injection indirecta y fallos de configuración, identidad y distribución de software. El filtrado de texto puede ayudar con algunas entradas maliciosas; no puede sustituir a los controles en esos límites de ejecución.
Cuando un agent puede leer un repositorio, llamar a una herramienta o enviar datos a un tercero, asigna cada acción propuesta a la comprobación que puede detenerla. Las secciones siguientes cubren permisos, hooks, sandboxes, credenciales y revisión humana.
Para consultar la checklist breve de controles, véase Checklist de seguridad de AI agents.
Pila de seguridad de los AI agents
Ningún guardrail protege por sí solo a un agent. Cada parte del sistema necesita su propia comprobación.
La tabla indica dónde se ejecuta cada comprobación. El harness es el programa de control descrito arriba. El runtime es la infraestructura que utiliza: el sandbox, el registro de la sesión, el almacén de checkpoints y las trazas que sobreviven al reinicio de un worker.
| Capa | Qué controla | Ejemplo de fallo que detecta | Dónde reside |
|---|---|---|---|
| Filtros de contenido | Texto de entrada y salida inseguro | Salida tóxica, filtración de PII, completions que infringen políticas | Harness |
| Escalera de permisos | Qué herramientas, rutas, APIs y scopes puede usar el agent | Un summarizer que intenta escribir en sistemas de producción | Harness |
| Pre-tool policy hook | Si esta acción concreta debe ejecutarse ahora | Comando de shell construido a partir de contenido recuperado no fiable | Harness |
| Sandbox | Qué puede tocar la herramienta en las capas del sistema operativo y la red | Exfiltración de archivos, compromiso de dependencias, command injection | Runtime |
| Comprobación de aprobación humana | Acciones irreversibles o de alto impacto | Enviar un email, mover dinero, desplegar en producción | Harness |
| Scoping de MCP y tokens | Para qué servidor y audiencia es válida una credencial | Reutilización de un token en un servidor de herramientas no previsto | Runtime |
| Traza de auditoría | Qué ocurrió, quién lo aprobó y por qué | Investigación de un incidente tras una ejecución autónoma prolongada | Runtime |
Los filtros de contenido preguntan si el modelo ha dicho algo inseguro. La seguridad de los agents también pregunta si el sistema puede realizar la siguiente acción.
Las filas del harness deciden si una acción puede ejecutarse. Las filas del runtime hacen cumplir los límites establecidos de antemano y registran lo ocurrido. Mantén el sandbox aunque las reglas de permisos parezcan completas: puede detener una llamada que el harness no haya previsto. No puede decidir si una acción permitida era la correcta; eso debe hacerlo el harness.
La última columna indica dónde se ejecuta una comprobación, no quién la opera. Un proveedor puede ofrecer un filtro de contenido, pero es el harness quien lo invoca.
Los filtros de contenido gestionados cubren la capa de texto. El resto de controles debe estar en la política de la aplicación, la identidad y la infraestructura.
Por qué la seguridad de los AI agents es distinta de la seguridad de los LLM
Bharani Subramaniam y Martin Fowler plantearon el marco a principios de 2025 en Emerging Patterns in Building GenAI Products. Su observación era concreta y directa:
«Con los sistemas tradicionales, podíamos evaluar la corrección principalmente mediante pruebas… Con los sistemas basados en LLM, nos encontramos con un sistema que ya no se comporta de forma determinista».
La evaluación de la salida pregunta si la respuesta del modelo cumple una rúbrica. El threat model de un agent también debe cubrir tool calls, comandos de shell, escrituras de archivos, credenciales y peticiones de red. Un evaluador de la salida no puede detener esas acciones. El harness sí: es el conjunto de comprobaciones que convierte una propuesta del modelo en una acción permitida. El resto de este artículo cubre esas comprobaciones.
Simon Willison acuñó la forma del riesgo específico de los agents en junio de 2025 con la tríada letal:
«La tríada letal de capacidades es: acceso a tus datos privados; exposición a contenido no fiable; capacidad de comunicarse externamente de una forma que podría utilizarse para robar tus datos. Si tu agent combina estas tres características, un atacante puede engañarlo fácilmente para que acceda a tus datos privados y se los envíe».
Muchos agents útiles combinan estas capacidades: acceso a la bandeja de entrada, recuperación web y una herramienta de mensajería; o acceso a un repositorio, lectura de issues y escritura de pull requests. Un guardrail de contenido pregunta si el modelo ha generado texto inseguro. La tríada pregunta si una entrada no fiable puede dirigir al sistema para divulgar datos mediante una acción permitida.
La versión estructural del mismo argumento aparece en el preprint Parallax de Joel Fokou (arXiv 2604.12986, enviado el 14 de abril de 2026, no revisado por pares). La afirmación central es:
«El sistema que razona sobre las acciones debe ser estructuralmente incapaz de ejecutarlas, y el sistema que ejecuta las acciones debe ser estructuralmente incapaz de razonar sobre ellas, con un validador independiente e inmutable interpuesto entre ambos».
No es necesario aceptar las cifras de evaluación del artículo para examinar su argumento estructural. Varios harnesses actuales implementan partes de esta misma separación:
- Hooks PreToolUse de Claude Code
- Ejecutor de Codex CLI en un OS sandbox (en Linux, bubblewrap más filtrado de llamadas al sistema mediante seccomp)
- Managed Agents de Anthropic, que mantienen las credenciales en un vault que el agent nunca ve
- Tokens vinculados a audiencia de MCP mediante RFC 8707
Estos sistemas mantienen separado el modelo del código que ejecuta comandos. Sus controles difieren, pero ninguno permite que el ejecutor de comandos dependa de la opinión del modelo sobre la seguridad.
Existe una disciplina complementaria que Alessandro Pignati formuló con especial claridad en enero de 2026: el Principio de la Mínima Agencia. Least Privilege pregunta ¿a qué puede acceder esta identidad?. Least Agency pregunta ¿qué puede decidir este agent?. El privilegio limita las credenciales; la agencia limita el alcance de un plan incluso cuando las credenciales son válidas. Excessive Agency aparece como entrada propia en el Top 10 de LLM Applications publicado por OWASP, el Open Worldwide Application Security Project. La lista específica de agents que se trata más adelante divide el mismo fallo entre el uso indebido de herramientas y el abuso de privilegios. Least Agency es la disciplina de diseño que evita ambos. Un agent que puede resumir tu bandeja de entrada probablemente no necesita permisos de commit en tu monorepo. Seguimos encontrando configuraciones en las que sí los tiene.
Qué cubren los guardrails de los LLM
Los guardrails de los LLM realizan un trabajo importante alrededor de la llamada al modelo. Inspeccionan la entrada, el texto recuperado y la salida, y bloquean, redactan, reparan o marcan el contenido que incumple una regla configurada. Los productos siguientes difieren en despliegue y cobertura. Una comprobación de contenido es independiente de una comprobación de autorización en el límite de la herramienta o del servidor MCP; algunos productos también ofrecen funciones de políticas en runtime que requieren su propia configuración y evaluación.
NVIDIA NeMo Guardrails
El más prescriptivo: un framework de orchestration alrededor de cinco tipos de rails (entrada, diálogo, recuperación, ejecución y salida), con su propio DSL: Colang, un lenguaje similar a Python para flujos de diálogo, intenciones de usuario y mensajes del bot. Puedes controlar lo básico desde Python + YAML, pero la lógica de diálogo más rica se escribe en Colang; de ahí que sea «prescriptivo». Documentación en docs.nvidia.com/nemo/guardrails.
Esta es una forma ilustrativa de la API; requiere el paquete y un directorio ./config configurado.
from nemoguardrails import LLMRails, RailsConfig
config = RailsConfig.from_path("./config")
rails = LLMRails(config)
response = rails.generate(
messages=[{"role": "user", "content": "Hello"}]
)
El repo de NeMo explicita su threat model: «vulnerabilidades habituales de los LLM, como jailbreaks y prompt injections». También explicita su alcance: «Los guardrails integrados pueden ser adecuados o no para un caso de uso de producción concreto… los desarrolladores deben trabajar con su equipo interno de aplicaciones para garantizar que los guardrails cumplen los requisitos». La ruta de filtrado de contenido mostrada aquí observa lo que dice el modelo. La documentación actual de NeMo también describe execution rails, custom actions e inspección de tool calls; son controles de runtime configurables, no una prueba de que la herramienta o el servidor MCP desplegados hayan autenticado y autorizado la llamada. La aplicación sigue siendo responsable de ese límite.
Meta Llama Guard 4
Un clasificador de contenido puro de 12B, podado de Llama-4-Scout y alineado con la taxonomía de riesgos de MLCommons (13 categorías de daño, además del abuso del intérprete de código, según la model card). Meta es inusualmente clara sobre sus limitaciones:
«Algunas categorías de riesgo pueden requerir conocimientos fácticos y actualizados para evaluarse por completo… Por último, como LLM, Llama Guard 4 puede ser susceptible a ataques adversariales o ataques de prompt injection que podrían eludir o alterar su uso previsto: véase Llama Prompt Guard 2 para detectar ataques contra prompts».
Meta distribuye un producto separado para defender su clasificador de contenido frente a prompt injection. Si esa frase parece una admisión estructural, es porque lo es.
Guardrails AI
Un registro de validadores. Combinas validadores del Hub (PII mediante Presidio, JailbreakDetect, CompetitorCheck y comprobaciones de provenance) con acciones on_fail exception | fix | fix_reask | filter | refrain | reask | noop, o con un callback personalizado (guardrailsai.com). Ten en cuenta exception, no raise. En el código fuente actual, una cadena on_fail no reconocida llega al manejo de callbacks personalizados y produce un error durante la configuración del validador, en lugar de mostrar una advertencia y usar un fallback. Fija la versión que despliegues y prueba esa ruta de fallo. No existe un threat model unificado; la cobertura equivale a la unión de los validadores instalados. Obtienes protección para aquello para lo que tienes un validador, y ninguna para lo demás.
Lakera Guard
La API SaaS de referencia, entrenada con decenas de millones de muestras de ataques recopiladas de Gandalf. Promete filtrar la entrada y la salida para detectar «prompt attacks… y data leakage». El producto separado AI Agent Security de Lakera también describe políticas y enforcement en runtime para lo que los agents pueden consultar, llamar y hacer. Es una superficie de producto distinta de la llamada de filtrado de contenido tratada aquí. Comprueba el producto y el contrato de precios actuales antes del despliegue.
AWS Bedrock Guardrails
La opción empresarial por defecto si ya utilizas Bedrock. ApplyGuardrail funciona con cualquier modelo, sea de Bedrock o no:
# No-run: illustrative AWS request; requires boto3, AWS credentials, and a real guardrail identifier.
import boto3
brt = boto3.client("bedrock-runtime")
resp = brt.apply_guardrail(
guardrailIdentifier="gr-xxxxxxxxxxxx",
guardrailVersion="2",
source="INPUT",
content=[{"text": {"text": "user question",
"qualifiers": ["guard_content"]}}],
)
Los precios publicados en ApplyGuardrail son de $0,15 por cada 1.000 unidades de texto para filtros de contenido o temas denegados, y de $0,10 para filtros de PII o contextual grounding. Una unidad de texto equivale a un máximo de 1.000 caracteres.
Azure AI Content Safety
Incluye Prompt Shields como endpoint unificado que «detecta y bloquea ataques de entrada adversariales… amenazas directas e indirectas». Azure también es claro: «No puedes utilizar Azure AI Content Safety para detectar imágenes ilegales de explotación infantil», y la calidad multilingüe está limitada a ocho idiomas evaluados.
OpenAI Moderation y OpenAI Guardrails
omni-moderation-latest es la baseline multimodal gratuita. Por separado, openai-guardrails-python (documentación en guardrails.openai.com) es la respuesta de framework de OpenAI: un pipeline de tres etapas (pre-flight, entrada y salida) con Jailbreak Detection, Hallucination Detection mediante FileSearch, NSFW, PII mediante Presidio y LLM-as-judge. GuardrailAgent se integra con el Agents SDK.
# No-run: illustrative OpenAI Guardrails API shape; requires the package and guardrail_config.json.
from guardrails import GuardrailsOpenAI, GuardrailTripwireTriggered
client = GuardrailsOpenAI(config="guardrail_config.json")
try:
resp = client.responses.create(model="<your-model-id>", input="...")
except GuardrailTripwireTriggered as e:
print(f"blocked: {e}")
Qué no deciden los filtros de contenido
Dos observaciones aplicables a los siete casos.
Primero, escasean las cifras publicadas de latencia y throughput. Bedrock, Azure y Lakera publican precios, pero no garantías para la latencia en el peor caso. Meta tampoco publica una garantía para un hosted endpoint de Llama Guard. NVIDIA distribuye NeMo Guardrails como software que alojas tú, por lo que la latencia depende de tu modelo y tu infraestructura. Mide cada comprobación síncrona en la ruta crítica en lugar de inferir su coste a partir del precio del producto.
Segundo, esta sección cubre las configuraciones centradas en contenido enumeradas arriba.
Un filtro de contenido puede inspeccionar la entrada y la salida del modelo. No demuestra que un tool call concreto esté autorizado, que un servidor MCP haya autenticado a quien llama ni que el sistema pueda detener una exfiltración de datos o una ejecución de código de varios pasos antes de una llamada al modelo. La autorización es una decisión independiente sobre si esta identidad puede realizar esta llamada a este servidor.
NeMo también documenta execution rails e inspección de tool calls, y Lakera describe enforcement en runtime en su producto separado AI Agent Security. Son controles adicionales que hay que configurar y probar. El resto de este artículo cubre las comprobaciones alrededor de la ejecución de herramientas.
Amenazas para la seguridad de los AI agents: siete incidentes y el OWASP ASI Top 10
La brecha entre filtrar texto y proteger la ejecución dejó de ser académica a mediados de 2025. Los siete incidentes siguientes alcanzaron capas de recuperación, configuración, credenciales, instalación de paquetes o ejecución en CI. Un clasificador de contenido todavía puede detectar una cadena sospechosa, pero los controles que bloquean directamente estas rutas viven en los límites de herramientas, identidad, sandbox y supply chain.
EchoLeak — CVE-2025-32711
Revelado en junio de 2025 por Aim Labs, el brazo de investigación de Aim Security, contra Microsoft 365 Copilot. El análisis técnico se encuentra ahora en Cato Networks, que adquirió ese equipo, bajo la firma de Itay Ravia, antiguo responsable de Aim Labs (análisis). Un email manipulado, redactado como instrucciones para el destinatario humano, eludió XPIA (el filtro integrado de Microsoft que busca ataques de prompt injection en las entradas de Copilot). A partir de ahí pasó a la capa de recuperación de Copilot, la parte del sistema que busca en tus documentos para encontrar contexto para las respuestas. Los investigadores llaman a este truco RAG-spraying: el atacante utiliza varios emails o un email largo dividido en chunks para ampliar la exposición a la recuperación. Eso aumenta la probabilidad de recuperación, pero no la garantiza. Una vez dentro, Copilot incorporó obedientemente los datos más sensibles de la sesión a un enlace Markdown que apuntaba a una imagen en un dominio controlado por el atacante. La API de vista previa de Teams, ejecutándose en un dominio en el que las políticas del navegador de Microsoft ya confiaban, obtuvo automáticamente esa URL de imagen y, al hacerlo, entregó los datos al atacante. Cero clics. Aim Labs denominó a esta clase de ataque «LLM Scope Violation»: el modelo cruza un límite que nunca debía cruzar utilizando únicamente operaciones que cada sistema individual consideraba legítimas.
Cada paso parecía legítimo de forma aislada. El email estaba dirigido a una persona. La recuperación obtuvo un documento que debía obtener. El enlace Markdown se renderizó como se renderizan los enlaces Markdown. La petición de la imagen llegó a un dominio incluido en la allowlist. Los investigadores eludieron el filtrado de XPIA, y el modelo siguió instrucciones indirectas procedentes del contenido recuperado. El caso combina un fallo de prompt injection con el comportamiento de recuperación, renderizado y egress; no demuestra que los detectores no tuvieran nada que marcar.
Amazon Q Developer VS Code v1.84.0 — julio de 2025
AWS distribuyó una build comprometida después de que un atacante hiciera commit de un archivo de system prompt malicioso mediante un token de GitHub de CodeBuild con demasiado scope (advisory). El payload intentaba modificar las instrucciones del agent para dirigirlo hacia acciones destructivas. El código malicioso se distribuyó con v1.84.0, pero no se ejecutó debido a un error de sintaxis. AWS revocó las credenciales, eliminó el código y publicó v1.85.0. El payload falló por ese error de sintaxis, no porque un control de seguridad lo bloqueara.
Servicio MCP de Azure Web Apps — CVE-2026-32211
El registro CVE del proveedor de Microsoft se refiere a la falta de autenticación en el servicio MCP alojado de Azure Web Apps. No es un advisory contra todos los servidores o SDKs MCP locales de Azure. Un caller que alcance un servicio de herramientas sin autenticación puede evitar por completo al modelo; el servicio desplegado debe autenticar y autorizar la petición.
Vulnerabilidades de confianza de proyectos en Claude Code
Fueron vulnerabilidades separadas, no pasos necesarios de un único ataque:
- Un bypass de la advertencia de confianza se corrigió en 1.0.87.
- La ejecución previa a la confianza, CVE-2025-59536, se corrigió en 1.0.111. La configuración del repositorio podía desencadenar una ejecución antes de que el proyecto fuese de confianza.
- La exposición del endpoint/API key, CVE-2026-21852, se corrigió en 2.0.65. Una configuración no fiable podía redirigir el tráfico de la API y exponer credenciales.
La confianza del proyecto, la ejecución de hooks y la configuración del endpoint son controles del host. Un clasificador de contenido no puede impedir que se ejecute código antes de la llamada al modelo.
Axios 1.14.1 y 0.30.4 — 31 de marzo de 2026
El postmortem del maintainer identifica dos releases maliciosas, 1.14.1 y 0.30.4, que contenían la dependencia plain-crypto-js@4.2.1. Esa dependencia instalaba un remote access trojan: malware que proporciona a un atacante acceso remoto a la máquina. La exposición requería resolver las versiones afectadas y ejecutar el comportamiento de instalación correspondiente; un npm install no relacionado no las descargaba automáticamente. Es un fallo de ejecución en la supply chain, independiente del comportamiento del modelo.
Secuestro de tags de Trivy Actions — 19 de marzo de 2026
El advisory de Aqua describe cómo 76 de 77 tags de versión de trivy-action y siete tags de setup-trivy redirigieron a contenido malicioso. El entrypoint de la action maliciosa recopiló memoria del proceso del runner y archivos de credenciales; no atribuyas toda esa ruta de recopilación al binario del scanner. El incidente posterior de Docker Hub tuvo una ventana de exposición independiente.
Un workflow que resolviera un tag afectado durante el compromiso podía ejecutar el payload. Los tags son referencias móviles, así que fija las Actions revisadas a SHAs de commit inmutables y verifica los cambios de dependencias. Un coding agent puede propagar la misma referencia insegura a más archivos de workflow.
OpenAI / Hugging Face — evaluaciones de julio de 2026
El informe del incidente del 26 de agosto de OpenAI describe cómo agents internos de evaluación de ciberseguridad accedieron a Internet mediante infraestructura compartida, colaboraron a través de un tablón de mensajes no autorizado y comprometieron sistemas de Hugging Face. El modelo principal era solo para uso interno y las evaluaciones se ejecutaron con salvaguardas reducidas. Esto aporta evidencia sobre ese entorno de evaluación, no una tasa de fallos medida para agents desplegados públicamente.
La lección de ingeniería es que un servicio interno permitido puede convertirse en una ruta de salida o en un canal de comunicación entre sesiones. Prueba qué pueden hacer en nombre del agent un package mirror, un proxy y un almacén compartido, no solo si el sandbox puede abrir una conexión directa a Internet. La investigación separada de METR examinó el comportamiento y la colaboración de los agents; evaluar la eficacia de las salvaguardas y la remediación quedaba fuera de su alcance.
Intervención asíncrona del proveedor
El sistema de monitorización de misalignment de OpenAI puede intervenir después de la salida o de las acciones. Para los modelos cubiertos, las peticiones de Responses con reasoning persistido, WebSockets u OpenAI compaction pueden detenerse automáticamente. Otras peticiones de Responses pueden generar alertas sin detención automática; Chat Completions queda fuera de este sistema. Un webhook no permite bloquear.
Una petición bloqueada devuelve misalignment_policy_violation, con HTTP 403 antes del streaming; los errores también pueden llegar a mitad del stream. Detén las acciones posteriores, conserva los registros de la petición y de las herramientas, y solicita revisión del operador. No hagas retry automático. Los efectos anteriores permanecen, y las alertas pueden equivocarse o no detectar algunos casos. Esto añade detección; no sustituye a la autorización local.
OWASP ASI Top 10, edición de 2026
La Agentic Security Initiative (ASI) de OWASP es un grupo de trabajo centrado específicamente en agents dirigidos por LLM y, el 9 de diciembre de 2025, publicó el Agentic Security Initiative Top 10 for 2026: un catálogo de diez categorías de riesgo para la seguridad de los agents.
Úsalo como checklist de cobertura del threat model, no como ranking medido de frecuencia de incidentes:
Los filtros de contenido pueden contribuir a detectar instrucciones maliciosas, incluidos el secuestro de objetivos y el envenenamiento de memoria. Las categorías se solapan: ninguna pertenece exclusivamente a un filtro de texto. Asigna cada ruta de ataque a los controles de identidad, política de herramientas, memoria, orchestration, monitorización y supply chain que correspondan. EchoLeak corresponde a ASI01. Amazon Q corresponde a ASI04 (Supply Chain) y ASI02 (Tool Misuse). Azure MCP es ASI03 (Identity). CVE-2025-59536 de Claude Code abarca ASI05 (Code Execution), ASI04 y ASI03. Axios y Trivy son ASI04. El mapeo muestra por qué el threat model debe extenderse más allá de la entrada y la salida del modelo.
El permiso es infraestructura, no un prompt
Esta es la parte en la que los guardrails dejan de ser el producto y pasan a ser un subsistema de un harness. Tres sistemas actuales (OpenAI Agents SDK, Codex CLI y Claude Code) muestran cómo es realmente una superficie de políticas de producción. Los tres aplican los permisos en código. Ninguno depende de que el modelo tenga cuidado.
OpenAI Agents SDK
El SDK separa harness de compute. Las herramientas MCP alojadas aceptan require_approval: la cadena simple "always" / "never", o un objeto de filtros indexado por esas dos políticas con los nombres de las herramientas que cubre cada una; además, un callback on_approval_request que se ejecuta para cada herramienta que permanezca bajo "always" y devuelve {"approve": bool} con un motivo opcional. El filtrado de herramientas detallado (tool_filter) está disponible en las variantes de servidores locales (MCPServerStdio, MCPServerStreamableHttp, MCPServerSse) si lo necesitas:
# No-run: illustrative Agents SDK shape; requires openai-agents, a configured hosted MCP server, and credentials.
from agents import Agent, HostedMCPTool
def approve(request):
# Only tools under the "always" policy reach this callback.
if request.data.name == "delete_repo":
return {"approve": False, "reason": "escalate to a human reviewer"}
return {"approve": True}
agent = Agent(
name="Ops",
tools=[HostedMCPTool(
tool_config={
"type": "mcp",
"server_label": "github",
"server_url": "https://mcp.example.com",
"require_approval": {
"always": {"tool_names": ["delete_repo"]},
"never": {"tool_names": ["list_issues"]},
},
},
on_approval_request=approve,
)],
)
El callback de aprobación es código. La política de aprobación por herramienta es código. Puedes leer este archivo. Puedes probarlo. Puedes compararlo. Nada de eso es cierto para un system prompt que diga «ten cuidado con producción».
Codex CLI y la capa de políticas gestionadas
El harness de coding de OpenAI admite un archivo requirements.toml gestionado que los departamentos de IT pueden distribuir mediante la gestión de dispositivos. En sistemas Unix, el archivo del sistema se encuentra en /etc/codex/requirements.toml. Actúa como una capa de restricciones duras, por lo que la configuración del proyecto no puede sobrescribir sus reglas:
# /etc/codex/requirements.toml
[rules]
prefix_rules = [
{ pattern = [{ token = "rm" }, { any_of = ["-rf", "-fr"] }], decision = "forbidden", justification = "Recursive force-delete prohibited by IT policy" },
]
prefix_rules.decision solo acepta "prompt" o "forbidden", nunca "allow". Un proyecto no puede concederse un permiso que la capa gestionada prohíbe. Las allowlists de MCP se indexan tanto por nombre como por identidad, por ejemplo, una cadena de comando o una URL. Por tanto, un proyecto no puede afirmar que es github-mcp y apuntar al servidor de un atacante. Los requisitos admitidos varían según el cliente y la versión. La documentación actual exige específicamente Codex 0.138.0 o posterior para las claves de perfiles de permisos gestionados; por tanto, prueba cualquier política de requisitos con cada versión de cliente de la flota antes de desplegarla.
La escalera de permisos de Claude Code
Claude Code no publica una secuencia fija de seis comprobaciones para cada tool call. Sus reglas de permisos se evalúan deny → ask → allow; la primera regla coincidente determina el resultado. Un hook PreToolUse se ejecuta antes del prompt de permisos. Un hook puede bloquear una llamada, pero su resultado no evita una regla coincidente de denegación o consulta. El modo de permisos activo gestiona las llamadas que las reglas no resuelven. El Claude Agent SDK tiene un callback canUseTool separado para las peticiones no resueltas. Ese callback es un control del SDK, no una comprobación de permisos de Claude Code CLI.
Los modos cambian entre default → acceptEdits → plan con Shift+Tab. auto, bypassPermissions y dontAsk se activan bajo condiciones de entrada específicas que la capa de políticas gestionada por la empresa puede bloquear. Esto es más que comprobar la corrección de un archivo de configuración. Es una máquina de estados con reglas de precedencia, publicada para que un equipo de seguridad pueda razonar sobre ella.
Tres radios de impacto en un único archivo
Esta es la forma de una configuración de permisos al estilo Codex con un valor por defecto y dos perfiles con nombre:
# ~/.codex/config.toml
approval_policy = "on-request"
sandbox_mode = "workspace-write"
[profiles.ci]
approval_policy = "never"
sandbox_mode = "read-only"
[profiles.release]
approval_policy = "untrusted"
sandbox_mode = "danger-full-access"
[mcp_servers.github]
command = "gh-mcp"
args = ["--readonly"]
Dos claves hacen el trabajo y son independientes. approval_policy decide cuándo se pregunta a una persona. on-request permite que el agent escale cuando llega a un bloqueo. never no pregunta nada. untrusted detiene cada comando que no esté en la lista de confianza. sandbox_mode decide qué puede tocar el comando si llega a ejecutarse.
CI nunca interrumpe a nadie y no puede escribir. Release puede alcanzar toda la máquina, pero antes debe superar casi todas las comprobaciones con una persona. El perfil release paga por ese alcance: danger-full-access desactiva el sandbox, así que la aprobación de untrusted es el único control que queda. Todo lo que esté fuera de la lista de confianza obtiene la aprobación de una persona o no se ejecuta. Esa lista de confianza es ahora todo el límite de seguridad.
Los perfiles default y CI mantienen el kernel por debajo: Seatbelt en macOS, bubblewrap más seccomp en Linux y tokens restringidos en Windows. En cualquier caso, la opinión del modelo no interviene.
La aplicación del sandbox es una cuestión del sistema operativo
Aquí el trabajo real lo hace el kernel. Cada sistema operativo proporciona un conjunto de herramientas diferente, y las dos CLIs no siempre eligen la misma:
| Plataforma | Claude Code | Codex CLI |
|---|---|---|
| macOS | Seatbelt mediante sandbox-exec con un perfil SBPL (Seatbelt Profile Language) | Seatbelt mediante sandbox-exec -p |
| Linux | bubblewrap + socat como proxy de red | bubblewrap + seccomp (Landlock heredado mediante use_legacy_landlock) |
| Windows | Requiere WSL2 | Tokens restringidos nativos + ACLs del workspace + SIDs de capacidad |
Coinciden donde el sistema operativo ofrece una única opción (Seatbelt, bubblewrap) y difieren donde no la ofrece. El sandbox de Claude Code requiere WSL2 en Windows; esto es una limitación del sandbox, no una afirmación de que la CLI no pueda ejecutarse de forma nativa. Codex incluye un sandbox nativo para Windows. En cualquier caso, el enforcement ocurre en el kernel, no en el modelo.
La ruta de Linux de Codex apila tres bloqueos a nivel de kernel alrededor del comando. PR_SET_NO_NEW_PRIVS impide que el proceso obtenga privilegios adicionales aunque lo intente. Un filtro seccomp hace que el kernel rechace directamente clases completas de llamadas al sistema. Las restricciones de red dependen de si la red está desactivada o de si se ha configurado un modo proxy; no se limitan universalmente a sockets Unix. Véase la implementación del sandbox de Linux. Un /proc nuevo y aislado oculta el resto de la máquina.
Codex también refuerza su propio binario al arrancar en todas las plataformas Unix. Establece RLIMIT_CORE=0 para suprimir los crash dumps y rechaza el attach del debugger. Es un límite distinto del sandbox.
El sandbox de Windows tiene dos modos. unelevated utiliza un token restringido como usuario y no proporciona el enforcement de red del modo elevado. elevated utiliza usuarios de sandbox dedicados, reglas de firewall y ACLs del sistema de archivos.
Cuando el acceso de red está desactivado, Codex coloca archivos stub .bat y .cmd para ssh y scp en un directorio situado al principio de PATH. Esos comandos terminan con un código distinto de cero en lugar de llegar a los binarios reales. Codex también apunta HTTP_PROXY, HTTPS_PROXY, ALL_PROXY y las variables del proxy de Git a un puerto local inactivo. Estas medidas afectan a las herramientas cooperativas; las variables de proxy y los stubs de comandos por sí solos no pueden confinar redes arbitrarias.
Un sandbox también necesita un modo de fallo definido. En la configuración actual de Claude Code, sandbox.failIfUnavailable: true detiene la ejecución cuando no se puede iniciar el sandbox. allowUnsandboxedCommands: false desactiva el escape de retry sin sandbox del agent, pero excludedCommands sigue evitándolo. Prueba la configuración efectiva con dependencias ausentes y un destino prohibido.
El credential brokering también está disponible localmente. sandbox.credentials de Claude Code puede denegar archivos y variables de entorno con nombre. El enmascaramiento de variables de entorno, disponible desde v2.1.199, proporciona un placeholder a los comandos en sandbox y permite que el proxy inyecte el valor real en las peticiones a hosts configurados. Define injectHosts restrictivos y configura la terminación TLS; el masking no autoriza la operación solicitada. Estos controles cubren los comandos Bash en sandbox, por lo que los hooks, los procesos MCP y otras rutas de ejecución siguen necesitando su propia política de credenciales.
Opciones de aislamiento más allá de Claude Code y Codex
Si estás construyendo tu propio agent, «sandbox» resulta ser un término paraguas. Las opciones open source se sitúan en un espectro: desde wrappers ligeros de namespaces hasta microVMs completas; la elección depende de cuánto confíes en el código que se ejecuta dentro.
Aislamiento ligero — mismo kernel, menos privilegios:
- bubblewrap: constructor de sandboxes de namespaces de bajo nivel utilizado por Flatpak y Claude Code en Linux. El caller debe elegir el sistema de archivos, la red y la política seccomp opcional; bubblewrap no es por sí mismo una política de seguridad lista para usar.
- Contenedores Docker / OCI estándar: aislamiento mediante namespaces sobre un kernel del host compartido. No son un sandbox para código no fiable; la propia documentación de gVisor lo deja claro («containers are not a sandbox»). Son un punto de partida razonable si se combinan con seccomp y AppArmor, pero nada más.
Aislamiento a nivel de kernel de aplicación — el agent habla con un kernel falso:
- gVisor: el kernel en espacio de usuario de Google. Tu contenedor cree estar en Linux mientras una implementación del kernel en Go intercepta las llamadas al sistema. Reduce la exposición directa al kernel del host sin una VM guest, con compromisos de compatibilidad y rendimiento.
Aislamiento mediante VM completa — un kernel dedicado por sandbox:
- Firecracker: tecnología de microVMs de AWS. Cada VM tiene su propio kernel Linux bajo KVM; los contenedores comparten el kernel del host. Un compromiso en una VM todavía debe atravesar los controles del VMM o del host para afectar al host o a otra VM, por lo que Jailer de Firecracker y un host parcheado siguen formando parte de la protección.
- Kata Containers: UX de contenedores con aislamiento de nivel de VM. Es la opción habitual en clusters de Kubernetes que necesitan ejecutar código no fiable.
Plataformas — lo que alquilarías en vez de construir:
- E2B convierte Firecracker en una API de sandbox alojada.
- OpenSandbox separa el SDK del aislamiento del runtime configurado por el administrador. Docker runc por defecto no es una microVM; su ruta de Firecracker utiliza Kata con Firecracker mediante Kubernetes.
- El Agent Governance Toolkit de Microsoft (con licencia MIT, abril de 2026) añade un motor de políticas de runtime. Mapea los controles de políticas al OWASP ASI Top 10. Su afirmación sobre la latencia de arranque se refiere a un motor de políticas, no al coste total de todas las comprobaciones de un agent desplegado.
Elige el nivel de aislamiento según el nivel de confianza del código, el límite entre tenants, el acceso de red, los datos del host y el coste de recuperación. Los controles de namespaces y seccomp pueden encajar con herramientas internas de confianza. El código generado por LLM y los paquetes no fiables necesitan un límite más fuerte, como gVisor, Kata o una microVM, seguido de pruebas contra las rutas de escape y exfiltración de tu propio threat model.
Claude Code y Codex eligieron del mismo menú que los demás. Simplemente lo envolvieron de forma distinta.
Hooks PreToolUse como políticas programables
Los modos y las allowlists resuelven los casos sencillos: «permite que el agent edite archivos, pero no ejecute bash», «deniega cualquier cosa que parezca rm -rf». Fallan cuando la política necesita lógica real. Quieres bloquear git push solo cuando la branch sea main. Quieres denegar cualquier Edit que toque un archivo cuyo nombre coincida con una regex de secretos. Quieres limitar la frecuencia de llamadas de shell por sesión o enviar cada invocación de una herramienta a tu registro de auditoría central (el SIEM, el sistema de gestión de información y eventos de seguridad que tu equipo de seguridad ya supervisa).
Nada de eso cabe en una allowlist estática. Para eso sirven los hooks: comandos de shell que Claude Code ejecuta en puntos concretos del ciclo de vida del tool call, con capacidad para inspeccionar la llamada pendiente y devolver un allow/deny estructurado. Claude Code expone unos treinta eventos del ciclo de vida (la lista completa está en la documentación), y uno de ellos reordena todo lo demás: un hook PreToolUse que devuelva permissionDecision: "deny" bloquea una herramienta independientemente del modo.
Esta es la estructura de settings:
{
"permissions": {
"defaultMode": "acceptEdits",
"deny": ["Bash(rm -rf:*)", "Bash(sudo:*)", "Read(.env*)"]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/pre-bash-firewall.sh"
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/protect-paths.sh"
}
]
}
]
}
}
Las reglas estáticas deny y los hooks dinámicos tienen modos de fallo distintos. Un resultado PreToolUse de deny bloquea antes del flujo normal de permisos, pero un command agotado, un hook HTTP o un hook de herramienta MCP son non-blocking: Claude Code continúa con ese flujo. En este ejemplo, acceptEdits puede aprobar por tanto un Edit o Write cuando protect-paths.sh agota su tiempo. No hagas depender una restricción de rutas no negociable únicamente de un command hook. Coloca las restricciones estáticas en reglas de denegación o en el sandbox; para políticas dinámicas, elige un control cuyo fallo del evaluador siga siendo restrictivo y prueba tanto su comportamiento al agotarse el tiempo como una denegación explícita.
Un hook puede ser un script de shell de cinco líneas o un motor de políticas completo. Lo importante es la forma de retorno:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "writes outside workspace prohibited"
}
}
El modelo recibe una denegación estructurada. El reasoning loop de la Parte 1 la gestiona como cualquier otra observación de una herramienta: la denegación se convierte en contexto, el agent replantea el plan y el loop continúa. Esto es lo que aporta que «el permiso sea infraestructura». La denegación está conectada al mismo mecanismo que gestiona un 500 de una herramienta HTTP. No es un workflow de seguridad separado que haya que añadir después.
Un antipatrón habitual consiste en escribir un system prompt que diga «no borres ningún archivo sin confirmación explícita del usuario», distribuir el agent y depender de esa instrucción como control. Un prompt inyectado, o un tool result controlado por un atacante, puede rodear esa instrucción. El modelo no es un motor de políticas. Puede seguir el patrón que has escrito o uno proporcionado por un atacante.
La aprobación humana solo funciona como escalado
Los filtros de contenido inspeccionan lo que dice el modelo. Las reglas de permisos inspeccionan el tool call antes de que se ejecute. La revisión human-in-the-loop gestiona las acciones que todavía necesitan una persona. Si las personas aprueban el 93 % de los prompts, revisa las reglas de escalado y la calidad de los prompts.
LangGraph proporciona la primitiva de pausa/reanudación. HumanLayer empaqueta el canal de aprobación, y los datos de uso de Anthropic muestran por qué hay que medir el número y la calidad de los escalamientos.
La primitiva de LangGraph
La combinación interrupt() + Command(resume=value) de LangGraph pausa un grafo, persiste su estado mediante el checkpointer configurado y reanuda la ejecución con un valor proporcionado por una persona. Que esa reanudación sea segura depende de un detalle de la documentación:
«Cuando la ejecución se reanuda (después de proporcionar la entrada solicitada), el runtime reinicia el nodo completo desde el principio; no reanuda desde la línea exacta en la que se llamó a
interrupt».
De este comportamiento de reinicio se derivan tres restricciones:
1. Los efectos secundarios anteriores a interrupt() deben ser idempotentes. Cuando la persona responde, el nodo completo vuelve a ejecutarse desde el principio, no desde la línea interrupt(). Por tanto, si el nodo envía un email, se pausa para solicitar aprobación y después devuelve «sent», al reanudarse el email se enviará una segunda vez. Solución: coloca los efectos secundarios después de la interrupción o haz que sea seguro repetirlos (claves de deduplicación, upsert en lugar de insert, caché por message ID).
2. Las interrupciones se emparejan con las reanudaciones por índice, no por nombre. Si un único nodo contiene dos llamadas a interrupt(), LangGraph las empareja con valores Command(resume=...) en el orden en que se producen. Cualquier branching que cambie el número de interrupciones (un if que omita una al reanudar, un loop que itere un número distinto de veces) desalineará los índices, de modo que un valor de reanudación puede acabar en la interrupción equivocada.
3. Mantén los payloads seguros para JSON. La documentación de LangGraph exige valores serializables a JSON para interrupt() y los payloads de reanudación. Utiliza strings, números, booleanos, arrays y diccionarios que contengan esos valores. Evita funciones, instancias de clases y otros objetos complejos, porque la serialización depende del checkpointer configurado. Convierte los datos de aprobación a diccionarios y primitivas antes de pasarlos a interrupt() o exponerlos mediante una API HTTP.
Los tres patrones canónicos:
# No-run: illustrative LangGraph sketches; requires LangGraph, a tool decorator, interrupt, and smtp_send.
# (a) Approval check
@tool
def send_email(to, subject, body):
resp = interrupt({"action": "send_email", "to": to,
"subject": subject, "body": body})
if resp.get("action") == "approve":
return smtp_send(to, subject, body)
return "Email cancelled"
# (b) Edit-and-continue
def review_node(state):
edited = interrupt({"content": state["generated_text"]})
return {"generated_text": edited}
# (c) Mid-run state correction — conditional edge
class AgeState(TypedDict):
age: int | None
pending_question: str | None
def get_age_node(state: AgeState):
question = state.get("pending_question") or "What is your age?"
answer = interrupt(question) # once per node invocation
if isinstance(answer, int) and answer > 0:
return {"age": answer, "pending_question": None}
return {"pending_question": f"'{answer}' is not valid. Please enter a positive number."}
def route_age(state: AgeState):
return END if state.get("age") is not None else "get_age"
builder = StateGraph(AgeState)
builder.add_node("get_age", get_age_node)
builder.add_edge(START, "get_age")
builder.add_conditional_edges("get_age", route_age)
Resume es graph.invoke(Command(resume={"action": "approve"}), config=cfg). LangGraph 0.4+ admite la reanudación de múltiples interrupciones basada en diccionarios para ramas paralelas, algo importante en cuanto tu agent se divide en varias ramas.
HumanLayer: la aprobación como producto
HumanLayer es la versión gestionada de la misma idea. Decora una función y las peticiones de aprobación se envían a Slack, email o Discord, con reglas sobre quién recibe el aviso. Cuando el agent intenta llamar a multiply(2, 5), los logs tienen este aspecto:
last message led to 1 tool calls: [('multiply', '{"x":2,"y":5}')]
HumanLayer: waiting for approval for multiply
La persona aprobadora pulsa approve o deny en Slack. En caso de denegación, la documentación de HumanLayer lo expresa así: «HumanLayer devolverá tus comentarios al agent, que podrá ajustar su enfoque». Estos comentarios permiten que el agent revise su plan en lugar de tratar el rechazo como un callejón sin salida.
Fatiga de aprobación en los datos
Anthropic publicó los datos reales en febrero de 2026. Hay tres hallazgos más importantes que el resto.
«Descubrimos que el 80 % de los tool calls proceden de agents que parecen tener al menos un tipo de salvaguarda (como permisos restringidos o requisitos de aprobación humana), el 73 % parece tener una persona en el loop de alguna forma y solo el 0,8 % de las acciones parece ser irreversible».
Esas son las buenas noticias. Trata el 80 % como un límite superior, porque la nota al pie 14 de Anthropic añade que «Claude sobreestimaba a menudo la implicación humana, por lo que esperamos que el 80 % sea un límite superior».
«Los usuarios nuevos (<50 sesiones) utilizan el auto-approve completo aproximadamente el 20 % del tiempo; al llegar a 750 sesiones, esto aumenta a más del 40 % de las sesiones».
Esta es la deriva. Los usuarios empiezan con cautela y se vuelven menos cautelosos a medida que desarrollan confianza en la herramienta. Es lo que hacen las personas y no es un defecto de carácter. Es una señal de telemetría que tu sistema debería seguir. (Una pequeña nota de verificación: la cobertura secundaria suele citarlo como «20 % → más del 50 %». Según los datos primarios de Anthropic, la cifra verificada es 20 % → más del 40 %. Si has visto la cifra del 50 %, ese es su origen).
El post de ingeniería de Anthropic de marzo de 2026 sobre el auto mode de Claude Code aporta la cifra clave:
«Los usuarios de Claude Code aprueban el 93 % de los prompts de permisos. Hemos creado clasificadores para automatizar algunas decisiones, aumentando la seguridad y reduciendo la fatiga de aprobación… Si una sesión acumula 3 denegaciones consecutivas o 20 en total, detenemos el modelo y escalamos a una persona».
Una tasa de aprobación alta justifica revisar si los prompts ayudan a las personas a decidir; no demuestra un umbral de fallo universal. El clasificador de dos etapas de Anthropic reduce los prompts y escala las denegaciones agrupadas. Su tasa de falsos positivos del 0,4 % se calculó con 10.000 ejemplos de tráfico. Las tasas de falsos negativos fueron del 17 % sobre 52 acciones reales demasiado entusiastas y del 5,7 % sobre 1.000 casos sintéticos de exfiltración. Son poblaciones distintas, no porcentajes complementarios ni una prueba de que la clasificación sustituya al sandboxing.
Mide la calidad del escalado
Incluye en una allowlist las acciones rutinarias y reversibles, y regístralas. Escala las acciones cuyos efectos secundarios crucen un límite que el runtime no pueda deshacer, como un mensaje externo, una escritura en producción, un force push o un pago. Anthropic plantea el objetivo como mantener la capacidad de una persona para intervenir cuando la decisión tiene consecuencias reales.
Sigue todo el funnel en lugar de perseguir una tasa de aprobación prestada: acciones propuestas, permisos automáticos, escalamientos, aprobaciones, denegaciones, ediciones e incidentes posteriores a la aprobación. Una tasa de aprobación alta puede significar que los prompts son ruido rutinario. Una tasa alta de denegaciones o ediciones puede indicar que el planner propone la acción incorrecta u oculta la información que necesita la persona aprobadora. El umbral útil depende de la clase de acción y del coste de un permiso incorrecto, así que establécelo a partir de tus propios datos de incidentes y revisiones.
MCP scoping y la supply chain
MCP conecta agents con herramientas externas como Slack, GitHub y bases de datos, por lo que su modelo de autorización forma parte del límite de seguridad. Las revisiones de la especificación de 2025 separaron los roles de emisor de tokens y servidor de recursos, y añadieron resource indicators. Ese historial explica qué comprobaciones de audiencia y forwarding debe aplicar hoy un servidor.
Autorización de MCP en tres revisiones
La autorización era opcional para las implementaciones de MCP en la especificación 2025-03-26. Para un despliegue HTTP de producción que proteja datos o herramientas de usuario, recomiendo OAuth 2.1 con PKCE (Proof Key for Code Exchange), que la especificación exige cuando una implementación admite autorización OAuth. El diseño inicial permitía que un servidor MCP desempeñara dos roles. El servidor de autorización emite tokens; el servidor de recursos los acepta. Son roles separados, aunque un mismo servicio desempeñe ambos. Si ese servicio reenvía una petición a otro servidor, la misma credencial puede viajar a un lugar para el que nunca estaba destinada. Ese es el agujero.
La revisión de 2025-06-18 hizo explícitos los roles. Un servidor MCP protegido actúa como servidor de recursos OAuth, mientras que un servidor de autorización emite el token. El servidor de autorización puede estar alojado junto con el servidor de recursos o ejecutarse por separado. Los Resource Indicators de RFC 8707 vinculan el token a un recurso de destino, y Protected Resource Metadata de RFC 9728 proporciona al cliente una ruta explícita de discovery. La especificación también prohíbe que un servidor MCP reenvíe el token del cliente upstream.
La revisión de 2025-11-25 mantuvo esa separación y trabajó en los aspectos que el cliente debe implementar correctamente. El discovery del servidor de autorización incorporó OpenID Connect Discovery, de modo que el cliente puede encontrar el issuer correcto en lugar de adivinarlo. El consentimiento incremental de scopes pasó a la cabecera WWW-Authenticate, lo que permite que un servidor solicite un scope adicional justo cuando lo necesita, en lugar de exigirlo todo desde el principio. El registro de clientes incorporó OAuth Client ID Metadata Documents como mecanismo recomendado, sustituyendo el registro dinámico en la mayoría de despliegues. El discovery de Protected Resource Metadata también se alineó con RFC 9728, haciendo que WWW-Authenticate sea opcional con un fallback .well-known.
Consulta la página de versionado antes de implementar. A 6 de septiembre de 2026, la revisión actual es 2026-07-28. Exige que cada petición declare la versión del protocolo y permite que el servidor acepte o rechace cada petición de forma independiente. Un cliente puede llamar a server/discover para seleccionar una versión de antemano, pero el discovery es opcional. La declaración y negociación por petición siguen siendo obligatorias, incluso cuando el cliente gestiona un error de versión no compatible y reintenta con una versión compatible para ambos.
El binding de audiencia limita el replay contra el servidor MCP equivocado. No neutraliza las vulnerabilidades separadas de configuración de Claude Code descritas arriba: un hook del host puede ejecutarse antes de que arranque el modelo, y un proyecto no fiable puede intentar cambiar la configuración local. El scope del token, la confianza del proyecto, la política de hooks y el sandboxing siguen siendo controles independientes.
Checklist de MCP para 2026
Si distribuyes o consumes MCP en producción:
- Trata la autenticación como un requisito de producción, no como un valor por defecto del protocolo. MCP deja la autorización como opcional, pero recomiendo OAuth 2.1 con PKCE para un despliegue HTTP protegido. El advisory sobre el servicio MCP de Azure Web Apps se refería a la falta de autenticación. Si tu servidor acepta tráfico sin verificar las credenciales del caller, has construido una herramienta que puede llamar cualquiera que tenga acceso a ella.
- Los tokens están vinculados a una audiencia. Solicita un token para el recurso MCP de destino y valida que el token presentado identifique tu servidor como audiencia. Rechaza los tokens emitidos para otro recurso.
- Aísla deliberadamente la autoridad de lectura y escritura. MCP vincula un token a un servidor de recursos, no a una herramienta individual. Si un servidor de Slack acepta una credencial con
chat:writey la dirige tanto a handlers de lectura como de escritura, una herramienta orientada a la lectura puede convertirse en una ruta de envío de mensajes mediante la política de ese servidor. Utiliza servidores de recursos o credenciales y comprobaciones de autorización separadas cuando las operaciones de lectura y escritura necesiten radios de impacto independientes. - Utiliza tokens nuevos y de corta duración en lugar de API keys permanentes. El patrón del vault de Claude Managed Agents (ingeniería de Anthropic) es la referencia: el propio agent nunca ve las credenciales reales. El proxy recupera las credenciales correspondientes del vault, llama a la herramienta en nombre del agent y devuelve el resultado. Emitir un token nuevo en cada llamada no es una garantía documentada; las credenciales de corta duración son una recomendación de despliegue.
Los controles de la supply chain siguen siendo aplicables
Los incidentes de axios y Trivy son fallos conocidos de la supply chain de paquetes y CI aplicados a sistemas que automatizan la instalación de dependencias. La automatización aumenta el número y la velocidad de las ejecuciones, por lo que los controles de versión, provenance y revisión deben ejecutarse antes de que el comando generado llegue a CI o a un sandbox.
La defensa es directa:
- Fija las versiones en el lockfile. Los agents nunca deben resolver una versión flotante: ni
@latest, ninpm update, ni--upgrade. - Escanea en CI con herramientas independientes del componente que se está comprobando.
- Utiliza SHAs de commit de GitHub para las Actions, no tags.
- Revisa los diffs de dependencias en las PRs generadas por agents antes del merge.
Son controles estándar de supply chain. La automatización de agents cambia su frecuencia, no su mecanismo.
Una pila de políticas para el Market Analyst Agent
El Market Analyst Agent de la Parte 1 es un agent pequeño de LangGraph que obtiene datos de mercado y escribe un informe de analista, pero no es tan pequeño como su descripción sugiere. Además de las herramientas de datos de mercado, ejecuta una CLI incluida en una allowlist mediante subprocess, evalúa Python escrito por el modelo en el propio proceso y crea registros de operaciones simuladas. Ejercita la invocación de herramientas, la ejecución de código y el enrutamiento de aprobaciones, pero no realiza órdenes reales. Esta es la apariencia de una pila de políticas mínima para él.
Capa 1: un hook PreToolUse que deniega antes de la ejecución
Incluso un agent que «solo lee datos bursátiles» puede intentar acceder a cosas que no debería: un curl a una URL controlada por un atacante, escrituras fuera del workspace o mutaciones git en el repo del host. Una regla de denegación es infraestructura, no un prompt. El sketch siguiente devuelve la forma de decisión propia del agent, no el envelope hookSpecificOutput que espera Claude Code.
# agent/permissions.py
from pathlib import Path
DENY_COMMANDS = frozenset({
"rm -rf", "sudo", "chmod 777",
"curl -X POST", "wget", "nc ",
})
WORKSPACE = Path("./workspace").resolve()
def _outside_workspace(path: str) -> bool:
# Resolve first: "~/.ssh/id_rsa" and "workspace/../../etc" both
# have to become real paths before the comparison means anything.
return not Path(path).expanduser().resolve().is_relative_to(WORKSPACE)
def pre_tool_use(tool_name: str, args: dict) -> dict | None:
if tool_name == "shell":
cmd = args.get("command", "")
if any(bad in cmd for bad in DENY_COMMANDS):
return {"permissionDecision": "deny",
"reason": f"command pattern disallowed: {cmd!r}"}
if tool_name == "write_file":
path = args.get("path", "")
if _outside_workspace(path):
return {"permissionDecision": "deny",
"reason": f"path outside workspace: {path!r}"}
return None # fall through to mode / canUseTool
El sketch hace visible el punto de control. El hook devuelve una denegación estructurada y el reasoning loop recibe esa denegación como observación de una herramienta.
La comprobación de rutas es una allowlist: una raíz de workspace, y todo lo demás denegado. Una deny-list de prefijos prohibidos solo bloquea las rutas que se te hayan ocurrido. ~/.ssh/id_rsa nunca se escribe exactamente como lo anotaste. La comprobación de comandos sigue siendo una deny-list. La coincidencia de subcadenas no es una política de shell de producción. Una implementación real debería parsear el comando y apoyarse en el sandbox del sistema operativo al llegar a la ejecución. El sketch no es por sí mismo un límite de ejecución: si lo ejecuta un hook externo, un timeout debe dejar vigente una restricción independiente del workspace.
Capa 2: un canary de entrada para prompt injection
El secuestro de objetivos del agent (ASI01) suele llegar a través de una página web recuperada, un mensaje de usuario o un PDF de investigación. Un canary barato basado en regex detecta patrones literales de instrucciones y crea un evento de telemetría útil. No detectará injections ofuscadas, multilingües o dependientes del contexto, por lo que no puede actuar como límite de decisión:
# agent/input_canary.py
import re
INJECTION_PATTERNS = [
re.compile(r"ignore\s+(?:all\s+|any\s+|the\s+)?"
r"(?:previous\s+|prior\s+|above\s+|earlier\s+)?"
r"(?:instructions|rules|prompts?)",
re.IGNORECASE),
re.compile(r"you are now|act as|roleplay as", re.IGNORECASE),
re.compile(r"system[ _:]*prompt", re.IGNORECASE),
re.compile(r"<\|im_(start|end)\|>"),
]
def input_canary(text: str) -> dict | None:
for pat in INJECTION_PATTERNS:
m = pat.search(text)
if m:
return {"flag": "possible_injection", "match": m.group(0)}
return None
Registra las entradas marcadas; no las rechaces automáticamente. Los falsos positivos son caros para un asistente de investigación. Pero el log es lo que permite detectar que el número de alertas de repente se dispara a partir de un usuario.
Capa 3: validación de structured outputs mediante un stop hook
Un modelo de Pydantic más un hook Stop proporciona un loop estricto de validar y reintentar para generar informes. El agent no puede afirmar «hecho» hasta que la salida supera la validación del schema y una smoke test:
# No-run: illustrative policy sketch; requires Pydantic and the repo-local agent.schemas module.
# agent/stop_hook.py
from pydantic import ValidationError
from agent.schemas import MarketReport
def on_stop(final_output: str) -> dict:
try:
report = MarketReport.model_validate_json(final_output)
except ValidationError as e:
return {"decision": "continue",
"feedback": f"schema invalid: {e.errors()[:3]}"}
if not report.tickers:
return {"decision": "continue",
"feedback": "no tickers in report — did you skip the snapshot step?"}
return {"decision": "allow_stop"}
Una comprobación del schema y una smoke test marcan la diferencia entre «el agent ha dicho que ha terminado» y «la salida es realmente un informe».
Capa 4: aprobación antes de acciones outbound
El execute_trade del analista de mercado es una transición de estado simulada e idempotente, por lo que demuestra el enrutamiento de aprobaciones, no un efecto financiero irreversible. Para una integración outbound real (email, Slack, un informe para un cliente o una orden de brokerage), muestra a la persona la acción propuesta y espera su aprobación o rechazo antes de ejecutar la herramienta. Utiliza interrupt() para esa pausa:
# No-run: illustrative outbound-tool sketch; requires LangGraph, a tool decorator, and smtp_send.
# agent/tools/notify.py
from langgraph.types import interrupt
@tool
def send_report(to: str, body: str):
resp = interrupt({
"action": "send_report",
"to": to,
"body": body, # Review the complete content that will execute.
})
if resp.get("action") == "approve":
return smtp_send(to, body)
return "send cancelled by human"
Las acciones outbound completan la tríada letal. Exige aprobación para las acciones outbound cuando la autorización y la política de despliegue existentes no las cubran. Vincula la aprobación al destino y al contenido completos; si cambian los argumentos, se necesita una nueva decisión. Los mensajes dirigidos a finanzas, clientes u otros destinatarios externos deben mostrar a la persona aprobadora qué se enviará y adónde.
Qué no hace esta pila
Esto no defiende frente a:
- Una dependencia upstream comprometida (tipo axios). El agent ejecuta lo que
uv syncindica que debe ejecutar. - Un
.mcp.jsonmalicioso en un repo clonado (tipo CVE-2025-59536). El modelo de permisos del cliente MCP del host es donde se detecta, no en el código del agent. - Una cadena de robo de datos construida con herramientas legítimas (tipo EchoLeak): el agent lee datos privados, obtiene URLs externas y envía mensajes fuera. Rompe o limita esa ruta de exfiltración con acceso a datos con scope, routing de confianza, restricciones de egress y aprobación cuando sea necesario. Eliminar una capacidad bloquea esta ruta concreta, no todos los ataques posibles.
- Un escape de
execute_python_analysis, el evaluador de Python en proceso del agent. Bloquea una lista de tipos de sentencias, rechaza cualquier identificador que empiece por un guion bajo y solo permite imports dejson,mathystatistics. Peroexecen el proceso worker no es un límite: un bypass se ejecuta con los file handles y la red del worker. Colócalo detrás de aislamiento del sistema de archivos, la red y las credenciales, además de límites de CPU, memoria y tiempo, antes de evaluar código no fiable. Un subprocess por sí solo hereda el acceso y no es un security sandbox.
Estas cuatro capas son políticas locales, y la política local es la capa más interna que controlas, no la única. Cada elemento de esa lista debe interceptarse en otro lugar: en el lockfile, en el cliente MCP, en el límite de proceso alrededor del código generado o en controles que impidan que instrucciones no fiables conecten datos privados con un destino de exfiltración.
Conclusiones clave
- Los filtros de contenido y la política de ejecución protegen límites distintos. Los filtros inspeccionan la entrada y la salida del modelo. La autorización de herramientas, el scope de las credenciales, los sandboxes y los controles de supply chain actúan sobre las rutas utilizadas en los siete incidentes.
- La mayoría de las categorías de OWASP ASI requieren controles fuera de la salida del modelo. Utiliza la lista para asignar cada amenaza al componente que realmente puede bloquearla o registrarla.
- El permiso es infraestructura, no un prompt. Claude Code documenta la precedencia de las reglas deny, ask y allow, mientras que
PreToolUsepuede bloquear antes de la ejecución. El Claude Agent SDK expone una rutacanUseToolseparada. Otros runtimes necesitan un modelo de precedencia igual de comprobable. - Trata la denegación estructurada de un hook PreToolUse como otra observación de herramienta. El reasoning loop ya la gestiona. No necesitas un workflow de seguridad separado.
- Una tasa de aprobación del 93 % es una señal para revisar la calidad de los prompts y la frecuencia de escalado. Registra las ediciones, denegaciones e incidentes posteriores a la aprobación en lugar de copiar un objetivo universal.
- Los tokens vinculados a audiencia y los vaults por sesión limitan el replay y la exposición de credenciales. No sustituyen a la confianza del proyecto, la política de hooks ni el sandboxing.
- Las comprobaciones de supply chain deben ejecutarse a la velocidad de la automatización. Fija las versiones y los SHAs de las Actions, escanea en CI y revisa los cambios de dependencias en las pull requests creadas por agents.
- Construye la capa de políticas de modo que el lanzamiento de un nuevo producto no la invalide. OpenAI Agents SDK, Codex CLI y Claude Code expresan las mismas primitivas de forma distinta. Las primitivas (escaleras de permisos, hooks, sandboxes, interrupciones y tokens vinculados a audiencia) son en las que estás apostando.
La siguiente capa es el runtime
La Parte 5, Long-Running AI Agent Runtime, muestra dónde viven el sandbox, el secret broker, el checkpoint y la traza de auditoría durante una ejecución prolongada. La Parte 6 entra después en el harness, donde esta escalera de permisos es una fase entre varias, y pregunta cómo las comprobaciones de aceptación, los reintentos y la evaluación basada en trazas impiden que el loop declare el éxito demasiado pronto. También añade una pregunta que este artículo no necesitaba: si una llamada que agotó el tiempo a mitad de ejecución es realmente segura de reenviar.
Referencias
Los marcos conceptuales
- Bharani Subramaniam y Martin Fowler, Emerging Patterns in Building GenAI Products.
- Simon Willison, The lethal trifecta for AI agents, 16 de junio de 2025.
- Joel Fokou, Parallax: Why AI Agents That Think Must Never Act, arXiv 2604.12986, 14 de abril de 2026 (no revisado por pares).
- Alessandro Pignati, Your AI Agent Has Too Much Power: Understanding and Taming Excessive Agency, enero de 2026.
Productos de guardrails para LLM
- NVIDIA NeMo Guardrails
- Meta Llama Guard 4
- Guardrails AI
- Lakera Guard
- AWS Bedrock Guardrails
- Azure Content Safety: Prompt Shields
- openai-guardrails-python
Incidentes
- Itay Ravia (antes en Aim Labs, ahora en Cato Networks), Breaking down EchoLeak (CVE-2025-32711).
- AWS, Amazon Q Developer VS Code v1.84.0 advisory (CVE-2025-8217).
- Microsoft, Azure MCP Server CVE record (CVE-2026-32211; referencia del proveedor: Microsoft).
- Check Point Research, RCE and API token exfiltration through Claude Code project files (CVE-2025-59536).
- axios, v1.14.1 / v0.30.4 compromise post-mortem.
- Aqua Security, Trivy Actions tag hijack (GHSA-69fq-xp46-6x23).
Superficies de políticas
- OpenAI Agents SDK — MCP tools docs
- Codex CLI managed configuration
- Claude Code permission modes
- Claude Code sandboxing
- Claude Managed Agents
HITL
- LangGraph interrupts docs
- HumanLayer Python quickstart
- Anthropic, Measuring AI agent autonomy in practice, 18 de febrero de 2026.
- Anthropic, Claude Code auto mode, 25 de marzo de 2026.
- Jackson Wells (Galileo), How to Build Human-in-the-Loop Oversight for Production AI Agents, 21 de diciembre de 2025.
OWASP
- OWASP Agentic Security Initiative, Top 10 for Agentic Applications, 2026, 9 de diciembre de 2025.
La capa de políticas del Market Analyst Agent vive en el grafo combinado de análisis a trading del repo, no en el grafo de análisis listado en la Parte 1. Gobierna el estado de operaciones simuladas: un nodo guardian determinista rechaza acciones restringidas, aprueba automáticamente las de bajo valor y escala el resto a un nodo de compliance officer antes de que el grafo se detenga con interrupt_before. La capa de políticas está en GitHub. El deny hook, el input canary y el validador del Stop-hook anteriores son sketches de los mismos puntos de control. Están escritos para leerse, no para copiarse directamente en ese repo.