agent-guardrail

Firewall de políticas determinista para agentes de IA que evalúa las llamadas a herramientas antes de la ejecución y devuelve decisiones ALLOW, WARN o BLOCK.

Documentación

Guardrail

agent-guardrail MCP server

📄 Lea el documento técnico

Un firewall de políticas para llamadas de herramientas de agentes de IA.

Tu agente quiere ejecutar un comando de shell, enviar un correo electrónico o mover dinero. Guardrail verifica esa solicitud contra las reglas que escribiste, antes de que ocurra, y la permite, la envía a un humano, o la bloquea — con una razón en lenguaje claro cada vez.

Inicio rápido en 60 segundos

git clone <this repo> && cd agent-guardrail
pip install -r requirements.txt

python3 cli.py check --agent trading-agent-001 --tool wallet.transfer \
  --args '{"amount": 9999, "to": "0xabc"}'

O pip install guardrail-mcp te da un comando guardrail directamente — misma salida, sin necesidad de clonar el repositorio (usa la política incluida en el paquete si no apuntas --policy a tu propio archivo):

guardrail check --agent trading-agent-001 --tool wallet.transfer \
  --args '{"amount": 9999, "to": "0xabc"}'
{
  "decision": "BLOCK",
  "matched_rules": [
    {"rule": "numeric_cap_exceeded", "severity": "BLOCK",
     "message": "amount=9999.0 exceeds cap 5 for 'wallet.transfer' (unknown agent)"}
  ]
}

Eso es todo — sin servidor, sin cuenta, sin clave API. policies/default.yaml es el archivo que tomó esta decisión; ábrelo y cambia los números para que coincidan con tus propias reglas.


Por qué esto, y no otra herramienta de "puntuación de riesgo de IA"

La mayoría de los proyectos de "seguridad de agentes de IA" (incluido un proyecto anterior mío) se apoyan en puntuaciones de riesgo estadísticas calculadas a partir de datos que nadie puede verificar realmente en tiempo de compilación — antigüedad de la billetera, "reputación", "riesgo" del contrato — lo cual requiere fuentes de datos pagadas que aún no tienes, o silenciosamente se convierte en datos simulados que pretenden ser reales. Bien para prototipos, deshonesto para publicar.

Guardrail solo hace afirmaciones que puede respaldar. Cada verificación es una regla determinista — una entrada en lista negra, una coincidencia de regex, un límite numérico, un límite de tasa — evaluada contra un archivo de políticas que escribes y puedes auditar tú mismo, respaldado por un registro de auditoría real y persistente (SQLite) que puedes consultar. Nada aquí pretende saber algo que no sabe.

Tampoco es específico de blockchain. Ejecución de shell, correo electrónico, solicitudes HTTP, eliminación de archivos, escrituras en bases de datos, transacciones criptográficas — el mismo motor, el mismo archivo de políticas, las mismas reglas.


Cuatro formas de usarlo

1. CLI — para probar una política manualmente

Mostrado arriba. Sin configuración, retroalimentación instantánea mientras escribes reglas.

2. Servidor MCP (mcp_server.py) — la vía de entrada fácil, consultivo

Expone guardrail_check, guardrail_record_outcome y guardrail_agent_history como herramientas MCP que cualquier agente compatible con MCP (Claude Desktop, Claude Code, clientes MCP personalizados) puede llamar.

{
  "mcpServers": {
    "guardrail": {
      "command": "python3",
      "args": ["/absolute/path/to/agent-guardrail/mcp_server.py"],
      "env": { "GUARDRAIL_POLICY": "/absolute/path/to/agent-guardrail/policies/default.yaml" }
    }
  }
}

Luego dile a tu agente (en su prompt del sistema) que siempre llame a guardrail_check antes de gastar dinero, eliminar datos, enviar mensajes a alguien externo o ejecutar código.

Sé claro sobre su límite: como cualquier herramienta MCP, nada impide que el modelo que llama simplemente no la invoque. Esto solo ayuda si el agente está instruido para verificar siempre primero — para una garantía que no puede omitir, ver #3.

