agent-guardrail

Firewall de política determinística para agentes de IA que avalia chamadas de ferramentas antes da execução e retorna decisões ALLOW, WARN ou BLOCK.

Documentação

Guardrail

agent-guardrail MCP server

📄 Leia o white paper

Um firewall de políticas para chamadas de ferramentas de agentes de IA.

Seu agente quer executar um comando de shell, enviar um e-mail ou mover dinheiro. O Guardrail verifica essa solicitação contra as regras que você escreveu, antes que ela aconteça, e ou permite, ou pede a um humano, ou bloqueia — com uma explicação em inglês simples a cada vez.

Início rápido em 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"}'

Ou o pip install guardrail-mcp oferece um comando guardrail diretamente — mesma saída, sem necessidade de clonar o repositório (recorre à política incluída no pacote se você não apontar o --policy para o seu próprio arquivo):

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)"}
  ]
}

É isso — sem servidor, sem conta, sem chave de API. O policies/default.yaml é o arquivo que decidiu isso; abra-o e altere os números para corresponder às suas próprias regras.


Por que isso, e não outra ferramenta de "pontuação de risco de IA"

A maioria dos projetos de "segurança de agentes de IA" (incluindo um projeto anterior meu) depende de pontuações de risco estatísticas calculadas a partir de dados que ninguém consegue verificar de fato no momento da compilação — idade da carteira, "reputação", "risco" do contrato — que ou exige feeds de dados pagos que você ainda não tem, ou silenciosamente se torna dados simulados fingindo ser reais. Bom para prototipagem, desonesto para lançar.

O Guardrail só faz afirmações que pode comprovar. Cada verificação é uma regra determinística — uma entrada de lista de bloqueio, uma correspondência de regex, um limite numérico, um limite de taxa — avaliada contra um arquivo de política que você escreve e pode auditar por conta própria, respaldado por um log de auditoria real e persistente (SQLite) que você pode consultar. Nada aqui finge saber algo que não sabe.

Também não é específico de blockchain. Execução de shell, e-mail, solicitações HTTP, exclusão de arquivos, gravações em banco de dados, transações de criptomoedas — o mesmo mecanismo, o mesmo arquivo de política, as mesmas regras.


Quatro maneiras de usar

1. CLI — para testar uma política manualmente

Mostrado acima. Sem configuração, feedback instantâneo enquanto você escreve regras.

2. Servidor MCP (mcp_server.py) — o caminho fácil, consultivo

Expõe guardrail_check, guardrail_record_outcome e guardrail_agent_history como ferramentas MCP que qualquer agente compatível com MCP (Claude Desktop, Claude Code, clientes MCP personalizados) pode chamar.

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

Depois diga ao seu agente (no prompt do sistema) para sempre chamar guardrail_check antes de gastar dinheiro, excluir dados, enviar mensagens externamente ou executar código.

Seja claro sobre seu limite: como qualquer ferramenta MCP, nada impede o modelo chamador de simplesmente não invocá-la. Isso só ajuda se o agente for instruído a sempre verificar primeiro — para uma garantia que ele não pode pular, veja #3.

3. guardrail.decorator.enforce — a garantia real

Encapsula a função Python real que executa o efeito colateral de uma ferramenta. A verificação ocorre no seu código, antes de essa função ser executada — o modelo nunca tem a chance de chamar a função real diretamente.

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

Use isso se você estiver construindo seu próprio loop de agente (LangChain, CrewAI, um host MCP personalizado, um bot do Slack com acesso a ferramentas). Execute python3 examples/example_agent_usage.py para ver bloquear uma chamada de função real.

4. guardrail.mcp_enforced_server.EnforcedGuardrailMCPServer — a garantia real, via MCP

O servidor MCP em #2 acima é honesto sobre ser consultivo: o modelo recebe uma ferramenta guardrail_check, mas nada impede que ele chame a ferramenta real (exposta por outro servidor MCP, ou pelo acesso direto do próprio modelo) sem verificar primeiro, ou verificar uma coisa e fazer outra. Se o modelo fala com sua infraestrutura apenas via MCP — sem decorador Python possível — esta é a mesma garantia #3 para esse caso: o operador registra executores de ações reais (o código que detém credenciais reais e executa o efeito colateral real) como a única maneira de o modelo invocar essa ação.

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()

O modelo recebe exatamente uma ferramenta MCP chamada wallet.transfer — não há nenhuma maneira separada e sem proteção de mover fundos por este servidor. Uma decisão BLOCK significa que do_transfer nunca é executado. Tanto este quanto o enforce() compartilham uma implementação de "verificar, talvez rotear WARN para um humano, executar apenas se não bloqueado, relatar o resultado real de volta" (guardrail/enforcement.py) — não duas cópias mantidas independentemente da mesma garantia.


Obtendo confirmação humana real para um WARN

on_warn é o gancho — o Guardrail inclui duas implementações prontas:

Interface web local (guardrail/confirmation/web_ui.py) — um pequeno servidor integrado (apenas stdlib, sem Flask) com botões Aprovar/Rejeitar. A função encapsulada bloqueia até que alguém clique em um, ou expira (falha fechada — expiração significa rejeitar, não "permitir por padrão").

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(...): ...

Experimente ao vivo: python3 examples/example_web_confirmation.py, depois abra http://localhost:8787.

Prompt de terminal (guardrail/confirmation/cli_ui.py) — para scripts e testes locais onde um navegador é exagero:

from guardrail.confirmation.cli_ui import cli_confirm

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

Nenhum é obrigatório — on_warn é apenas uma função (decision) -> bool, então uma mensagem do Slack, um ticket ou qualquer outra coisa que você já usa também funciona.


Escrevendo uma política

