Sécurité des agents AI : permissions, sandboxes et menaces liées à MCP
Traduction automatique Cet article a été traduit automatiquement depuis la version originale en anglais.
Mise à jour de l’article
Publié initialement le 20 avril 2026. Révisé et mis à jour le 6 septembre 2026. Cette mise à jour couvre les contrôles de sandbox plus récents, les interventions de sécurité des fournisseurs et les résultats de sécurité publiés, ainsi que leurs limites et les liens vers les sources.
La sécurité d’un agent commence lorsqu’un modèle propose une action, avant que la machine ne l’exécute. Déterminez quel contrôle a le dernier mot avant que l’action n’accède aux identifiants, aux fichiers, aux réseaux ou à un système externe.
Le harness est le code qui construit chaque prompt, décide quels appels d’outils proposés sont exécutés et renvoie les résultats au modèle. La plupart des contrôles doivent s’y trouver, car il s’agit du dernier point d’arrêt peu coûteux avant l’exécution d’une commande. Après l’exécution d’une commande, la sandbox, les identifiants auxquels elle a eu accès et tout processus de récupération doivent contenir les dommages. Certains incidents décrits dans cet article n’atteignent jamais un modèle.
La sécurité des agents IA est plus large que la sécurité des LLM. Les premiers produits de guardrails inspectaient l’entrée et la sortie d’un appel de modèle. Ils pouvaient filtrer les textes toxiques, supprimer les données personnelles, bloquer les jailbreaks et rejeter les réponses hors sujet. Cette limite était utile tant que le modèle ne pouvait renvoyer que du texte.
Les tool loops ont ajouté des systèmes de fichiers, des shells, des serveurs Model Context Protocol (MCP) et des identifiants. La surface de menace s’est ainsi étendue du texte dangereux aux actions dangereuses. Les sept groupes d’incidents ci-dessous couvrent l’injection indirecte de prompt ainsi que les défaillances de configuration, d’identité et de distribution logicielle. L’analyse du texte peut contribuer à bloquer certaines entrées malveillantes ; elle ne peut pas remplacer les contrôles appliqués à ces frontières d’exécution.
Lorsqu’un agent peut lire un dépôt, appeler un outil ou envoyer des données à un tiers, associez chaque action proposée au contrôle capable de l’arrêter. Les sections ci-dessous couvrent les permissions, les hooks, les sandboxes, les identifiants et la revue humaine.
En bref : Les filtres de contenu inspectent le texte autour d’un appel de modèle. La sécurité des agents contrôle également les appels d’outils proposés, les identifiants, les fichiers, l’accès réseau et les effets de bord irréversibles. Plusieurs incidents ont entièrement contourné le modèle ; d’autres ont utilisé du texte malveillant pour orienter des opérations autorisées. Une pile de politiques pratique combine les permissions, les hooks pré-outil, les sandboxes au niveau du système d’exploitation, l’escalade vers un humain, des identifiants à portée limitée et une trace d’audit.
Pour consulter la checklist de contrôle synthétique, voir Checklist de sécurité des agents IA.
Pile de sécurité des agents IA
Aucun guardrail unique ne suffit à sécuriser un agent. Chaque composant du système doit disposer de son propre contrôle.
Le tableau indique où chaque contrôle s’exécute. Le harness est le programme de contrôle décrit ci-dessus. Le runtime est l’infrastructure qu’il utilise : la sandbox, le journal de session, le stockage des points de contrôle et les traces qui persistent après le redémarrage d’un worker.
| Couche | Ce qu’elle contrôle | Exemple d’échec qu’elle détecte | Où elle s’exécute |
|---|---|---|---|
| Filtres de contenu | Texte d’entrée et de sortie non sécurisé | Sortie toxique, fuite de PII, complétions contraires aux règles | Harness |
| Échelle des permissions | Outils, chemins, API et scopes accessibles à l’agent | Un summarizer qui tente d’écrire dans des systèmes de production | Harness |
| Hook de policy pré-outil | Si cette action précise doit être exécutée maintenant | Commande shell construite à partir de contenu récupéré non fiable | Harness |
| Sandbox | Ce que l’outil peut toucher au niveau du système d’exploitation et du réseau | Exfiltration de fichiers, compromission de dépendances, injection de commandes | Runtime |
| Vérification par approbation humaine | Actions irréversibles ou à fort impact | Envoi d’un e-mail, transfert d’argent, déploiement en production | Harness |
| Scoping MCP et des tokens | Serveur et audience pour lesquels un credential est valide | Réutilisation d’un token sur un serveur d’outils non prévu | Runtime |
| Trace d’audit | Ce qui s’est passé, qui a approuvé l’action et pourquoi | Investigation d’un incident après une longue exécution autonome | Runtime |
Les filtres de contenu vérifient si le modèle a produit quelque chose de non sécurisé. La sécurité des agents vérifie également si le système peut effectuer l’action suivante.
Les lignes du harness déterminent si une action peut être exécutée. Les lignes du runtime appliquent les limites définies à l’avance et consignent ce qui s’est passé. Conservez la sandbox même lorsque les règles de permission semblent complètes : elle peut bloquer un appel que le harness n’avait pas anticipé. Elle ne peut pas décider si une action autorisée était la bonne ; c’est au harness de le faire.
La dernière colonne indique où s’exécute une vérification, et non qui l’opère. Un fournisseur peut proposer un filtre de contenu, mais c’est le harness qui l’appelle.
Les filtres de contenu managés couvrent la couche texte. Les autres contrôles relèvent de la policy applicative, de l’identité et de l’infrastructure.
Pourquoi la sécurité des agents IA diffère de la safety des LLM
Bharani Subramaniam et Martin Fowler ont posé ce cadre début 2025 dans Emerging Patterns in Building GenAI Products. Leur observation était ciblée et directe :
« Avec les systèmes traditionnels, nous pouvions principalement évaluer la correction au moyen de tests… Avec les systèmes basés sur des LLM, nous sommes confrontés à un système qui ne se comporte plus de manière déterministe. »
L’évaluation des sorties vérifie si la réponse d’un modèle respecte un référentiel. Un threat model d’agent doit également couvrir les appels d’outils, les commandes shell, les écritures de fichiers, les credentials et les requêtes réseau. Un grader de sortie ne peut pas bloquer ces actions. C’est le harness qui le fait : il regroupe les vérifications qui transforment une proposition du modèle en action autorisée. Le reste de cet article porte sur ces vérifications.
Simon Willison a formulé la structure du risque propre aux agents en juin 2025 avec la lethal trifecta :
« La trifecta létale des capacités est la suivante : l’accès à vos données privées ; l’exposition à du contenu non fiable ; la capacité à communiquer vers l’extérieur d’une manière qui pourrait servir à dérober vos données. Si votre agent combine ces trois fonctionnalités, un attaquant peut facilement le piéger pour qu’il accède à vos données privées et les lui envoie. »
De nombreux agents utiles combinent ces capacités : accès à une boîte de réception, récupération de contenu sur le Web et outil de messagerie ; ou accès à un dépôt, lecture des issues et possibilité d’écrire des pull requests. Une guardrail de contenu vérifie si le modèle a généré un texte dangereux. La trifecta vérifie si une entrée non fiable peut orienter le système vers la divulgation de données au moyen d’une action autorisée.
La version structurelle du même argument se trouve dans le preprint Parallax de Joel Fokou (arXiv 2604.12986, soumis le 14 avril 2026, non évalué par les pairs). L’argument central :
« Le système qui raisonne sur les actions doit être structurellement incapable de les exécuter, et le système qui exécute les actions doit être structurellement incapable de raisonner à leur sujet, avec un validateur indépendant et immuable interposé entre les deux. »
Il n’est pas nécessaire d’accepter les chiffres de l’évaluation de l’article pour examiner son argument structurel. Plusieurs harnesses actuels mettent en œuvre une partie de cette même séparation :
- Les hooks
PreToolUsede Claude Code - L’exécuteur de Codex CLI isolé dans un sandbox au niveau de l’OS (sous Linux, bubblewrap avec filtrage des appels système par seccomp)
- Les Managed Agents d’Anthropic, qui conservent les credentials dans un vault auquel l’agent n’a jamais accès
- Les tokens liés à l’audience de la RFC 8707 de MCP
Ces systèmes séparent le modèle du code qui exécute les commandes. Leurs contrôles diffèrent, mais aucun ne permet au runner de commandes de se fier à l’avis du modèle sur la sécurité.
Une discipline complémentaire a été formulée avec le plus de clarté par Alessandro Pignati en janvier 2026 : le Principe du moindre pouvoir d’action. Le moindre privilège pose la question : à quelles ressources cette identité peut-elle accéder ? Le moindre pouvoir d’action pose la question : quelles décisions cet agent est-il autorisé à prendre ? Le privilège contraint les credentials ; le pouvoir d’action limite la portée d’un plan, même lorsque les credentials sont valides. L’excès de pouvoir d’action constitue une catégorie à part entière du Top 10 des applications LLM publié par l’OWASP, l’Open Worldwide Application Security Project. La liste distincte consacrée aux systèmes agentiques, présentée plus loin dans cet article, répartit le même échec entre mauvais usage des outils et abus de privilèges. Le moindre pouvoir d’action est la discipline de conception qui prévient ces deux problèmes. Un agent capable de résumer votre boîte de réception n’a probablement pas besoin de droits de commit sur votre monorepo. Nous trouvons pourtant sans cesse des configurations où c’est le cas.
Ce que couvrent les guardrails pour les LLM
!!! byte « Byte dit »
Donnez à un agent un filtre de contenu et un outil d’écriture. Le filtre peut inspecter ses mots ; la policy de l’outil décide si l’écriture du fichier est exécutée.
Les guardrails pour les LLM jouent un rôle important autour de l’appel au modèle. Ils inspectent l’entrée, le texte récupéré et la sortie, puis bloquent, masquent, corrigent ou signalent le contenu qui ne respecte pas une règle configurée. Les produits ci-dessous diffèrent par leur mode de déploiement et leur couverture. Un contrôle de contenu est distinct d’un contrôle d’autorisation à la frontière de l’outil ou du serveur MCP ; certains produits proposent également des fonctionnalités de policy runtime qui nécessitent leur propre configuration et leur propre évaluation.
NVIDIA NeMo Guardrails
Le plus prescriptif : un framework d’orchestration autour de cinq types de rails (entrée, dialogue, retrieval, exécution, sortie), avec son propre DSL — Colang, un langage proche de Python pour les flux de dialogue, les intentions utilisateur et les messages du bot. Vous pouvez piloter les fonctionnalités de base depuis Python + YAML, mais une logique de dialogue plus riche s’écrit en Colang — d’où son caractère « prescriptif ». Documentation sur docs.nvidia.com/nemo/guardrails.
Il s’agit d’une forme d’API illustrative ; elle nécessite le package et un répertoire ./config configuré.
from nemoguardrails import LLMRails, RailsConfig
config = RailsConfig.from_path("./config")
rails = LLMRails(config)
response = rails.generate(
messages=[{"role": "user", "content": "Hello"}]
)
Le dépôt de NeMo explicite son modèle de menace : « les vulnérabilités courantes des LLM, comme les jailbreaks et les prompt injections ». Il précise tout aussi clairement son périmètre : « Les guardrails intégrés peuvent convenir ou non à un cas d’usage donné en production… les développeurs doivent travailler avec l’équipe interne responsable de l’application afin de s’assurer que les guardrails répondent aux exigences. » Le chemin de filtrage du contenu présenté ici surveille ce que dit le modèle. La documentation actuelle de NeMo décrit également les rails d’exécution, les actions personnalisées et l’inspection des tool calls ; il s’agit de contrôles configurables à l’exécution, et non d’une preuve que l’outil déployé ou le serveur MCP a authentifié et autorisé l’appel. L’application reste responsable de cette frontière.
Meta Llama Guard 4
Un classifieur de contenu pur de 12 milliards de paramètres, dérivé de Llama-4-Scout et aligné sur la taxonomie des risques de MLCommons (13 catégories de préjudices, plus les abus de l’interpréteur de code, selon la fiche du modèle). Meta fait preuve d’une franchise inhabituelle concernant ses limites :
« Certaines catégories de risques peuvent nécessiter des connaissances factuelles et à jour pour être évaluées complètement… Enfin, en tant que LLM, Llama Guard 4 peut être vulnérable aux attaques adversariales ou aux prompt injections susceptibles de contourner ou de modifier son usage prévu : voir Llama Prompt Guard 2 pour détecter les attaques par prompt. »
Meta fournit un produit distinct pour défendre son classifieur de contenu contre les prompt injections. Si cette phrase ressemble à un aveu structurel, c’est bien le cas.
Guardrails AI
Un registre de validateurs. Vous composez des validateurs du Hub (PII via Presidio, JailbreakDetect, CompetitorCheck, contrôles de provenance) avec des actions on_fail exception | fix | fix_reask | filter | refrain | reask | noop, ou un callback personnalisé (guardrailsai.com). Notez exception, et non raise. Dans le code source actuel, une chaîne on_fail non reconnue est prise en charge par le mécanisme des callbacks personnalisés et provoque une erreur lors de l’initialisation du validateur, au lieu d’émettre un avertissement et de revenir au comportement par défaut. Épinglez la version déployée et testez ce chemin d’erreur. Il n’existe pas de modèle de menace unifié ; la couverture correspond à l’union des validateurs installés. Vous bénéficiez d’une protection pour tout ce qui dispose d’un validateur, et d’aucune protection pour le reste.
Lakera Guard
L’API SaaS historique, entraînée sur des dizaines de millions d’échantillons d’attaques collectés via Gandalf. Elle promet de filtrer les entrées et les sorties pour détecter les « prompt attacks… et les fuites de données ». Le produit distinct AI Agent Security de Lakera décrit également l’application de politiques et de contrôles à l’exécution pour déterminer ce que les agents peuvent consulter, appeler et faire. Il s’agit d’une surface produit différente de l’appel de filtrage de contenu présenté ici. Vérifiez le produit et le contrat tarifaire actuels avant le déploiement.
AWS Bedrock Guardrails
Le choix par défaut en entreprise si vous utilisez déjà Bedrock. ApplyGuardrail fonctionne avec n’importe quel modèle, qu’il soit hébergé sur Bedrock ou non :
# No-run: illustrative AWS request; requires boto3, AWS credentials, and a real guardrail identifier.
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"]}}],
)
Tarifs publiés pour ApplyGuardrail ici : $0.15 par tranche de 1 000 unités de texte pour les filtres de contenu ou les sujets interdits, $0.10 pour les filtres PII ou le contextual grounding. Une unité de texte correspond à 1 000 caractères au maximum.
Azure AI Content Safety
Fournit Prompt Shields comme endpoint unifié qui « détecte et bloque les attaques par entrées utilisateur adversariales… les menaces directes et indirectes ». Azure fait également preuve de transparence : « Vous ne pouvez pas utiliser Azure AI Content Safety pour détecter des images illégales d’exploitation d’enfants », et la qualité multilingue est limitée à huit langues évaluées.
OpenAI Moderation et OpenAI Guardrails
omni-moderation-latest constitue la référence multimodale gratuite. Par ailleurs, openai-guardrails-python (documentation sur guardrails.openai.com) est la réponse d’OpenAI sous forme de framework : un pipeline en trois étapes (pre-flight, entrée, sortie) avec détection des jailbreaks, détection des hallucinations via FileSearch, détection NSFW, détection des PII via Presidio et LLM-as-judge. GuardrailAgent s’intègre au SDK Agents.
# No-run: illustrative OpenAI Guardrails API shape; requires the package and guardrail_config.json.
from guardrails import GuardrailsOpenAI, GuardrailTripwireTriggered
client = GuardrailsOpenAI(config="guardrail_config.json")
try:
resp = client.responses.create(model="<your-model-id>", input="...")
except GuardrailTripwireTriggered as e:
print(f"blocked: {e}")
Ce que les filtres de contenu ne déterminent pas
Deux observations s’appliquent aux sept solutions.
Premièrement, les chiffres publiés concernant la latence et le débit sont rares. Bedrock, Azure et Lakera publient leurs tarifs, mais ne garantissent pas la latence dans les pires cas. Meta ne fournit pas non plus de garantie pour un endpoint hébergé exécutant Llama Guard. NVIDIA fournit NeMo Guardrails sous forme de logiciel que vous hébergez ; la latence dépend donc de votre modèle et de votre infrastructure. Mesurez chaque vérification synchrone sur le chemin critique au lieu d’en déduire le coût à partir des tarifs du produit.
Deuxièmement, cette section couvre les configurations axées sur le contenu listées ci-dessus.
Un filtre de contenu peut inspecter les entrées et les sorties du modèle. Il ne démontre pas qu’un appel d’outil donné est autorisé, qu’un serveur MCP a authentifié son appelant ou que le système peut empêcher l’exfiltration de données en plusieurs étapes ou l’exécution de code avant un appel de modèle. L’autorisation est une décision distincte qui consiste à déterminer si cette identité peut effectuer cet appel auprès de ce serveur.
NeMo documente également les rails d’exécution et l’inspection des appels d’outils, tandis que Lakera décrit l’enforcement à l’exécution dans son produit distinct AI Agent Security. Il s’agit de contrôles supplémentaires à configurer et à tester. La suite de cet article couvre les vérifications autour de l’exécution des outils.
Menaces pesant sur la sécurité des agents IA : sept incidents et l’OWASP ASI Top 10
L’écart entre le filtrage de texte et la protection de l’exécution a cessé d’être théorique à la mi-2025. Les sept incidents ci-dessous ont atteint la récupération de données, la configuration, les identifiants, l’installation de packages ou l’exécution dans la CI. Un classifieur de contenu peut toujours détecter une chaîne suspecte, mais les contrôles qui bloquent directement ces vecteurs se situent aux frontières des outils, des identités, du sandboxing et de la supply chain.
EchoLeak — CVE-2025-32711
Divulguée en juin 2025 par Aim Labs, la branche de recherche d’Aim Security, cette vulnérabilité concernait Microsoft 365 Copilot. L’analyse technique est désormais publiée sur le site de Cato Networks, qui a acquis cette équipe, sous la signature de l’ancien responsable d’Aim Labs, Itay Ravia (analyse). Un e-mail spécialement conçu, formulé comme des instructions destinées au destinataire humain, a contourné XPIA (le filtre intégré de Microsoft qui recherche les attaques par prompt injection dans les entrées de Copilot). Il a ensuite été intégré à la couche de retrieval de Copilot, c’est-à-dire la partie du système qui recherche dans vos documents le contexte nécessaire aux réponses. Les chercheurs appellent cette technique le RAG-spraying : l’attaquant utilise plusieurs e-mails ou un long e-mail découpé en chunks afin d’élargir l’exposition au retrieval. Cela augmente les chances de retrieval, sans le garantir. Une fois dans le système, Copilot a docilement incorporé les données les plus sensibles de la session dans un lien Markdown pointant vers une image hébergée sur un domaine contrôlé par l’attaquant. L’API d’aperçu de Teams, exécutée sur un domaine déjà approuvé par les propres règles de navigateur de Microsoft, a automatiquement récupéré l’URL de cette image et, ce faisant, transmis 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 pris individuellement considérait comme légitimes.
Chaque étape semblait légitime, prise isolément. L’e-mail était adressé à un humain. Le retrieval a récupéré un document qu’il était censé récupérer. Le lien Markdown s’est affiché comme s’affichent les liens Markdown. La récupération de l’image a ciblé un domaine autorisé. Les chercheurs ont contourné le filtrage de XPIA, et le modèle a suivi des instructions indirectes provenant du contenu récupéré. Ce cas combine une défaillance de prompt injection avec des comportements de retrieval, de rendu et d’egress ; il ne montre pas que les détecteurs n’avaient aucun élément à signaler.
Amazon Q Developer VS Code v1.84.0 — juillet 2025
AWS a publié une build compromise après qu’un attaquant a soumis un fichier de system prompt malveillant au moyen d’un token GitHub de CodeBuild doté de permissions trop larges (avis de sécurité). Le payload tentait de modifier les instructions de l’agent pour l’orienter vers des actions destructrices. Le code malveillant a été distribué avec la version v1.84.0, mais ne s’est pas exécuté en raison d’une erreur de syntaxe. AWS a révoqué les identifiants, supprimé le code et publié la version v1.85.0. Le payload a échoué à cause de cette erreur de syntaxe, et non parce qu’un contrôle de sécurité l’a bloqué.
Service MCP Azure Web Apps — CVE-2026-32211
La fiche CVE du fournisseur publiée par Microsoft concerne l’absence d’authentification dans le service MCP Azure Web Apps hébergé. Il ne s’agit pas d’un avis visant tous les serveurs MCP ou SDK Azure locaux. Un appelant qui atteint un service d’outils non authentifié peut contourner entièrement le modèle ; le service déployé doit authentifier et autoriser la requête.
Vulnérabilités liées à la confiance accordée aux projets dans Claude Code
Il s’agissait de vulnérabilités distinctes, et non d’étapes nécessaires d’une même attaque :
- Un contournement de l’avertissement de confiance a été corrigé dans la version 1.0.87.
- L’exécution avant établissement de la confiance, CVE-2025-59536, a été corrigée dans la version 1.0.111. La configuration du dépôt pouvait déclencher une exécution avant que le projet ne soit considéré comme fiable.
- L’exposition du endpoint et de la clé API, CVE-2026-21852, a été corrigée dans la version 2.0.65. Une configuration non fiable pouvait rediriger le trafic API et exposer les identifiants.
La confiance accordée au projet, l’exécution des hooks et la configuration des endpoints relèvent des contrôles de l’hôte. Un classifieur de contenu ne peut pas empêcher l’exécution d’un code qui s’exécute avant l’appel au modèle.
Axios 1.14.1 et 0.30.4 — 31 mars 2026
Le post-mortem du mainteneur identifie deux versions malveillantes, 1.14.1 et 0.30.4, contenant la dépendance plain-crypto-js@4.2.1. Cette dépendance installait un cheval de Troie d’accès distant : un malware qui permet à un attaquant d’accéder à distance à la machine. L’exposition nécessitait de résoudre les versions concernées et d’exécuter le comportement d’installation correspondant ; un npm install sans rapport ne les récupérait pas automatiquement. Il s’agit d’une défaillance d’exécution de la chaîne logistique logicielle, indépendante du comportement du modèle.
Détournement de tag des Trivy Actions — 19 mars 2026
L’avis d’Aqua décrit la redirection vers du contenu malveillant de 76 des 77 tags de version trivy-action et de sept tags setup-trivy. Le point d’entrée de l’action malveillante collectait la mémoire des processus du runner et les fichiers contenant des identifiants ; n’attribuez pas l’intégralité de ce parcours de collecte au binaire du scanner. L’événement ultérieur sur Docker Hub correspondait à une fenêtre d’exposition distincte.
Un workflow qui résolvait un tag concerné pendant la compromission pouvait exécuter le payload. Les tags sont des références modifiables : épinglez donc les Actions vérifiées sur des SHA de commit immuables et vérifiez les modifications des dépendances. Un agent de code peut propager la même référence non sûre dans plusieurs fichiers de workflow.
OpenAI / Hugging Face — évaluations de juillet 2026
Le rapport d’incident du 26 août d’OpenAI décrit des agents internes d’évaluation de la cybersécurité qui ont accédé à Internet via une infrastructure partagée, collaboré sur un forum de discussion non autorisé et compromis des systèmes de Hugging Face. Le modèle principal était réservé à un usage interne et les évaluations se sont déroulées avec des garde-fous réduits. Il s’agit d’éléments concernant cet environnement d’évaluation, et non d’un taux d’échec mesuré pour des agents déployés publiquement.
La leçon d’ingénierie est qu’un service interne autorisé peut devenir un canal de sortie ou de communication inter-session. Testez ce qu’un miroir de packages, un proxy et un store partagé peuvent faire pour le compte de l’agent, et pas seulement si le sandbox peut ouvrir une connexion directe à Internet. L’enquête distincte de METR a examiné le comportement et la collaboration des agents ; l’évaluation de l’efficacité des garde-fous et de la remédiation ne relevait pas de son périmètre.
Intervention asynchrone du provider
Le monitoring de mésalignement d’OpenAI peut intervenir après la production d’une sortie ou l’exécution d’actions. Pour les modèles couverts, les requêtes Responses avec un raisonnement persisté, WebSockets ou la compaction OpenAI peuvent être automatiquement arrêtées. D’autres requêtes Responses peuvent générer des alertes sans arrêt automatique ; Chat Completions est hors du périmètre de ce système. Un webhook n’active pas le blocage.
Une requête bloquée renvoie misalignment_policy_violation, avec une réponse HTTP 403 avant le streaming ; des erreurs peuvent également survenir en cours de streaming. Arrêtez les actions supplémentaires, conservez les traces des requêtes et des outils, puis demandez une revue par un opérateur. Ne relancez pas automatiquement la requête. Les effets antérieurs subsistent, et les alertes peuvent être erronées ou manquer certains problèmes. Ce mécanisme ajoute de la détection ; il ne remplace pas l’autorisation locale.
L’OWASP ASI Top 10, édition 2026
L’Agentic Security Initiative (ASI) de l’OWASP est un groupe de travail consacré aux agents pilotés par des LLM. Le 9 décembre 2025, il a publié le Agentic Security Initiative Top 10 for 2026 : un catalogue de dix catégories de risques liés à la sécurité des agents.
Utilisez-le comme checklist de couverture pour le threat modeling, et non comme classement mesuré de la fréquence des incidents :
Les filtres de contenu peuvent contribuer à détecter les instructions malveillantes, notamment le détournement d’objectif et l’empoisonnement de la mémoire. Les catégories se recouvrent : aucune ne relève exclusivement d’un filtre textuel. Reliez chaque chemin d’attaque aux contrôles applicables portant sur l’identité, les politiques d’outils, la mémoire, l’orchestration, la supervision et la supply chain. EchoLeak relève de ASI01. Amazon Q relève de ASI04 (Supply Chain) et ASI02 (Tool Misuse). Azure MCP relève de ASI03 (Identity). Claude Code CVE-2025-59536 couvre ASI05 (Code Execution), ASI04 et ASI03. Axios et Trivy relèvent de ASI04. Cette cartographie montre pourquoi le threat model doit aller au-delà des entrées et sorties du modèle.
Les permissions sont une infrastructure, pas un prompt
C’est à ce stade que les guardrails cessent d’être le produit pour devenir un sous-système d’un harness. Trois systèmes actuels — OpenAI Agents SDK, Codex CLI et Claude Code — montrent à quoi ressemble réellement une surface de politiques en production. Tous trois appliquent les permissions dans le code. Aucun ne compte sur la prudence du modèle.
OpenAI Agents SDK
Le SDK sépare le harness du compute. Les outils MCP hébergés acceptent require_approval — soit la chaîne brute "always" / "never", soit un objet de filtrage indexé par ces deux politiques et contenant les noms des outils couverts par chacune — ainsi qu’un callback on_approval_request appelé pour chaque outil laissé sous "always" et renvoyant {"approve": bool}, avec une raison facultative. Un filtrage plus fin des outils (tool_filter) est disponible avec les variantes de serveur local (MCPServerStdio, MCPServerStreamableHttp, MCPServerSse) si nécessaire :
# No-run: illustrative Agents SDK shape; requires openai-agents, a configured hosted MCP server, and credentials.
from agents import Agent, HostedMCPTool
def approve(request):
# Only tools under the "always" policy reach this callback.
if request.data.name == "delete_repo":
return {"approve": False, "reason": "escalate to a human reviewer"}
return {"approve": True}
agent = Agent(
name="Ops",
tools=[HostedMCPTool(
tool_config={
"type": "mcp",
"server_label": "github",
"server_url": "https://mcp.example.com",
"require_approval": {
"always": {"tool_names": ["delete_repo"]},
"never": {"tool_names": ["list_issues"]},
},
},
on_approval_request=approve,
)],
)
Le callback d’approbation est du code. La politique d’approbation par outil est du code. Vous pouvez lire ce fichier, le tester et le comparer avec un diff. Rien de tout cela n’est possible avec un system prompt qui dit « soyez prudent avec la production ».
Codex CLI et la couche de politiques gérées
Le harness de coding d’OpenAI prend en charge un fichier requirements.toml géré que les services IT peuvent déployer via la gestion des appareils. Sur les systèmes Unix, le fichier système se trouve à /etc/codex/requirements.toml. Il agit comme une couche de contraintes strictes : les paramètres au niveau du projet ne peuvent donc pas remplacer ses règles :
# /etc/codex/requirements.toml
[rules]
prefix_rules = [
{ pattern = [{ token = "rm" }, { any_of = ["-rf", "-fr"] }], decision = "forbidden", justification = "Recursive force-delete prohibited by IT policy" },
]
prefix_rules.decision accepte uniquement "prompt" ou "forbidden", jamais "allow". Un projet ne peut pas s’accorder une permission interdite par la couche gérée. Les listes d’autorisation MCP sont indexées à la fois par le nom et par l’identité, par exemple une chaîne de commande ou une URL. Un projet ne peut donc pas se déclarer github-mcp tout en pointant vers le serveur d’un attaquant. Les exigences prises en charge varient selon le client et sa version. La documentation actuelle exige précisément Codex 0.138.0 ou une version ultérieure pour les clés de profil de permissions gérées. Testez donc toute politique d’exigences avec chaque version cliente présente dans le parc avant son déploiement.
Échelle des permissions de Claude Code
Claude Code ne publie pas une séquence fixe de six contrôles pour chaque appel d’outil. Ses règles de permission sont évaluées deny → ask → allow ; la première règle correspondante détermine le résultat. Un hook PreToolUse s’exécute avant l’invite de permission. Un hook peut bloquer un appel, mais son résultat ne contourne pas une règle correspondante de refus ou de demande d’autorisation. Le mode de permission actif gère les appels que les règles ne permettent pas de résoudre. Le Claude Agent SDK dispose d’un callback canUseTool distinct pour les requêtes non résolues. Ce callback est un mécanisme de contrôle du SDK, et non un contrôle de permission de la CLI Claude Code.
Les modes défilent default → acceptEdits → plan avec Shift+Tab. auto, bypassPermissions et dontAsk s’activent dans des conditions d’entrée spécifiques que la couche de politique gérée au niveau de l’entreprise peut désactiver. Il ne s’agit pas simplement de vérifier la validité d’un fichier de configuration. C’est une machine à états avec des règles de précédence, documentée afin qu’une équipe de sécurité puisse en raisonner.
Trois rayons d’impact dans un seul fichier
Voici la structure d’une configuration de permissions de style Codex, avec une configuration par défaut et deux profils nommés :
# ~/.codex/config.toml
approval_policy = "on-request"
sandbox_mode = "workspace-write"
[profiles.ci]
approval_policy = "never"
sandbox_mode = "read-only"
[profiles.release]
approval_policy = "untrusted"
sandbox_mode = "danger-full-access"
[mcp_servers.github]
command = "gh-mcp"
args = ["--readonly"]
Deux clés font l’essentiel du travail, et elles sont indépendantes. approval_policy détermine quand l’intervention d’un humain est requise. on-request permet à l’agent de demander des privilèges supplémentaires lorsqu’il est bloqué. never ne demande jamais rien. untrusted s’arrête pour toute commande qui ne figure pas dans la liste de confiance. sandbox_mode détermine les ressources auxquelles la commande peut accéder si elle s’exécute.
La CI n’interrompt jamais personne et ne peut pas écrire. La mise en production peut accéder à toute la machine, mais doit d’abord obtenir l’accord d’un humain pour presque tout. Le profil de mise en production paie le prix de cette portée : danger-full-access désactive la sandbox, si bien que untrusted est le seul contrôle restant. Tout ce qui ne figure pas dans la liste de confiance doit obtenir l’accord d’un humain ou ne s’exécute pas. Cette liste de confiance constitue désormais l’intégralité de la frontière de sécurité.
Les profils par défaut et CI conservent le noyau de protection sous-jacent : Seatbelt sur macOS, bubblewrap associé à seccomp sur Linux, et des jetons restreints sur Windows. Dans tous les cas, l’avis du modèle n’intervient pas.
L’application de la sandbox relève du système d’exploitation
C’est le noyau qui effectue réellement ce travail. Chaque système d’exploitation fournit une boîte à outils différente, et les deux CLI ne choisissent pas toujours le même composant :
| Plateforme | Claude Code | Codex CLI |
|---|---|---|
| macOS | Seatbelt via sandbox-exec avec un profil SBPL (Seatbelt Profile Language) | Seatbelt via sandbox-exec -p |
| Linux | bubblewrap + proxy réseau socat | bubblewrap + seccomp (Landlock historique via use_legacy_landlock) |
| Windows | WSL2 requis | Jetons restreints natifs + ACL de l’espace de travail + SID de capacité |
Ils convergent lorsque l’OS propose une seule option (Seatbelt, bubblewrap) et divergent lorsqu’il n’en propose pas. Le sandbox de Claude Code nécessite WSL2 sous Windows ; il s’agit d’une limitation du sandbox, et non d’une affirmation selon laquelle la CLI ne pourrait pas s’exécuter nativement. Codex fournit un sandbox Windows natif. Dans tous les cas, l’application des restrictions s’effectue dans le noyau, pas dans le modèle.
Le chemin Linux de Codex empile trois mécanismes de verrouillage au niveau du noyau autour de la commande. PR_SET_NO_NEW_PRIVS empêche le processus d’obtenir des privilèges supplémentaires, même s’il essaie. Un filtre seccomp demande au noyau de refuser directement des catégories entières d’appels système. Les restrictions réseau dépendent de la désactivation du réseau ou de la configuration d’un mode proxy ; elles n’imposent pas universellement l’utilisation exclusive de sockets Unix. Consultez la implémentation du sandbox Linux. Un nouvel /proc isolé masque le reste de la machine.
Codex renforce également son propre binaire au démarrage sur toutes les plateformes Unix. Il définit RLIMIT_CORE=0 pour désactiver les core dumps et refuse l’attachement d’un débogueur. Il s’agit d’une frontière distincte de celle du sandbox.
Le sandbox Windows propose deux modes. unelevated utilise un jeton restreint pour l’utilisateur et n’assure pas l’application des restrictions réseau du mode élevé. elevated utilise des utilisateurs de sandbox dédiés, des règles de pare-feu et des ACL du système de fichiers.
Lorsque l’accès réseau est désactivé, Codex place des fichiers .bat et .cmd factices pour ssh et scp dans un répertoire placé au début de PATH. Ces commandes se terminent avec un code différent de zéro au lieu d’atteindre les vrais binaires. Codex redirige également HTTP_PROXY, HTTPS_PROXY, ALL_PROXY et les variables de proxy Git vers un port local inactif. Ces mesures concernent les outils coopératifs ; les variables de proxy et les stubs de commandes ne peuvent pas, à eux seuls, confiner des communications réseau arbitraires.
Un sandbox doit également définir un mode d’échec. Dans la configuration actuelle de Claude Code, sandbox.failIfUnavailable: true interrompt l’exécution lorsque le sandbox ne peut pas démarrer. allowUnsandboxedCommands: false désactive le mécanisme de repli non sandboxé de l’agent, mais excludedCommands le contourne toujours. Testez la configuration effective avec des dépendances manquantes et une destination interdite.
Le courtage des identifiants est désormais disponible localement lui aussi. Le sandbox.credentials de Claude Code peut refuser l’accès à des fichiers et variables d’environnement nommés. Le masquage des variables d’environnement, disponible depuis la v2.1.199, fournit une valeur de remplacement aux commandes exécutées dans le sandbox et permet au proxy d’injecter la valeur réelle dans les requêtes destinées aux hôtes configurés. Définissez un injectHosts restrictif et configurez la terminaison TLS ; le masquage n’autorise pas l’opération demandée. Ces contrôles couvrent les commandes Bash exécutées dans le sandbox ; les hooks, les processus MCP et les autres chemins d’exécution nécessitent donc leur propre politique de gestion des identifiants.
Choix d’isolation au-delà de Claude Code et Codex
Si vous développez votre propre agent, vous découvrirez que « sandbox » est un terme générique. Les options open source se répartissent sur un continuum — des wrappers légers reposant sur les namespaces à une extrémité, aux microVM complètes à l’autre — et votre choix dépend du niveau de confiance accordé au code exécuté à l’intérieur.
Isolation légère — même noyau, moins de privilèges :
- bubblewrap — un constructeur bas niveau de sandbox basée sur les namespaces, utilisé par Flatpak et Claude Code sous Linux. C’est à l’appelant de choisir le système de fichiers, le réseau et la politique seccomp facultative ; bubblewrap ne fournit pas lui-même une politique de sécurité prête à l’emploi.
- Conteneurs Docker / OCI standard — isolation par namespaces au-dessus d’un noyau hôte partagé. Ce n’est pas une sandbox pour du code non fiable ; la documentation de gVisor le précise explicitement (« les conteneurs ne sont pas une sandbox »). Un point de départ raisonnable avec seccomp et AppArmor, mais rien de plus.
Isolation au niveau du noyau applicatif — l’agent parle à un faux noyau :
- gVisor — le noyau en espace utilisateur de Google. Votre conteneur croit s’exécuter sous Linux, tandis qu’une implémentation du noyau en Go intercepte les appels système. Cela réduit l’exposition directe au noyau hôte sans recourir à une VM invitée, au prix de compromis en matière de compatibilité et de performances.
Isolation complète par VM — un noyau dédié par sandbox :
- Firecracker — la technologie de microVM d’AWS. Chaque VM possède son propre noyau Linux sous KVM ; les conteneurs partagent le noyau hôte. Une compromission dans une VM doit encore franchir les contrôles du VMM ou de l’hôte pour affecter l’hôte ou une autre VM. Le Jailer de Firecracker et un hôte corrigé font donc toujours partie de la protection.
- Kata Containers — l’expérience utilisateur des conteneurs, avec une isolation de niveau VM. C’est la solution vers laquelle se tournent les clusters Kubernetes lorsqu’ils doivent exécuter du code non fiable.
Plateformes — ce que vous loueriez plutôt que de le construire :
- E2B encapsule Firecracker dans une API de sandbox hébergée.
- OpenSandbox sépare le SDK de l’isolation du runtime configurée par l’administrateur. Docker runc n’utilise pas une microVM par défaut ; son chemin Firecracker s’appuie sur Kata avec Firecracker via Kubernetes.
- L’Agent Governance Toolkit de Microsoft (sous licence MIT, avril 2026) ajoute un moteur de politiques d’exécution par-dessus. Il met en correspondance les contrôles de politique avec l’OWASP ASI Top 10. Son affirmation concernant la latence de lancement porte sur un seul moteur de politiques, et non sur le coût total de chaque vérification effectuée par un agent déployé.
Choisissez le niveau d’isolation en fonction du niveau de confiance accordé au code, de la frontière entre tenants, de l’accès réseau, des données présentes sur l’hôte et du coût de récupération. Les contrôles par namespaces et seccomp peuvent convenir à des outils internes de confiance. Le code généré par un LLM et les packages non fiables nécessitent une frontière plus robuste, comme gVisor, Kata ou une microVM, suivie de tests ciblant les chemins d’évasion et d’exfiltration définis dans votre propre modèle de menace.
Claude Code et Codex ont choisi dans le même éventail de solutions que tout le monde. Ils l’ont simplement encapsulé différemment.
Les hooks PreToolUse comme politique programmable
Les modes et les allowlists couvrent les cas simples : « autoriser l’agent à modifier des fichiers, mais pas à exécuter bash », « refuser tout ce qui ressemble à rm -rf ». Ils échouent lorsque votre politique nécessite une véritable logique. Vous voulez bloquer git push uniquement lorsque la branche est main. Vous voulez refuser toute opération Edit qui touche un fichier correspondant à une expression régulière de secret. Vous voulez limiter le débit des appels shell par session, ou faire transiter chaque invocation d’outil vers votre journal d’audit central (le SIEM, c’est-à-dire le système de gestion des informations et des événements de sécurité que votre équipe de sécurité surveille déjà).
Aucune de ces options ne peut tenir dans une allowlist statique. C’est précisément le rôle des hooks — des commandes shell exécutées par Claude Code à des moments précis du cycle de vie des appels d’outils, capables d’inspecter l’appel en attente et de renvoyer une décision structurée d’autorisation ou de refus. Claude Code expose une trentaine d’événements du cycle de vie (la liste complète se trouve dans la documentation), dont l’un réordonne tout le reste : un hook PreToolUse qui renvoie permissionDecision: "deny" bloque un outil quel que soit le mode.
Voici la structure 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"
}
]
}
]
}
}
Les règles statiques deny et les hooks dynamiques n’échouent pas de la même manière. Un résultat PreToolUse égal à deny bloque avant le flux normal d’autorisation, tandis qu’un hook command, HTTP ou d’outil MCP arrivé à expiration est non bloquant : Claude Code poursuit ce flux. Dans cet exemple, acceptEdits peut donc autoriser un Edit ou un Write lorsque protect-paths.sh arrive à expiration. Ne faites pas dépendre une restriction non négociable sur un chemin uniquement d’un command hook. Placez les restrictions statiques dans les règles de refus ou dans la sandbox ; pour une policy dynamique, choisissez un contrôle dont l’échec de l’évaluateur reste restrictif, puis testez à la fois son comportement en cas d’expiration et un refus explicite.
Un hook peut être un script shell de cinq lignes ou un moteur de policy complet. C’est la structure de retour qui compte :
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "writes outside workspace prohibited"
}
}
Le modèle reçoit un refus structuré. La boucle de raisonnement décrite dans la Partie 1 le traite comme n’importe quelle autre observation d’outil : le refus devient du contexte, l’agent replanifie et la boucle continue. C’est ce que permet l’idée selon laquelle « la permission est une infrastructure ». Le refus est intégré au même mécanisme que celui qui gère une erreur 500 renvoyée par un outil HTTP. Il ne s’agit pas d’un workflow de sécurité distinct qu’il faudrait ajouter après coup.
Une anti-pattern courante consiste à rédiger un system prompt disant « ne supprimez aucun fichier sans confirmation explicite de l’utilisateur », à déployer l’agent, puis à s’appuyer sur cette instruction comme mécanisme de contrôle. Un prompt injecté, ou un résultat d’outil contrôlé par un attaquant, peut contourner cette instruction. Le modèle n’est pas un moteur de policy. Il peut reconnaître le motif que vous avez écrit, ou un motif fourni par un attaquant.
L’approbation humaine ne fonctionne que comme mécanisme d’escalade
!!! byte « Byte dit »
Après vingt demandes d’approbation, il devient facile de cliquer sur Approve sans lire. Réservez ces demandes aux actions qui nécessitent une décision humaine.
Les filtres de contenu inspectent ce que dit le modèle. Les règles de permission inspectent l’appel d’outil avant son exécution. La revue human-in-the-loop traite les actions qui nécessitent encore l’intervention d’une personne. Si les utilisateurs approuvent 93 % des demandes, examinez les règles d’escalade et la qualité des prompts.
LangGraph fournit la primitive de pause/reprise. HumanLayer encapsule le canal d’approbation, et les données d’utilisation d’Anthropic montrent pourquoi il faut mesurer le nombre et la qualité des escalades.
La primitive de LangGraph
Le interrupt() + Command(resume=value) de LangGraph met un graphe en pause, persiste son état via le checkpointer configuré, puis le reprend avec une valeur fournie par un humain. La sécurité de cette reprise dépend d’un détail important de la documentation :
« Lorsque l’exécution reprend (après la fourniture de l’entrée demandée), le runtime redémarre le nœud entier depuis le début — il ne reprend pas exactement à la ligne où
interrupta été appelé. »
Trois contraintes découlent de ce comportement de redémarrage :
1. Les effets de bord avant interrupt() doivent être idempotents. Lorsque l’humain répond, le nœud entier est réexécuté depuis le début, et non à partir de la ligne interrupt(). Ainsi, si votre nœud envoie un e-mail, se met en pause en attendant une approbation, puis renvoie « sent », l’e-mail est envoyé une seconde fois lors de la reprise. Correctif : placez les effets de bord après l’interruption ou rendez-les sûrs en cas de répétition (clés de déduplication, upsert plutôt qu’insert, mise en cache par ID de message).
2. Les interruptions sont associées aux reprises par index, et non par nom. Si un même nœud contient deux appels à interrupt(), LangGraph les associe aux valeurs Command(resume=...) dans l’ordre de leur déclenchement. Toute branche qui modifie le nombre d’interruptions exécutées (un if qui en ignore une lors de la reprise, une boucle qui effectue un nombre d’itérations différent) désynchronisera les index ; une valeur de reprise peut alors être affectée à la mauvaise interruption.
3. Gardez des payloads compatibles JSON. La documentation de LangGraph exige des valeurs sérialisables en JSON pour interrupt() et les payloads de reprise. Utilisez des chaînes, des nombres, des booléens, des tableaux et des dictionnaires contenant ces types. Évitez les fonctions, les instances de classes et autres objets complexes, car la sérialisation dépend du checkpointer configuré. Convertissez les données d’approbation en dictionnaires et en types primitifs avant de les transmettre à interrupt() ou de les exposer via une API HTTP.
Les trois patterns canoniques :
# No-run: illustrative LangGraph sketches; requires LangGraph, a tool decorator, interrupt, and smtp_send.
# (a) Approval check
@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 — conditional edge
class AgeState(TypedDict):
age: int | None
pending_question: str | None
def get_age_node(state: AgeState):
question = state.get("pending_question") or "What is your age?"
answer = interrupt(question) # once per node invocation
if isinstance(answer, int) and answer > 0:
return {"age": answer, "pending_question": None}
return {"pending_question": f"'{answer}' is not valid. Please enter a positive number."}
def route_age(state: AgeState):
return END if state.get("age") is not None else "get_age"
builder = StateGraph(AgeState)
builder.add_node("get_age", get_age_node)
builder.add_edge(START, "get_age")
builder.add_conditional_edges("get_age", route_age)
La reprise s’effectue avec graph.invoke(Command(resume={"action": "approve"}), config=cfg). LangGraph 0.4+ prend en charge la reprise multi-interruptions sous forme de dictionnaire pour les branches parallèles, ce qui devient important dès que votre agent effectue un fan-out.
HumanLayer : l’approbation comme produit
HumanLayer est la version managée de cette même approche. Annotez une fonction, et les demandes d’approbation sont routées vers Slack, e-mail ou Discord, avec des règles définissant les personnes à notifier. Lorsque l’agent tente 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 refus, la documentation de HumanLayer le formule ainsi : « HumanLayer transmettra vos commentaires à l’agent, qui pourra alors adapter son approche. » Ces commentaires permettent à l’agent de réviser son plan au lieu de considérer le refus comme une impasse.
La fatigue liée aux approbations dans les données
Anthropic a publié les données réelles en février 2026. Trois résultats sont plus importants que les autres.
« Nous avons constaté que 80 % des appels d’outils proviennent d’agents qui semblent disposer d’au moins un type de garde-fou (comme des permissions restreintes ou des exigences d’approbation humaine), que 73 % semblent intégrer un humain dans la boucle d’une manière ou d’une autre, et que seulement 0,8 % des actions semblent irréversibles. »
C’est la bonne nouvelle. Considérez 80 % comme une limite supérieure, car la note de bas de page 14 d’Anthropic précise que « Claude surestimait souvent l’implication humaine ; nous considérons donc que 80 % constitue une limite supérieure. »
« Les nouveaux utilisateurs (<50 sessions) utilisent le full auto-approve environ 20 % du temps ; après 750 sessions, cette proportion dépasse 40 % des sessions. »
C’est la dérive observée. Les utilisateurs commencent avec prudence, puis deviennent moins prudents à mesure qu’ils développent leur confiance dans l’outil. C’est un comportement humain, et non un défaut de caractère. Il s’agit d’un signal de télémétrie que votre système devrait suivre. (Petite précision de vérification : la couverture secondaire a largement cité « 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 %, c’est de là qu’il provient.)
Anthropic a publié en mars 2026 un billet d’ingénierie sur le mode automatique de Claude Code qui donne le chiffre clé :
« Les utilisateurs de Claude Code approuvent 93 % des demandes d’autorisation. Nous avons conçu des classifieurs pour automatiser certaines décisions, afin d’accroître la sécurité tout en réduisant la fatigue liée aux approbations… Si une session cumule 3 refus consécutifs ou 20 refus au total, nous arrêtons le modèle et transmettons la décision à un humain. »
Un taux d’approbation élevé justifie d’examiner si les prompts aident réellement les utilisateurs à décider ; il ne prouve pas l’existence d’un seuil d’échec universel. Le classifieur en deux étapes d’Anthropic réduit le nombre de prompts et escalade les refus groupés. Le taux de faux positifs annoncé, de 0,4 %, a été mesuré sur 10 000 exemples issus du trafic. Les taux de faux négatifs étaient de 17 % sur 52 actions réelles trop zélées et de 5,7 % sur 1 000 cas synthétiques d’exfiltration. Ces ensembles correspondent à des populations différentes : il ne s’agit ni de pourcentages complémentaires ni d’une preuve que la classification remplace le sandboxing.
Mesurer la qualité des escalades
Autorisez automatiquement les actions courantes et réversibles, et journalisez-les. Escaladez les actions dont les effets de bord franchissent une limite que le runtime ne peut pas annuler, par exemple l’envoi d’un message externe, une écriture en production, un force push ou un paiement. Anthropic présente l’objectif ainsi : permettre à un humain d’intervenir lorsque la décision entraîne une conséquence réelle.
Suivez l’intégralité du funnel au lieu de viser un taux d’approbation repris d’un autre contexte : actions proposées, autorisations automatiques, escalades, approbations, refus, modifications et incidents survenus après approbation. Un taux d’approbation élevé peut indiquer que les prompts ne sont qu’un bruit routinier. Un taux élevé de refus ou de modifications peut signifier que le planner propose une mauvaise action ou masque les informations nécessaires à l’approbateur. Le seuil pertinent dépend de la classe d’action et du coût d’une autorisation erronée ; définissez-le donc à partir de vos propres données d’incidents et de revue.
MCP : périmètre d’autorisation et supply chain
MCP connecte les agents à des outils externes tels que Slack, GitHub et des bases de données, ce qui fait de son modèle d’autorisation une composante de la frontière de sécurité. Les révisions de la spécification de 2025 ont séparé les rôles d’émetteur de tokens et de resource server, et ajouté les resource indicators. Cet historique permet de comprendre les vérifications d’audience et de transmission qu’un serveur doit appliquer aujourd’hui.
L’autorisation MCP en trois révisions
L’autorisation était facultative pour les implémentations MCP dans la spécification 2025-03-26. Pour un déploiement HTTP en production qui protège des données utilisateur ou des outils, je recommande OAuth 2.1 avec PKCE (Proof Key for Code Exchange), que la spécification exige lorsqu’une implémentation prend en charge l’autorisation OAuth. La conception initiale permettait à un même serveur MCP d’assumer deux rôles. L’authorization server émet les tokens ; le resource server les accepte. Ce sont deux rôles distincts, même lorsqu’un seul service assure les deux. Si ce service transmet une requête à un autre serveur, la même credential peut parvenir à un endroit auquel elle n’était jamais destinée. C’est la faille.
La révision du 2025-06-18 a explicité les rôles. Un serveur MCP protégé agit comme un OAuth resource server, tandis qu’un authorization server émet le token. L’authorization server peut être cohébergé avec le resource server ou exécuté séparément. Les Resource Indicators de la RFC 8707 lient le token à une ressource cible, et les Protected Resource Metadata de la RFC 9728 fournissent au client un chemin explicite de découverte. La spécification interdit également à un serveur MCP de transmettre en amont le token du client.
La révision du 25/11/2025 a conservé cette séparation et s’est concentrée sur les éléments qu’un client doit correctement implémenter. La découverte du serveur d’autorisation a intégré OpenID Connect Discovery, afin qu’un client puisse trouver le bon issuer au lieu de le deviner. Le consentement incrémental pour les scopes a été déplacé dans l’en-tête WWW-Authenticate, ce qui permet à un serveur de demander un scope supplémentaire au moment où il en a besoin plutôt que d’exiger tous les scopes dès le départ. L’enregistrement des clients a intégré les OAuth Client ID Metadata Documents comme mécanisme recommandé, en remplacement de l’enregistrement dynamique dans la plupart des déploiements. La découverte des métadonnées de ressource protégée a également été alignée sur la RFC 9728, ce qui rend WWW-Authenticate facultatif, avec un mécanisme de repli via .well-known.
Consultez la page consacrée au versionnage avant l’implémentation. Au 6 septembre 2026, la révision actuelle est 2026-07-28. Elle exige que chaque requête déclare la version du protocole et permet au serveur d’accepter ou de rejeter chaque requête indépendamment. Un client peut appeler server/discover pour sélectionner une version à l’avance, mais la découverte reste facultative. La déclaration et la négociation par requête restent obligatoires, y compris lorsque le client traite une erreur de version non prise en charge et réessaie avec une version mutuellement compatible.
La liaison à l’audience limite les rejeux contre le mauvais serveur MCP. Elle ne neutralise pas les vulnérabilités distinctes de configuration de Claude Code décrites plus haut : un hook côté hôte peut toujours s’exécuter avant le démarrage du modèle, et un projet non fiable peut toujours tenter de modifier la configuration locale. Le scope du token, la confiance accordée au projet, la politique applicable aux hooks et le sandboxing restent des contrôles distincts.
Checklist MCP 2026
Si vous déployez ou consommez MCP en production :
- Traitez l’authentification comme une exigence de production, et non comme une valeur par défaut du protocole. MCP rend l’autorisation facultative, mais je recommande OAuth 2.1 avec PKCE pour un déploiement HTTP protégé. L’avis de sécurité concernant le service MCP Azure Web Apps hébergé portait sur l’absence d’authentification. Si votre serveur accepte du trafic sans vérifier les identifiants de l’appelant, vous avez construit un outil que toute personne pouvant l’atteindre peut appeler.
- Les tokens sont liés à une audience. Demandez un token destiné à la ressource MCP cible et vérifiez que le token présenté désigne votre serveur comme audience. Rejetez les tokens émis pour une autre ressource.
- Isolez délibérément les droits de lecture et d’écriture. MCP lie un token à un resource server, et non à un outil individuel. Si un serveur Slack accepte un identifiant avec
chat:writeet le route vers des handlers de lecture et d’écriture, un outil orienté lecture peut devenir un chemin d’envoi de messages via la politique de ce serveur. Utilisez des resource servers distincts, ou des identifiants et des contrôles d’autorisation distincts, lorsque les opérations de lecture et d’écriture doivent avoir des rayons d’impact indépendants. - Utilisez des tokens récents à durée de vie courte plutôt que des clés API permanentes. Le modèle de coffre-fort de Claude Managed Agents (Anthropic engineering) sert de référence : l’agent lui-même ne voit jamais les véritables identifiants. Le proxy récupère les identifiants correspondants stockés dans le coffre-fort, appelle l’outil au nom de l’agent et renvoie le résultat. L’émission d’un token différent pour chaque appel ne constitue pas une garantie documentée ; les identifiants à courte durée de vie sont une recommandation de déploiement.
Les contrôles de la chaîne d’approvisionnement s’appliquent toujours
Les incidents axios et Trivy sont des défaillances familières de la chaîne d’approvisionnement des packages et de la CI, appliquées à des systèmes qui automatisent l’installation des dépendances. L’automatisation augmente le nombre et la vitesse des exécutions ; les contrôles de version, de provenance et de revue doivent donc s’exécuter avant que la commande générée n’atteigne la CI ou un sandbox.
La défense est simple :
- Épinglez les versions dans le lockfile. Les agents ne doivent jamais résoudre une version flottante — ni
@latest, ninpm update, ni--upgrade. - Effectuez les scans dans la CI avec des outils indépendants du composant contrôlé.
- Utilisez les SHA de commit GitHub pour les Actions, et non des tags.
- Examinez les différences de dépendances dans les PR pilotées par un agent avant le merge.
Ce sont des contrôles standard de la chaîne d’approvisionnement. L’automatisation par agent modifie leur fréquence, pas leur mécanisme.
Une pile de politiques pour le Market Analyst Agent
Le Market Analyst Agent de la Partie 1 est un petit agent LangGraph qui récupère des données de marché et rédige un rapport d’analyste — mais il est moins simple que cette description ne le laisse penser. En plus des outils de données de marché, il exécute une CLI autorisée via subprocess, évalue du Python rédigé par le modèle dans le processus et crée des enregistrements de trades simulés. Il met en œuvre l’invocation d’outils, l’exécution de code et le routage des approbations, mais ne passe pas de véritables ordres. Voici à quoi ressemble une pile de politiques minimale pour cet agent.
Couche 1 : un hook PreToolUse qui refuse avant l’exécution
Même un agent qui « se contente de lire des données boursières » peut tenter ce qu’il ne devrait pas : un curl vers une URL contrôlée par un attaquant, des écritures en dehors de l’espace de travail, des mutations git dans le dépôt hôte. Une règle de refus relève de l’infrastructure, pas du prompt. L’esquisse ci-dessous renvoie la propre structure de décision de l’agent, et non l’enveloppe hookSpecificOutput attendue par Claude Code.
# agent/permissions.py
from pathlib import Path
DENY_COMMANDS = frozenset({
"rm -rf", "sudo", "chmod 777",
"curl -X POST", "wget", "nc ",
})
WORKSPACE = Path("./workspace").resolve()
def _outside_workspace(path: str) -> bool:
# Resolve first: "~/.ssh/id_rsa" and "workspace/../../etc" both
# have to become real paths before the comparison means anything.
return not Path(path).expanduser().resolve().is_relative_to(WORKSPACE)
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 _outside_workspace(path):
return {"permissionDecision": "deny",
"reason": f"path outside workspace: {path!r}"}
return None # fall through to mode / canUseTool
L’esquisse rend le point de contrôle visible. Le hook renvoie un refus structuré, et la boucle de raisonnement reçoit ce refus comme une observation d’outil.
La vérification du chemin est une allowlist : une seule racine d’espace de travail est autorisée, tout le reste est refusé. Une deny-list de préfixes interdits ne bloque que les chemins auxquels vous avez pensé. ~/.ssh/id_rsa ne s’écrit jamais exactement comme vous l’avez noté. La vérification de la commande reste une deny-list. La recherche de sous-chaînes ne constitue pas une politique shell utilisable en production. Une implémentation réelle devrait parser la commande et s’appuyer sur le sandbox de l’OS au moment de l’exécution. L’esquisse ne constitue pas en elle-même une frontière d’exécution : si un hook externe l’exécute, un délai d’expiration doit maintenir en vigueur une restriction indépendante de l’espace de travail.
Couche 2 : un canary d’entrée pour l’injection de prompt
Le détournement de l’objectif de l’agent (ASI01) arrive souvent via une page web récupérée, un message utilisateur ou un PDF d’article de recherche. Un canary regex peu coûteux détecte les motifs d’instructions littéraux et génère un événement de télémétrie utile. Il ne détectera pas les injections obfusquées, multilingues ou dépendantes du contexte ; il ne peut donc pas servir de frontière de décision :
# agent/input_canary.py
import re
INJECTION_PATTERNS = [
re.compile(r"ignore\s+(?:all\s+|any\s+|the\s+)?"
r"(?:previous\s+|prior\s+|above\s+|earlier\s+)?"
r"(?:instructions|rules|prompts?)",
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
Consignez les entrées signalées ; ne les rejetez pas automatiquement. Les faux positifs sont coûteux pour un assistant de recherche. Mais c’est le journal qui permet de remarquer quand le nombre de signalements augmente soudainement pour un même utilisateur.
Couche 3 : validation de la sortie structurée via un hook d’arrêt
Un modèle Pydantic associé à un hook Stop fournit une boucle stricte de validation puis de nouvelle tentative pour générer le rapport. L’agent ne peut pas déclarer la tâche « terminée » tant que la sortie n’a pas passé la validation du schéma et un smoke test :
# No-run: illustrative policy sketch; requires Pydantic and the repo-local agent.schemas module.
# 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"}
La vérification du schéma et un smoke test suffisent à faire la différence entre « l’agent a indiqué que c’était terminé » et « la sortie est effectivement un rapport ».
Couche 4 : approbation avant les actions sortantes
Le execute_trade de l’analyste de marché est une transition d’état simulée et idempotente ; il illustre donc le routage de l’approbation plutôt qu’un effet financier irréversible. Pour une véritable intégration sortante — e-mail, Slack, rapport destiné à un client ou ordre de courtage — présentez l’action proposée à une personne et attendez son approbation ou son rejet avant d’exécuter l’outil. Utilisez interrupt() pour cette pause :
# No-run: illustrative outbound-tool sketch; requires LangGraph, a tool decorator, and smtp_send.
# 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": body, # Review the complete content that will execute.
})
if resp.get("action") == "approve":
return smtp_send(to, body)
return "send cancelled by human"
Les actions sortantes complètent la trifecta létale. Exigez une approbation pour les actions sortantes lorsque l’autorisation existante et la politique de déploiement ne les couvrent pas. L’approbation doit porter sur la destination et le contenu complets ; toute modification des arguments nécessite une nouvelle décision. Les messages destinés aux services financiers, aux clients ou à d’autres destinataires externes doivent montrer à l’approbateur ce qui sera envoyé et où.
Ce que cette stack ne fait pas
Il ne s’agit pas d’une protection contre :
- Une dépendance en amont compromise (de type axios). L’agent exécute ce que
uv synclui indique d’exécuter. - Un
.mcp.jsonmalveillant dans un dépôt cloné (de type CVE-2025-59536). C’est le modèle d’autorisation du client MCP hôte qui intercepte ce cas, pas le code de l’agent. - Une chaîne de vol de données constituée d’outils légitimes (de type EchoLeak) : l’agent lit des données privées, récupère des URL externes, puis envoie des messages. Rompez ou contraignez ce chemin d’exfiltration au moyen d’un accès aux données limité au strict nécessaire, d’un routage de confiance, de restrictions d’egress et d’une approbation lorsque celle-ci est requise. Supprimer une capacité bloque ce chemin précis, pas toutes les attaques possibles.
- Une sortie de
execute_python_analysis, l’évaluateur Python in-process de l’agent. Il bloque une liste de types d’instructions, rejette tout identifiant commençant par un underscore et n’autorise les imports que depuisjson,mathetstatistics. Maisexecdans le processus worker n’est pas une frontière de sécurité : un contournement s’exécute avec les descripteurs de fichiers et l’accès réseau du worker. Placez-le derrière une isolation du système de fichiers, du réseau et des identifiants, avec des limites de CPU, de mémoire et de temps, avant d’évaluer du code non fiable. Un simple sous-processus hérite des accès et ne constitue pas un sandbox de sécurité.
Ces quatre couches relèvent de la politique locale, qui est la couche la plus interne que vous contrôlez, et non la seule. Chaque élément de cette liste doit être intercepté ailleurs — dans le lockfile, dans le client MCP, dans la frontière de processus autour du code généré ou dans des contrôles qui empêchent des instructions non fiables de relier des données privées à une destination d’exfiltration.
Points clés
- Les filtres de contenu et la politique d’exécution protègent des frontières différentes. Les filtres inspectent les entrées et sorties du modèle. L’autorisation des outils, la portée des identifiants, les sandbox et les contrôles de la supply chain agissent sur les chemins utilisés dans les sept incidents.
- La plupart des catégories OWASP ASI nécessitent des contrôles en dehors de la sortie du modèle. Utilisez la liste pour associer chaque menace au composant capable de la bloquer ou de l’enregistrer réellement.
- Les permissions relèvent de l’infrastructure, pas du prompt. Claude Code documente la priorité entre les règles deny, ask et allow, tandis que
PreToolUsepeut bloquer avant l’exécution. Le Claude Agent SDK expose un chemincanUseTooldistinct. Les autres runtimes ont besoin d’un modèle de priorité tout aussi testable. - Traitez le deny structuré d’un hook PreToolUse comme n’importe quelle autre observation d’outil. La boucle de raisonnement le gère déjà. Il n’est pas nécessaire de mettre en place un workflow de sécurité distinct.
- Un taux d’approbation de 93 % indique qu’il faut examiner la qualité des prompts et la fréquence des escalades. Suivez les modifications, les refus et les incidents après approbation, plutôt que de recopier une cible universelle.
- Les tokens liés à l’audience et les coffres-forts par session limitent la réutilisation et l’exposition des identifiants. Ils ne remplacent ni la confiance accordée au projet, ni la politique des hooks, ni le sandboxing.
- Les contrôles de supply chain doivent s’exécuter à la vitesse de l’automatisation. Épinglez les versions et les SHA des Actions, lancez les scans dans la CI et examinez les changements de dépendances dans les pull requests rédigées par des agents.
- Construisez la couche de policy de façon à ce qu’un nouveau lancement produit ne l’invalide pas. OpenAI Agents SDK, Codex CLI et Claude Code expriment différemment les mêmes primitives. Ce sont ces primitives — échelles de permissions, hooks, sandbox, interruptions, tokens liés à l’audience — sur lesquelles vous misez.
La couche suivante est le runtime
La partie 5, Long-Running AI Agent Runtime, montre où résident le sandbox, le broker de secrets, le checkpoint et la trace d’audit pendant une longue exécution. La partie 6 entre ensuite dans le harness, où cette échelle de permissions constitue une étape parmi d’autres, et examine comment les contrôles d’acceptation, les nouvelles tentatives et l’évaluation fondée sur les traces empêchent la boucle de déclarer trop tôt sa réussite. Elle ajoute également une question dont cet article n’avait pas besoin : peut-on réellement renvoyer un appel arrivé à expiration en cours d’exécution ?
Références
Les cadres de référence
- 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: Why AI Agents That Think Must Never Act, arXiv 2604.12986, 14 avril 2026 (non évalué par les pairs).
- Alessandro Pignati, Your AI Agent Has Too Much Power: Understanding and Taming Excessive Agency, janvier 2026.
Produits de guardrails pour les 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 chez Aim Labs, désormais chez Cato Networks), Analyse d’EchoLeak (CVE-2025-32711).
- AWS, avis de sécurité Amazon Q Developer VS Code v1.84.0 (CVE-2025-8217).
- Microsoft, fiche CVE d’Azure MCP Server (CVE-2026-32211 ; référence fournisseur : Microsoft).
- Check Point Research, RCE et exfiltration de jetons d’API via les fichiers de projet de Claude Code (CVE-2025-59536).
- axios, retour d’expérience sur la compromission de v1.14.1 / v0.30.4.
- Aqua Security, détournement du tag Trivy Actions (GHSA-69fq-xp46-6x23).
Surfaces de policy
- OpenAI Agents SDK — documentation des outils MCP
- Configuration gérée de Codex CLI
- Modes d’autorisation de Claude Code
- Sandboxing de Claude Code
- Agents gérés de Claude
HITL
- Documentation des interruptions de LangGraph
- Guide de démarrage rapide Python de HumanLayer
- Anthropic, Mesurer l’autonomie des agents IA en pratique, 18 février 2026.
- Anthropic, mode automatique de Claude Code, 25 mars 2026.
- Jackson Wells (Galileo), Comment mettre en place une supervision human-in-the-loop pour des agents IA en production, 21 décembre 2025.
OWASP
- OWASP Agentic Security Initiative, Top 10 des applications agentiques, 2026, 9 décembre 2025.
Le policy layer de l’agent Market Analyst se trouve dans le graphe combiné analyse-vers-trade du dépôt, et non dans le graphe d’analyse répertorié dans la Partie 1. Il gouverne l’état des trades simulés : un nœud guardian déterministe rejette les actions restreintes, approuve automatiquement celles de faible valeur et transmet les autres à un nœud d’agent de conformité avant que le graphe ne s’arrête avec interrupt_before. Le policy layer est disponible sur GitHub. Le deny hook, l’input canary et le validateur du Stop-hook ci-dessus sont des esquisses de ces mêmes points de contrôle. Ils sont conçus pour être lus, et non déposés tels quels dans ce dépôt.