3. guardrail.decorator.enforce — la garantía real

Envuelve la función real de Python que realiza el efecto secundario de una herramienta. La verificación se ejecuta en tu código, antes de que esa función se ejecute — el modelo nunca tiene la oportunidad de llamar a la función real directamente.

from guardrail.decorator import enforce, BlockedActionError

@enforce(engine, tool_name="send_email")
def send_email(agent_id: str, to: str, subject: str, body: str):
    ...  # only runs if the decision is ALLOW, or WARN-and-confirmed

Usa esto si estás construyendo tu propio bucle de agente (LangChain, CrewAI, un host MCP personalizado, un bot de Slack con acceso a herramientas). Ejecuta python3 examples/example_agent_usage.py para verlo bloquear una llamada de función real.

4. guardrail.mcp_enforced_server.EnforcedGuardrailMCPServer — la garantía real, a través de MCP

El servidor MCP en #2 arriba es honesto sobre ser consultivo: el modelo obtiene una herramienta guardrail_check, pero nada impide que llame a la herramienta real (expuesta por otro servidor MCP, o por el acceso directo del propio modelo) sin verificar primero, o verificar una cosa y hacer otra. Si el modelo se comunica con tu infraestructura solo a través de MCP — sin decorador de Python posible — esta es la misma garantía #3 para ese caso: el operador registra ejecutores de acciones reales (el código que tiene credenciales reales y realiza el efecto secundario real) como la única forma en que el modelo puede invocar esa acción.

from guardrail.mcp_enforced_server import EnforcedGuardrailMCPServer

def do_transfer(request):
    wallet = get_wallet_for(request.agent_id)  # real credentials, held here - never exposed to the model
    tx_hash = wallet.transfer(to=request.arguments["to"], amount=request.arguments["amount"])
    return {"tx_hash": tx_hash}

server = EnforcedGuardrailMCPServer(policy_path="policies/default.yaml")
server.register_action(
    "wallet.transfer", "Transfer funds from the agent's wallet.",
    input_schema={"type": "object", "properties": {"to": {"type": "string"}, "amount": {"type": "number"}}, "required": ["to", "amount"]},
    executor=do_transfer,
)
server.serve_stdio()

Al modelo se le da exactamente una herramienta MCP llamada wallet.transfer — no hay una forma separada y sin protección de mover fondos a través de este servidor. Una decisión de BLOQUEO significa que do_transfer nunca se ejecuta. Tanto esto como enforce() comparten una implementación de "verificar, quizás enrutar ADVERTENCIA a un humano, ejecutar solo si no está bloqueado, informar el resultado real" (guardrail/enforcement.py) — no dos copias mantenidas independientemente de la misma garantía.


Conseguir que un humano confirme realmente una ADVERTENCIA

on_warn es el gancho — Guardrail incluye dos implementaciones listas:

Interfaz web local (guardrail/confirmation/web_ui.py) — un pequeño servidor integrado (solo stdlib, sin Flask) con botones Aprobar/Rechazar. La función envuelta se bloquea hasta que alguien hace clic en uno, o se agota el tiempo (falla cerrado — el tiempo de espera significa rechazo, no "permitir por defecto").

from guardrail.confirmation.web_ui import ConfirmationServer

confirmation = ConfirmationServer(port=8787, timeout_seconds=300)
confirmation.start(open_browser=True)

@enforce(engine, tool_name="wallet.transfer", on_warn=confirmation.request_confirmation)
def transfer(...): ...

Pruébalo en vivo: python3 examples/example_web_confirmation.py, luego abre http://localhost:8787.

Prompt de terminal (guardrail/confirmation/cli_ui.py) — para scripts y pruebas locales donde un navegador es excesivo:

from guardrail.confirmation.cli_ui import cli_confirm

@enforce(engine, tool_name="wallet.transfer", on_warn=cli_confirm)
def transfer(...): ...