Políticas são YAML simples — veja policies/default.yaml para um ponto de partida real e funcionante (11 ferramentas com confirmação obrigatória, 10 verificações de padrões destrutivos, limites numéricos, regras de domínio, limites de taxa, tudo comentado).

Tipo de regraO que verifica
blocked_toolsNomes de ferramentas que nunca são permitidos
confirmation_required_toolsNomes de ferramentas que sempre produzem WARN
argument_patternsRegex contra os argumentos de chamada serializados em JSON — comandos de shell destrutivos, SQL, credenciais vazadas, path traversal, SSRF, force-pushes, independentemente de qual ferramenta os carrega
numeric_capsLimites numéricos de campos por ferramenta, mais rígidos para agentes sem histórico
aggregate_capsUm limite compartilhado entre várias ferramentas, rastreado como um total contínuo por agente — veja abaixo
domain_rulesListas de permitir/negar em um campo de URL ou destinatário de e-mail, por ferramenta
rate_limitsLimites de chamadas em janela deslizante por (agente, ferramenta), respaldados por SQLite

numeric_caps limita cada ferramenta independentemente — wallet.transfer limitado a 1000/dia e wallet.approve limitado a 1000/dia separadamente significa que um agente usando ambos ainda pode mover 2000/dia combinados. aggregate_caps fecha isso: cada ferramenta listada no mesmo grupo usa um total contínuo compartilhado, por exemplo

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

Apenas gastos confirmados contam para o total: uma solicitação BLOCKada nunca adiciona nada, e uma solicitação registrada provisoriamente (porque sua própria verificação passou) é reembolsada se a ação real depois não tiver sido bem-sucedida — engine.record_outcome(request_id, "error"), chamado automaticamente tanto pelo enforce() quanto pelo servidor MCP forçado (eles compartilham uma implementação disso, guardrail/enforcement.py) quando o executor real levanta uma exceção, ou quando um WARN que um humano rejeita resulta em um BlockedActionError. A aplicação real disso, portanto, tem a mesma ressalva que tudo o mais que depende de record_outcome ser chamado: funciona totalmente sob enforce() e o servidor MCP forçado (veja abaixo); sob o servidor MCP apenas consultivo (#2 acima), um valor registrado provisoriamente apenas permanece registrado, já que nada relata de volta se a ação realmente aconteceu. Veja o docstring do módulo de guardrail/storage/aggregate_spend.py para o quadro completo.

Nenhuma mudança de código é necessária para ajustar nada disso — edite o YAML, reinicie o processo (ou o servidor MCP).


Executando os testes

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

134 testes: avaliação de regras, o pipeline completo do mecanismo (limite de taxa real respaldado por SQLite, rastreamento de gastos agregados e persistência de auditoria), o decorador enforce e o servidor MCP forçado (ambos provando que um BLOCK genuinamente impede a ação real de ser executada, compartilhando uma implementação dessa garantia), o tratamento JSON-RPC do servidor MCP consultivo, a interface web de confirmação sobre solicitações HTTP reais contra um servidor ativo, e uma suíte dedicada que verifica se o policies/default.yaml enviado — não apenas políticas de teste sintéticas — realmente captura o que afirma.


O que honestamente ainda falta

  • SQLite de processo único por padrão. Bom para um processo de agente; para múltiplas réplicas compartilhando limites de taxa/histórico de auditoria, aponte cada processo para o mesmo arquivo em armazenamento compartilhado, ou troque por um banco de dados real (as classes de armazenamento são pequenas e fáceis de redirecionar).
  • Redação de segredos/PII no log de auditoria está ativada por padrão. AuditLog redige valores cuja chave parece sensível (password, api_key, authorization, ...) e alguns formatos de valor de alta confiança (blocos de chave privada PEM, strings em formato JWT) independentemente do nome da chave, recursando em dicts/listas aninhados — veja guardrail/storage/redaction.py para exatamente o que é e não é capturado, e por que heurísticas de entropia de propósito geral foram deliberadamente deixadas de fora (muitos falsos positivos em UUIDs/hashes comuns). Passe AuditLog(redact=False) para armazenar argumentos como enviados, ou extra_sensitive_keys={...} para redigir nomes de campos adicionais específicos das suas ferramentas.
  • A política padrão é um ponto de partida razoável, não um modelo de ameaça completo. Ela captura padrões destrutivos bem conhecidos de shell/SQL e formatos óbvios de credenciais — estenda argument_patterns para o que seus agentes realmente tocam.
  • A interface web de confirmação não tem autenticação. Ela vincula a 127.0.0.1 por design (não exposta na rede), mas qualquer pessoa com acesso local a essa porta pode aprovar/rejeitar. Bom para a máquina de um único desenvolvedor; coloque atrás da sua própria autenticação se várias pessoas compartilharem o host.

Nenhum desses é simulado ou falso — eles simplesmente ainda não foram construídos, e são os próximos passos honestos se você adotar isso.


Publicando isso / fazendo as pessoas realmente usarem

Veja PUBLISHING.md para uma lista de verificação concreta: diretórios MCP para enviar, o que uma listagem precisa, e como é o "pronto".


Projetos relacionados

Mesmo autor, mesmo princípio aplicado em outro lugar:

  • agentic-wallet-guardian-v3 — uma camada de decisão de segurança para agentes de IA transacionando on-chain. MIT, 112 testes.
  • x402-attest — atestações assinadas criptograficamente (Ed25519), verificáveis de forma independente para decisões de política de pagamento entre agentes. Prova de conceito inicial.
  • open-agent-attestation — especificação aberta neutra de fornecedor (JWT+EdDSA) para assinar decisões de política de agentes, verificável por qualquer pessoa. O x402-attest acima usa um formato personalizado; esta é a versão generalizada. Rascunho v0.1.

Estrutura do projeto

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)