Capítulo 4.7 — Policy-as-code¶
🧠 Conceito¶
Políticas críticas são codificadas, testáveis e versionadas. Exemplos:
- "Agente não pode enviar e-mail externo sem aprovação dupla."
- "Tool de CRM só acessa campos permitidos por role."
- "Documentos confidenciais não vão para modelo externo sem contrato."
🧰 Engines comuns¶
- Open Policy Agent (OPA) / Rego - engine declarativo de propósito geral, usado em Kubernetes, microsserviços, gateways e agentes.
- AWS Cedar - linguagem de política focada em authorization, com semântica de "policy + entity store".
- AuthZed SpiceDB - ReBAC distribuído, modelo inspirado em Zanzibar (Google).
🧪 Exemplo prático - autorização de tool de agente em Rego¶
Cenário: um harness chama OPA passando o input abaixo antes de executar qualquer tool_call. A policy precisa:
- negar tool com status
deprecatedouremoved; - negar cross-tenant (
user.tenant_id != resource.tenant_id); - exigir aprovação humana para
risk_level == "high"; - permitir apenas tools na allowlist da role.
Input típico enviado pelo executor:
{
"user": {
"id": "u-1042",
"tenant_id": "tenant-abc",
"roles": ["support-agent"]
},
"agent": {
"id": "support-agent@2.1.0",
"tenant_id": "tenant-abc"
},
"tool": {
"name": "crm.update_contact",
"version": "1.4.0",
"status": "active",
"risk_level": "high"
},
"action": "execute",
"resource": {
"type": "contact",
"tenant_id": "tenant-abc"
},
"approval": {
"required": true,
"granted_by": "manager-7",
"granted_at": "2026-05-10T18:22:00Z"
}
}
Policy Rego (exemplo educacional):
package agent.tools.authz
default allow := false
# Allowlist de tools por role.
allowed_tools := {
"support-agent": {
"crm.search_contact",
"crm.update_contact",
"kb.search",
},
"billing-agent": {
"crm.search_contact",
"billing.create_invoice",
},
}
# Tools com status proibido nunca podem rodar.
blocked_status := {"deprecated", "removed", "blocked"}
# Helper: a tool está na allowlist da role do usuário?
tool_in_allowlist {
some role
role := input.user.roles[_]
allowed_tools[role][input.tool.name]
}
# Helper: tenant do usuário == tenant da agente == tenant do recurso.
same_tenant {
input.user.tenant_id == input.agent.tenant_id
input.user.tenant_id == input.resource.tenant_id
}
# Helper: aprovação humana válida para alto risco.
approval_ok {
input.tool.risk_level != "high"
}
approval_ok {
input.tool.risk_level == "high"
input.approval.required
input.approval.granted_by != ""
}
# Regra principal.
allow {
not blocked_status[input.tool.status]
tool_in_allowlist
same_tenant
approval_ok
}
# Motivos de negação (úteis para audit log).
deny_reason["tool_status_blocked"] {
blocked_status[input.tool.status]
}
deny_reason["tool_not_in_allowlist"] {
not tool_in_allowlist
}
deny_reason["cross_tenant"] {
not same_tenant
}
deny_reason["approval_missing"] {
input.tool.risk_level == "high"
not input.approval.granted_by
}
O que essa policy garante:
- Bloqueia tool deprecated/removed independentemente do que o modelo propõe.
- Bloqueia chamada cross-tenant mesmo se o prompt dissesse o contrário.
- Bloqueia tool fora da allowlist da role.
- Exige aprovação humana registrada para
risk_level == "high". - Devolve motivos estruturados para o audit log.
O que essa policy NÃO garante:
- Que o conteúdo dos argumentos é válido (precisa schema/Pydantic).
- Que a aprovação humana foi consciente (depende de UX e treinamento).
- Que o backend da tool aplica RBAC interno (precisa autorização no serviço chamado também - defesa em profundidade).
- Que o
inputenviado ao OPA é honesto (depende do harness; quem controla o input controla a decisão).
🧪 Testes da policy¶
Toda policy precisa de testes positivos e negativos versionados no mesmo repo. Em OPA, isso pode ser feito com opa test e arquivos *_test.rego:
package agent.tools.authz_test
import data.agent.tools.authz
test_allow_simple_case {
authz.allow with input as {
"user": {"id": "u1", "tenant_id": "t1", "roles": ["support-agent"]},
"agent": {"id": "a@1", "tenant_id": "t1"},
"tool": {"name": "kb.search", "status": "active", "risk_level": "low"},
"action": "execute",
"resource": {"type": "doc", "tenant_id": "t1"},
"approval": {"required": false, "granted_by": "", "granted_at": ""}
}
}
test_deny_cross_tenant {
not authz.allow with input as {
"user": {"id": "u1", "tenant_id": "t1", "roles": ["support-agent"]},
"agent": {"id": "a@1", "tenant_id": "t1"},
"tool": {"name": "kb.search", "status": "active", "risk_level": "low"},
"action": "execute",
"resource": {"type": "doc", "tenant_id": "t2"},
"approval": {"required": false, "granted_by": "", "granted_at": ""}
}
}
test_deny_deprecated_tool {
not authz.allow with input as {
"user": {"id": "u1", "tenant_id": "t1", "roles": ["support-agent"]},
"agent": {"id": "a@1", "tenant_id": "t1"},
"tool": {"name": "crm.update_contact", "status": "deprecated", "risk_level": "high"},
"action": "execute",
"resource": {"type": "contact", "tenant_id": "t1"},
"approval": {"required": true, "granted_by": "m7"}
}
}
🧰 Exemplos práticos relacionados (planejados)¶
EX-AGT-06- agente com policy Rego sidecar bloqueando tool calls.EX-SEC-03- tool misuse mitigado por policy.
📌 Checklist¶
- [ ] Políticas críticas estão em código e versionadas?
- [ ] Há testes de policy (positivos e negativos)?
- [ ] Há observabilidade de policy decisions (decisão + motivo)?
- [ ] Decisões são logadas com versão do bundle de policy?
- [ ] Backend chamado pelas tools aplica autorização independente (defesa em profundidade)?
📚 Referências¶
- Open Policy Agent - Policy Language (Rego): https://www.openpolicyagent.org/docs/policy-language
- Open Policy Agent - Policy Testing: https://www.openpolicyagent.org/docs/policy-testing
- AWS Cedar - Policy Language Reference: https://docs.cedarpolicy.com/policies/syntax-policy.html
- AuthZed SpiceDB - Concepts: https://authzed.com/docs/spicedb/concepts/zedtokens
- Google Zanzibar (paper): https://research.google/pubs/pub48190/