Ninguno es obligatorio — on_warn es solo una función (decision) -> bool, así que un mensaje de Slack, un ticket o cualquier otra cosa que ya uses también funciona.


Escribiendo una política

Las políticas son YAML simple — ver policies/default.yaml para un punto de partida real y funcionante (11 herramientas con confirmación, 10 verificaciones de patrones destructivos, límites numéricos, reglas de dominio, límites de tasa, todo comentado).

Tipo de reglaQué verifica
blocked_toolsNombres de herramientas que nunca están permitidos
confirmation_required_toolsNombres de herramientas que siempre producen WARN
argument_patternsRegex contra los argumentos de llamada serializados en JSON — comandos de shell destructivos, SQL, credenciales filtradas, traversal de rutas, SSRF, force-pushes, independientemente de qué herramienta los lleve
numeric_capsLímites numéricos de campos por herramienta, más estrictos para agentes sin historial
aggregate_capsUn límite compartido entre varias herramientas, rastreado como un total acumulado por agente — ver abajo
domain_rulesListas de permitidos/denegados en un campo de URL o destinatario de correo, por herramienta
rate_limitsLímites de llamadas con ventana deslizante por (agente, herramienta), respaldados por SQLite

numeric_caps limita cada herramienta de forma independiente — wallet.transfer con un límite de 1000/día y wallet.approve con un límite de 1000/día por separado significa que un agente que usa ambas aún puede mover 2000/día combinados. aggregate_caps cierra eso: cada herramienta listada en el mismo grupo extrae de un total acumulado compartido, por ejemplo

aggregate_caps:
  daily_money_movement:
    tools:
      wallet.transfer: amount
      wallet.approve: amount
    window_seconds: 86400
    max_unknown_agent: 5
    max_known_agent: 1000

