Action is proposed as structured data (tool + args).
El problema (en producción)
Empiezas con una blocklist porque es rápido: “bloqueamos lo peligroso”.
Luego agregas un tool nuevo. Te olvidas de actualizar la blocklist. El agente lo descubre.
Y ahora estás on-call porque “bloqueamos lo peligroso” era más una historia que un control.
Las blocklists se sienten como control. En sistemas de agentes, casi siempre son una trampa. Y se pudren rápido: dos semanas después nadie recuerda qué estaba “protegiendo” la lista.
Por qué esto se rompe en producción
1) No puedes enumerar “todas las herramientas peligrosas” por adelantado
Hoy es db.delete_user.
Mañana es crm.merge_accounts.
La semana que viene es tickets.close_all.
El tool que olvides bloquear es el que te muerde.
2) Las blocklists fallan por naming e indirección
Bloqueas db.write, alguien shippea db.patch.
Bloqueas email.send, alguien shippea email.send_bulk.
Peor: wrappers.
El agente llama workflow.run("close_ticket") y tu “blocklist” ni ve el side effect real.
3) Default-allow hace crecer la blast radius en silencio
Si la policy dice “todo permitido excepto…”, cada tool nuevo es expansión de permisos por defecto. Eso es un incidente esperando turno.
4) Las allowlists fuerzan una decisión explícita
Las allowlists molestan. Bien. Te obligan a decir: “sí, el agente puede llamar este tool bajo estas condiciones”.
Ejemplo de implementación (código real)
Un evaluador de policy pequeño:
- default-deny
- deny list opcional para modo incidente (freno de emergencia)
- “write tools requieren approval”
from dataclasses import dataclass
WRITE_TOOLS = {"email.send", "db.write", "ticket.close"}
@dataclass(frozen=True)
class Policy:
allow: set[str]
deny: set[str] = None # for incident mode
require_approval_for_writes: bool = True
class Denied(RuntimeError):
pass
def evaluate(policy: Policy, tool: str) -> str:
deny = policy.deny or set()
if tool in deny:
raise Denied(f"denied: {tool} (incident mode)")
if tool not in policy.allow:
raise Denied(f"not allowed: {tool}")
if policy.require_approval_for_writes and tool in WRITE_TOOLS:
return "approve"
return "allow"const WRITE_TOOLS = new Set(["email.send", "db.write", "ticket.close"]);
export class Denied extends Error {}
export function evaluate(policy, tool) {
const deny = new Set(policy.deny || []);
if (deny.has(tool)) throw new Denied("denied: " + tool + " (incident mode)");
if (!policy.allow.includes(tool)) throw new Denied("not allowed: " + tool);
if (policy.requireApprovalForWrites && WRITE_TOOLS.has(tool)) return "approve";
return "allow";
}Incidente real (con números)
Vimos a un equipo shippear un agente con: “bloquea herramientas peligrosas”.
Luego agregaron un tool: ticket.close_bulk.
No estaba en la deny list.
El agente lo usó porque era el camino más corto a “resolver”.
Impacto:
- ~200 tickets cerrados incorrectamente
- ~5 horas ingeniero para reabrir, explicar y parchear
- el agente quedó apagado una semana: nadie confiaba en él
Fix:
- allowlist default-deny
- writes detrás de approvals
- deny list solo para modo incidente (temporal)
Las blocklists sirven como freno. Como volante, son malas.
Trade-offs
- Las allowlists ralentizan el “just ship it”. En prod, es un feature.
- Necesitas tooling para gestionarlas (no hardcodees en 12 lugares).
- La gente intentará saltárselas con wrappers. No lo permitas.
Cuándo NO usarlo
- En prototipos locales puedes ser más laxo — pero no lleves eso a prod.
- Incluso con tools read-only, una allowlist pequeña evita exposiciones accidentales.
- Si tu “tool” es un RPC genérico que puede hacer cualquier cosa, arréglalo primero: divide por capacidades.
Checklist (copiar/pegar)
- [ ] Allowlist default-deny (nombres explícitos)
- [ ] Separar tools por capacidad (read vs write)
- [ ] Approvals para writes irreversibles
- [ ] Deny list solo para modo incidente (temporal)
- [ ] Log de denies (señal)
- [ ] No exponer tools genéricos “do anything”
Config segura por defecto (JSON/YAML)
policy:
default: "deny"
allow: ["search.read", "kb.read", "http.get"]
require_approval_for_writes: true
incident_mode:
deny: ["browser.run"] # freno temporal
logging:
log_denies: true
FAQ (3–5)
Usado por patrones
Fallos relacionados
- AI Agent Infinite Loop (Detectar + arreglar, con código)
- Explosión de presupuesto (cuando un agente quema dinero) + fixes + código
- Tool Spam Loops (fallo del agente + fixes + código)
- Incidentes de exceso de tokens (prompt bloat) + fixes + código
- Corrupción de respuestas de tools (schema drift + truncation) + código
Gobernanza requerida
P: ¿Las blocklists sirven para algo?
R: Sí: modo incidente y deshabilitaciones temporales. Son freno, no modelo de permisos.
P: ¿Puedo usar wildcards en allowlists?
R: Con cuidado. Los wildcards se vuelven default-allow con el tiempo. Si los usas, que sean estrechos y revisados.
P: ¿Qué pasa con tools tipo ‘workflow.run’?
R: Ocultan side effects. Prefiere tools explícitos para acciones explícitas, para que la policy pueda razonar.
P: ¿Cuál es la policy mínima segura?
R: Default-deny allowlist + tools read-only. Agrega writes después, con approvals.
Páginas relacionadas (3–6 links)
- Foundations: How agents use tools · Planning vs reactive agents
- Failure: Prompt injection attacks · Tool spam loops
- Governance: Tool permissions · Kill switch design
- Production stack: Production agent stack