Action is proposed as structured data (tool + args).
Le problème (côté prod)
Tu commences par une blocklist parce que c’est rapide : « on interdit les trucs dangereux ».
Puis tu ajoutes un nouvel outil. Personne ne pense à mettre à jour la blocklist. L’agent le découvre.
Et maintenant tu es d’astreinte parce que « interdire les trucs dangereux » était surtout une histoire que tu te racontais.
Les blocklists donnent l’impression de contrôle. En systèmes d’agents, c’est souvent un piège. Et ça pourrit vite : deux semaines après, plus personne ne sait ce que la liste est censée protéger.
Pourquoi ça casse en prod
1) Tu ne peux pas lister « tous les outils dangereux » à l’avance
Aujourd’hui c’est db.delete_user.
Demain c’est crm.merge_accounts.
La semaine prochaine c’est tickets.close_all.
L’outil que tu as oublié de bloquer est celui qui te mord.
2) Les blocklists cassent sur le naming et l’indirection
Tu bloques db.write, quelqu’un ship db.patch.
Tu bloques email.send, quelqu’un ship email.send_bulk.
Pire : les wrappers.
L’agent appelle workflow.run("close_ticket") et ta « blocklist » ne voit jamais le vrai side effect.
3) Le default-allow fait grossir la blast radius en silence
Si ta policy dit « tout est autorisé sauf… », tu expédies une expansion de permissions par défaut. Chaque nouvel outil devient un incident en attente.
4) Les allowlists forcent une décision explicite
Les allowlists sont pénibles. Tant mieux. Elles t’obligent à dire : « oui, l’agent peut appeler cet outil, dans ces conditions ».
Exemple d’implémentation (code réel)
Un petit évaluateur de policy :
- default-deny
- deny list optionnelle pour l’Incident Mode (frein d’urgence)
- « write tools → approbation »
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";
}Incident réel (avec chiffres)
On a vu une équipe shipper un agent avec une policy : « on bloque les outils dangereux ».
Puis ils ont ajouté un outil : ticket.close_bulk.
Il n’était pas dans la deny list.
L’agent l’a utilisé parce que c’était le chemin le plus court vers « resolve ».
Impact :
- ~200 tickets fermés à tort
- ~5 heures ingénieur à rouvrir, expliquer, patcher
- l’agent a été désactivé une semaine : plus personne ne lui faisait confiance
Fix :
- allowlist default-deny
- write tools derrière approvals
- deny list réservée à l’Incident Mode (temporaire)
Les blocklists, c’est bien comme frein. C’est mauvais comme volant.
Compromis
- Les allowlists ralentissent le « just ship it ». En prod, c’est un feature.
- Il te faut du tooling pour gérer l’allowlist (ne la hardcode pas à 12 endroits).
- Les gens vont tenter de contourner via des wrappers. Ne les laisse pas.
Quand NE PAS l’utiliser
- En proto local, tu peux être plus permissif — mais ne remonte pas ça en prod.
- Même en read-only, une petite allowlist évite des surprises.
- Si ton « tool » est un RPC générique qui peut tout faire, corrige le tool d’abord : découpe par capacité.
Checklist (copier-coller)
- [ ] Allowlist default-deny (noms d’outils explicites)
- [ ] Outils séparés par capacité (read vs write)
- [ ] Approvals pour les writes irréversibles
- [ ] Deny list uniquement en Incident Mode (temporaire)
- [ ] Log des denies (c’est un signal)
- [ ] Pas d’outils génériques « do anything »
Config par défaut sûre (JSON/YAML)
policy:
default: "deny"
allow: ["search.read", "kb.read", "http.get"]
require_approval_for_writes: true
incident_mode:
deny: ["browser.run"] # frein temporaire
logging:
log_denies: true
FAQ (3–5)
Utilisé par les patterns
Pannes associées
Gouvernance requise
Q: Les blocklists servent à quelque chose ?
A: Oui : Incident Mode, désactivations temporaires. C’est un frein d’urgence, pas un modèle de permissions.
Q: Je peux utiliser des wildcards dans l’allowlist ?
A: Avec prudence. Les wildcards glissent vite vers le default-allow. Si tu en as besoin, garde-les étroites et review régulièrement.
Q: Et les tools type ‘workflow.run’ ?
A: Ils cachent les side effects. Préfère des tools explicites pour que la policy puisse raisonner.
Q: C’est quoi la policy minimale safe ?
A: Default-deny allowlist + tools read-only. Ajoute les writes plus tard, derrière approvals.
Pages liées (3–6 liens)
- 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