Solo el gasto confirmado cuenta para el total: una solicitud BLOCKda nunca agrega nada, y una solicitud registrada provisionalmente (porque su propia verificación pasó) se reembolsa si la acción real luego resulta no haber tenido éxito — engine.record_outcome(request_id, "error"), llamado automáticamente tanto por enforce() como por el servidor MCP forzado (comparten una implementación de esto, guardrail/enforcement.py) cuando el ejecutor real lanza una excepción, o cuando un WARN que un humano rechaza resulta en un BlockedActionError. La aplicación real de esto por lo tanto tiene la misma advertencia que todo lo demás que depende de que record_outcome sea llamado: funciona completamente bajo enforce() y el servidor MCP forzado (ver abajo); bajo el servidor MCP solo consultivo (#2 arriba), un monto registrado provisionalmente simplemente permanece registrado, ya que nada informa si la acción realmente ocurrió. Ver el docstring del módulo de guardrail/storage/aggregate_spend.py para el panorama completo.

No se necesitan cambios de código para ajustar nada de esto — edita el YAML, reinicia el proceso (o el servidor MCP).


Ejecutando las pruebas

pip install -r requirements.txt
PYTHONPATH=. python3 -m unittest discover -s tests -v

134 pruebas: evaluación de reglas, el pipeline completo del motor (límite de tasa real respaldado por SQLite, seguimiento de gasto agregado y persistencia de auditoría), el decorador enforce y el servidor MCP forzado (ambos demuestran que un BLOCK genuinamente impide que la acción real se ejecute, compartiendo una implementación de esa garantía), el manejo JSON-RPC del servidor MCP consultivo, la interfaz web de confirmación sobre solicitudes HTTP reales contra un servidor en vivo, y una suite dedicada que verifica que el policies/default.yaml enviado — no solo políticas de prueba sintéticas — realmente detecta lo que afirma.


Lo que honestamente aún falta

  • SQLite de un solo proceso por defecto. Está bien para un proceso de agente; para múltiples réplicas que comparten límites de tasa/historial de auditoría, apunta cada proceso al mismo archivo en almacenamiento compartido, o cambia a una base de datos real (las clases de almacenamiento son pequeñas y fáciles de reorientar).
  • La redacción de secretos/PII en el registro de auditoría está activada por defecto. AuditLog redacta valores cuya clave parece sensible (password, api_key, authorization, ...) y un par de formas de valores de alta confianza (bloques de claves privadas PEM, cadenas con forma de JWT) independientemente del nombre de la clave, recursando en dicts/listas anidadas — ver guardrail/storage/redaction.py para exactamente qué se detecta y qué no, y por qué las heurísticas de entropía de propósito general se omitieron deliberadamente (demasiados falsos positivos en UUIDs/hashes ordinarios). Pasa AuditLog(redact=False) para almacenar argumentos tal como se enviaron, o extra_sensitive_keys={...} para redactar nombres de campos adicionales específicos de tus herramientas.
  • La política predeterminada es un punto de partida razonable, no un modelo de amenazas completo. Detecta patrones destructivos conocidos de shell/SQL y formatos obvios de credenciales — extiende argument_patterns para lo que tus agentes realmente toquen.
  • La interfaz web de confirmación no tiene autenticación. Se vincula a 127.0.0.1 por diseño (no expuesta en la red), pero cualquiera con acceso local a ese puerto puede aprobar/rechazar. Está bien para la máquina de un solo desarrollador; ponla detrás de tu propia autenticación si varias personas comparten el host.

Nada de esto está simulado o falsificado — simplemente aún no está construido, y son los próximos pasos honestos si adoptas esto.


Publicar esto / lograr que la gente realmente lo use

Ver PUBLISHING.md para una lista de verificación concreta: directorios MCP a los que enviar, qué necesita un listado y cómo se ve "terminado".


Proyectos relacionados

Mismo autor, mismo principio aplicado en otro lugar:

  • agentic-wallet-guardian-v3 — una capa de decisión de seguridad para agentes de IA que transaccionan en cadena. MIT, 112 pruebas.
  • x402-attest — atestaciones firmadas criptográficamente (Ed25519), verificables de forma independiente para decisiones de política de pago entre agentes. Prueba de concepto temprana.
  • open-agent-attestation — especificación abierta neutral al proveedor (JWT+EdDSA) para firmar decisiones de política de agentes, verificable por cualquiera. x402-attest arriba usa un formato personalizado; esta es la versión generalizada. Borrador v0.1.

Estructura del proyecto

guardrail/
    __main__.py            CLI implementation — also the `guardrail` console command
    mcp_server.py            MCP stdio server — also the `guardrail-mcp-server` console command
    core/
        models.py               ActionRequest, RuleMatch, GuardrailDecision (stdlib only)
        policy.py                 Policy loader (the one place PyYAML is used)
    rules.py                    Deterministic rule evaluators
    storage/
        rate_limiter.py           SQLite-backed sliding-window rate limiter
        audit.py                    SQLite-backed persistent audit log
    engine.py                    GuardrailEngine — orchestrates rules + rate limit + audit
    decorator.py                 enforce() — the unbypassable integration point
    confirmation/
        web_ui.py                    Local web UI for human approve/reject (stdlib http.server)
        cli_ui.py                      Terminal-prompt confirmation
    policies/default.yaml           Copy of the default policy bundled into the installed package
policies/default.yaml       Canonical, editable default policy (git-clone workflow)
cli.py                      Thin shim -> guardrail/__main__.py (for `python3 cli.py`)
mcp_server.py                Thin shim -> guardrail/mcp_server.py (for `python3 mcp_server.py`)
pyproject.toml               Package metadata — `pip install .` gives you `guardrail` + `guardrail-mcp-server`
.github/workflows/ci.yml      Runs the test suite + policy validation + package build on every push
examples/
    example_agent_usage.py       Decorator basics
    example_web_confirmation.py    Real browser-based approve/reject, live
tests/                       46 unit tests, all runnable with just PyYAML installed
CONTRIBUTING.md              How to add a rule type, ground rules
CHANGELOG.md                  Version history
PUBLISHING.md                 How to actually get this in front of people
landing/index.html             Static one-page site (open directly or host on GitHub Pages)