# Les outils HTTP génériques effacent les permissions

Un appel HTTP générique ressemble à un seul outil pour l'hôte d'un agent. En pratique, il peut contenir des milliers de capacités. Il suffit de changer l'URL, l'identifiant, la méthode, les en-têtes ou le corps pour que la même fonction lise une page d'état, exporte des dossiers clients, renouvelle un secret de signature ou supprime une ressource de production.

Ce décalage fait de la permission accordée à l'outil une frontière de sécurité fragile. Autoriser un agent à utiliser `http_request` ne dit pas ce qu'il peut faire. La décision utile se trouve un niveau plus bas, là où le système peut lier un identifiant précis à une destination résolue, une méthode permise et des détails de requête contraints.

La distinction compte même si l'agent se comporte correctement. Les prompts contiennent des erreurs, les textes récupérés peuvent contenir des instructions hostiles et un outil trop large peut transformer une petite erreur de planification en véritable action d'API. Donner un nom plus rassurant à l'outil ne corrige rien. Il faut autoriser l'action qui va traverser le réseau.

## Un seul outil d'appel contient plusieurs capacités

Un outil ne constitue une frontière de capacité que si ses entrées ne peuvent pas changer radicalement son autorité. Une consultation météo avec un fournisseur, une méthode GET et un format de réponse fixes se rapproche d'une capacité unique. Une fonction qui accepte n'importe quelle URL, méthode, en-tête et corps est un client réseau programmable.

Prenons un schéma avec cinq champs ordinaires : `url`, `method`, `headers`, `body` et `credential_name`. L'hôte peut le présenter comme un seul élément de sa liste. Pourtant, `GET /projects/42` avec un jeton de lecture et `DELETE /projects/42` avec un jeton administrateur ne méritent pas la même décision. Une requête vers une API publique et une requête résolue vers un service du réseau privé non plus.

Les annotations d'outils du Model Context Protocol ne réparent pas cet effondrement. La spécification MCP décrit les annotations de lecture seule ou de comportement destructeur comme des indications et demande aux clients de ne pas leur faire confiance si elles ne proviennent pas de serveurs fiables. Un appelant générique ne peut pas non plus attribuer une valeur toujours vraie à `readOnlyHint`, car son comportement dépend d'arguments encore inconnus.

Les équipes confondent souvent deux notions : la sélection de l'outil répond à la question de l'implémentation exécutée, tandis que l'autorisation de l'action détermine l'effet externe permis. Si l'hôte n'approuve que le nom de l'implémentation, chaque requête matériellement différente hérite de cette approbation. Un sélecteur d'outils bien rangé peut donc cacher un canal sortant presque sans limites.

Séparer chaque opération d'API dans un outil améliore parfois les descriptions et la planification, mais ne règle pas toute la sécurité. Les outils générés ont toujours besoin d'un point d'application, les identifiants conservent leur propre autorité et les redirections peuvent envoyer une opération apparemment étroite ailleurs. Utilisez des outils précis pour leur ergonomie. Appliquez les permissions à la requête finale.

## Une permission a besoin de quatre coordonnées

Une décision HTTP défendable comporte quatre coordonnées : l'identifiant, la destination résolue, la méthode et les détails de la requête. Retirez-en une et la requête peut garder une apparence approuvée tout en changeant d'effet.

L'identifiant indique l'autorité exercée. Un jeton de déploiement et un jeton de facturation peuvent viser le même hôte et le même chemin tout en donnant des pouvoirs sans rapport. La destination indique où cette autorité peut être présentée. La méthode exprime l'intention HTTP générale. Les autres détails, dont le chemin, la requête, certains en-têtes, le type de contenu et les champs du corps, déterminent l'opération réelle.

Un enregistrement d'autorisation compact peut ressembler à ceci :

```yaml
credential: issue-tracker-read
origin: https://api.example.test:443
path_prefix: /v2/issues/
methods:
  - GET
redirects: deny
headers:
  allow:
    - Accept
query:
  deny:
    - include_deleted
body: forbidden
```

Ce fragment évite plusieurs échecs à la fois. L'agent ne peut pas choisir un identifiant plus puissant, l'envoyer vers une autre origine, transformer la lecture en POST, suivre une redirection, ajouter un en-tête d'autorisation, demander des éléments supprimés avec ce paramètre ni glisser un corps dans une opération attendue en lecture seule.

Cet objet de politique n'est pas un modèle universel. Certaines API exigent des chemins exacts, d'autres un identifiant de locataire dans le chemin et d'autres encore décrivent une action dans le corps d'un POST. La forme de la décision compte : évaluer les quatre coordonnées ensemble après analyse et normalisation, puis injecter l'identifiant seulement si la requête est acceptée.

Ne laissez pas l'agent fournir le véritable secret dans `headers`. Faites-lui désigner un identifiant par un nom opaque, puis demandez à un exécuteur fiable d'ajouter le jeton bearer, l'authentification basic ou l'en-tête personnalisé après autorisation. Sinon, l'agent peut copier le secret dans un autre champ, un journal ou une seconde destination et rendre la règle de destination purement décorative.

## Les identifiants définissent l'autorité

Les identifiants doivent être des autorisations séparées avec le minimum de droits offert par l'API cible. Un outil HTTP générique devient beaucoup moins dangereux lorsque l'exécuteur choisit parmi des identifiants nommés et limités au lieu de détenir un jeton valable pour toute l'organisation.

Les portées OAuth aident, mais portée et audience répondent à des questions différentes. Une portée peut dire qu'un jeton peut lire les contacts. L'audience dit quel serveur de ressources doit l'accepter. La RFC 8707 définit le paramètre OAuth `resource` afin qu'un client demande un jeton pour une ressource protégée précise, et recommande aux serveurs d'autorisation de limiter l'audience du jeton émis. Son argument est concret : un jeton présenté à une ressource ne doit pas fonctionner sur une autre.

La spécification actuelle d'autorisation MCP applique la même séparation. Elle exige que les serveurs MCP n'acceptent que les jetons qui leur sont destinés et interdit de transmettre le jeton MCP entrant à une API en aval. Quand un serveur MCP appelle une autre API, il agit comme un client OAuth distinct et utilise un jeton séparé. C'est une frontière claire qu'un exécuteur HTTP générique doit préserver, même si l'agent ne voit aucun flux OAuth.

Les clés d'API offrent souvent des contrôles natifs plus faibles. Traitez tout de même chacune comme une autorité distincte. Notez les origines qui peuvent la recevoir, la forme d'en-tête que l'exécuteur injectera, sa date d'expiration et le responsable de son renouvellement. Ne supposez pas qu'une entrée nommée `staging` est inoffensive. Vérifiez ses véritables privilèges auprès du service.

Le choix de l'identifiant doit aussi apparaître dans l'écran d'approbation. Un message qui affiche seulement `POST api.example.test` ne dit pas si l'appel utilise une clé de test ou un jeton de propriétaire. Affichez un libellé lisible et le compte ou locataire concerné, mais jamais le secret. Si l'exécuteur ne peut pas déterminer ce contexte, l'autorisation est trop ambiguë pour être réutilisée en silence.

Garder les secrets hors du modèle réduit les divulgations accidentelles, mais le secret seul ne limite pas l'utilisation. Un agent peut détourner un secret sans en voir les octets si un exécuteur trop large accepte de le joindre à n'importe quelle requête. La non-divulgation et l'autorité minimale résolvent deux problèmes différents. Il faut les deux.

## La destination est le point final résolu

Vérifier une chaîne URL une seule fois ne contrôle pas la destination. L'exécuteur doit analyser et normaliser l'URL, résoudre l'hôte, appliquer les règles réseau et reprendre la décision à chaque redirection avant de joindre un identifiant.

Commencez par le schéma, l'hôte et le port effectif exacts. `https://api.example.test` et `https://api.example.test:8443` sont deux origines différentes. Refusez les informations utilisateur dans les URL, les encodages ambigus, les schémas non pris en charge et les noms d'hôte qui ressemblent seulement à un suffixe approuvé. Une vérification qui accepte `api.example.test.attacker.invalid` n'est pas une liste d'autorisation.

Résolvez ensuite le DNS et inspectez chaque adresse renvoyée. Un nom d'apparence publique peut pointer vers loopback, link-local, une plage privée ou les métadonnées d'une instance cloud. La résolution peut aussi changer entre validation et connexion. Le composant qui valide doit contrôler la connexion et vérifier l'adresse réellement utilisée, sans remettre l'URL à un second client qui la résoudrait de nouveau.

La Server Side Request Forgery Prevention Cheat Sheet de l'OWASP recommande une liste de destinations fiables lorsque l'application peut les identifier. Elle conseille aussi de désactiver le suivi automatique des redirections, car une redirection peut contourner la validation des entrées. Ce conseil convient particulièrement aux outils d'agents : le modèle fournit souvent l'URL et la requête peut porter un identifiant réservé à la première destination.

Une redirection exige une nouvelle décision d'autorisation. La RFC 9110 demande de la prudence pour les redirections automatiques de méthodes non sûres et recommande de supprimer les champs liés à la ressource, comme Authorization et Cookie. Un exécuteur sûr peut faire plus simple : refuser par défaut les redirections authentifiées ou présenter la nouvelle destination comme une nouvelle action et réinjecter l'identifiant seulement après validation.

Les contrôles de chemin restent nécessaires dans une origine approuvée. Passerelles multilocataires, hôtes SaaS partagés et routes administratives peuvent vivre derrière le même nom. La RFC 8707 précise qu'un chemin identifiant un locataire peut devoir appartenir à l'identifiant de ressource dans un système multilocataire. Une liste d'origines sans contrainte de chemin ou de locataire peut donc être bien plus large que prévu.

## Les méthodes HTTP sont des signaux

Limiter les méthodes élimine beaucoup d'erreurs, mais leur nom ne prouve pas qu'une requête est inoffensive. La RFC 9110 définit GET, HEAD, OPTIONS et TRACE comme sûres, car leur sémantique spécifiée est essentiellement en lecture. Elle définit PUT, DELETE et les méthodes sûres comme idempotentes, ce qui signifie que répéter l'opération a le même effet prévu que l'exécuter une fois.

Sûr et idempotent ne sont pas synonymes. DELETE peut être idempotent tout en détruisant une ressource. POST n'est généralement ni sûr ni idempotent, mais une API peut l'utiliser pour une recherche en lecture seule trop grande pour une URL. Un système qui réduit le risque à GET contre POST classera mal les deux cas.

Pire, certaines API réelles violent la sémantique des méthodes. La RFC 9110 avertit des ressources qui placent des actions telles qu'une suppression dans une requête GET et demande au propriétaire d'empêcher ce comportement via une méthode sûre. L'exécuteur ne peut pas supposer que tous les services respectent cette règle. Si `GET /jobs?id=7&action=cancel` change un état, autoriser tous les GET ne crée pas une permission de lecture seule.

Utilisez la méthode comme une donnée parmi d'autres. Associez-la au chemin et, si nécessaire, à des contraintes propres à l'opération. Pour une API bien décrite, une opération OpenAPI fournit une carte utile : la spécification OpenAPI permet de déclarer des exigences de sécurité par opération et les entrées OAuth listent les portées requises. Importez ces informations comme configuration, puis vérifiez-les face à ce que le service émet et accepte réellement. Le fichier de description n'applique rien à lui seul.

Les nouvelles tentatives font aussi partie de la décision. Une expiration réseau après un POST ne dit pas si le serveur a appliqué l'action. Ne relancez pas automatiquement une requête dangereuse et non idempotente, sauf si l'API fournit un mécanisme d'idempotence ou si le client sait que la première requête n'a pas été appliquée. L'idempotence peut sécuriser la relance, mais n'autorise pas l'action originale.

## Les détails déterminent l'effet réel

Deux requêtes ayant le même identifiant, la même origine, le même chemin et la même méthode peuvent produire des effets opposés. Le corps, la requête et certains en-têtes contiennent souvent l'opération qui importe.

Un point de facturation peut accepter `POST /v1/subscriptions/update` pour réduire le nombre de sièges ou passer à une offre coûteuse. Un point de dépôt peut utiliser une seule route de mutation avec un champ `operation` pour archiver, transférer ou supprimer. Une recherche peut révéler des dossiers cachés ou supprimés si `include_deleted=true` apparaît. Autoriser la route sans contraindre ces champs autorise tous ses modes.

Les en-têtes méritent la même prudence. L'exécuteur doit contrôler `Authorization`, `Proxy-Authorization`, `Host` et chaque en-tête personnalisé portant un identifiant. Il doit normalement refuser que l'agent les définisse. Les en-têtes qui sélectionnent un compte, usurpent un utilisateur, remplacent une méthode, demandent une exécution asynchrone ou transportent des conditions d'écriture peuvent changer l'autorité ou l'effet. Transmettre des en-têtes arbitraires revient à cacher un second outil générique dans le premier.

Le type de contenu contrôle l'analyse. Si la politique inspecte du JSON mais que le client peut envoyer un formulaire, du contenu multipart ou des octets compressés, l'agent peut placer le champ sensible hors de l'analyseur. Imposez le type annoncé, fixez une taille maximale avant mise en mémoire, refusez les champs dupliqués ou ambigus et autorisez exactement les octets que l'exécuteur enverra. Valider un objet puis en sérialiser un autre crée des écarts.

Le traitement de la réponse appartient aussi à la frontière, même s'il ne constitue pas une coordonnée de permission pour l'effet sortant. Limitez la taille, classez les types et traitez les instructions renvoyées comme des données non fiables. Un GET autorisé peut récupérer une page contenant une injection de prompt, des secrets ou une charge énorme. L'autorisation de sortie ne rend pas la réponse sûre pour l'agent.

Les schémas de corps exacts coûtent cher à maintenir, alors concentrez cet effort sur les routes puissantes. Pour une lecture peu risquée, interdire le corps et limiter les noms de requête peut suffire. Pour les changements de compte, déploiements, renouvellements de secrets, mouvements d'argent ou suppressions, validez les champs d'objet, locataire, montant, environnement et transition demandée. Si l'API propose un point ou un identifiant plus étroit, préférez-le à une règle locale complexe.

## L'approbation doit décrire l'action résolue

Une bonne demande d'approbation montre ce que l'exécuteur fiable enverra après normalisation, pas l'appel proposé par le modèle. La personne qui valide a besoin du libellé de l'identifiant, du compte ou locataire, de la destination résolue, de la méthode, du chemin, des champs importants et du comportement des redirections.

Cela ne signifie pas déverser du JSON brut dans une boîte de dialogue. Une charge brute cache le champ dangereux parmi les dates et les valeurs par défaut. Affichez d'abord un résumé, puis permettez d'inspecter la requête canonique. Une approbation de déploiement pourrait annoncer que l'identifiant `production-deployer` créera la version `2026.07.24` dans le locataire de production, puis montrer l'hôte, le chemin POST et les différences du corps.

Liez l'approbation à une empreinte de l'action canonique. Si l'hôte, la méthode, le chemin, les en-têtes protégés ou le corps changent après le clic, calculez une autre empreinte et exigez une nouvelle décision. Cela ferme un écart courant entre contrôle et utilisation, lorsque l'interface approuve un objet puis qu'un middleware suit une redirection, ajoute des valeurs ou modifie le corps.

Choisissez consciemment la limite de réutilisation. Une approbation de session peut convenir à des lectures répétées avec un identifiant et une destination étroits. Un identifiant capable de supprimer des ressources doit exiger une approbation par appel ou une opération préautorisée bien plus limitée. Ne remplacez pas la réduction des droits par des alertes répétées. Les humains finissent par cliquer sans lire des cartes toutes identiques.

La réponse par défaut doit être le refus lorsque le contexte nécessaire manque. Un hôte non résolu, un type inconnu, un remplacement de méthode non reconnu ou un corps que la politique ne sait pas analyser ne sont pas des requêtes à faible risque. Le système ne sait tout simplement pas décrire ces actions avec précision.

## Un plan anodin peut devenir un appel dangereux

L'échec commence souvent par une tâche ordinaire et une requête dont le sens change en traversant plusieurs couches. Supposons qu'un agent doive lire un ticket et publier une courte note d'état. L'hôte autorise l'appelant générique pour la session, car les deux opérations utilisent le même service de projet.

L'identifiant de lecture échoue sur le POST, donc le planificateur choisit une autre entrée dont la description mentionne l'automatisation du projet. Ce jeton peut aussi administrer les webhooks. Un commentaire récupéré demande à l'agent de notifier un callback externe et le planificateur fournit cette URL comme cible. L'outil a toujours son approbation de session, le nom de l'identifiant semble pertinent et la méthode reste POST. La permission de l'outil ne voit aucune frontière franchie.

Le callback initial renvoie une redirection 307 vers une adresse privée. Une bibliothèque HTTP pratique conserve le corps POST pour ce statut. Elle peut supprimer un en-tête Authorization généré automatiquement, mais un en-tête personnalisé ajouté par le code peut survivre si le client ne le traite pas. La requête porte maintenant l'autorité du projet et le contenu du ticket vers une destination jamais examinée. Même si le service privé refuse l'identifiant, le corps peut divulguer des données ou déclencher une action sans authentification.

Quatre contrôles coordonnés arrêtent la séquence à des endroits différents. L'identifiant de lecture ne peut pas autoriser POST. L'identifiant d'automatisation ne peut pas atteindre un callback inconnu. La redirection exige une nouvelle décision de destination. Les règles de requête refusent un callback arbitraire dans le corps. Aucun contrôle ne porte toute la défense et les noms rassurants n'en portent aucune partie.

C'est pourquoi je déconseille d'approuver un appelant générique une fois par session. Ce modèle est populaire parce que les demandes répétées interrompent le travail et qu'un nom stable semble décrire un risque stable. Ce n'est pas le cas. Limitez l'autorisation de session à un identifiant et une destination avec des opérations contraintes, puis redemandez dès qu'une coordonnée change.

La même séquence peut échouer sans commentaire malveillant. Un agent peut déduire un point d'accès d'une ancienne documentation, copier une URL depuis une erreur ou choisir un identifiant au nom proche après un 403. Ce sont des comportements normaux de récupération. La sécurité doit les prévoir et maintenir la tentative dans l'autorisation originale.

La canonicalisation doit précéder la comparaison. Décodez les segments encodés selon une règle documentée, refusez les segments à points qui sortent du préfixe permis, normalisez le port effectif et décidez comment l'API traite les noms de requête répétés. Si la politique voit `/v2/issues/%2e%2e/admin` comme un chemin de tickets et le serveur le résout en `/v2/admin`, ils autorisent des ressources différentes. Refusez les formes ambiguës au lieu de deviner l'interprétation de chaque intermédiaire.

Injectez l'identifiant après ce travail et aussi près de la transmission que possible. Construisez la requête canonique, autorisez-la, liez son empreinte, ouvrez la connexion approuvée puis ajoutez le secret dans l'exécuteur fiable. Si un middleware peut réécrire l'hôte, la méthode ou le corps après l'injection, incluez sa sortie dans l'autorisation ou retirez-lui cette liberté.

Les échecs ont aussi besoin d'un chemin fermé. Si l'identifiant choisi reçoit 401 ou 403, renvoyez ce résultat à l'agent sans essayer automatiquement toute la chambre forte. Le repli transforme un échec limité en découverte de privilèges. La prochaine tentative doit nommer un autre identifiant et passer une nouvelle décision selon son autorité et son compte.

Traitez également les erreurs du service comme des données. Les API renvoient souvent des URL, des noms d'opérations ou des actions suggérées. Le planificateur peut raisonnablement utiliser ce texte, mais il n'acquiert aucune autorité parce qu'il vient d'un hôte approuvé. La requête suivante subit les mêmes contrôles de destination et de détails.

Testez ce comportement comme une séquence, pas seulement par requête isolée. Commencez par une lecture permise, renvoyez une redirection préparée ou une suggestion d'erreur, laissez le planificateur produire la suite et vérifiez que la session n'élargit rien. Faites ensuite échouer l'identifiant prévu et assurez-vous que l'exécuteur n'en essaie pas un autre. Ces tests détectent un héritage de permissions qu'un test unitaire sur une seule règle manque.

## Les journaux doivent garder la décision et l'appel

Les journaux d'invocation sont trop grossiers pour enquêter. Noter que l'agent a appelé `http_request` laisse les questions centrales sans réponse : quel identifiant, quelle destination, quelle opération et quelle correspondance entre requête approuvée et requête envoyée.

Enregistrez l'identité de session ou du processus, l'identifiant d'appel, l'identifiant de l'autorité, la destination canonique, l'adresse résolue, la méthode, un résumé expurgé, la version de politique, l'identité de validation, l'empreinte d'action, le statut, la durée et le résultat. Gardez les secrets et corps sensibles hors des journaux courants. Pour les champs nécessaires comme preuve, stockez une empreinte ou chiffrez-les avec des contrôles séparés.

Journalisez les refus autant que les réussites. Une série de tentatives rejetées vers des adresses privées ou d'autres hôtes peut révéler une injection de prompt ou un mauvais planificateur avant tout effet externe. Distinguez refus de politique, refus humain, panne réseau, rejet du service et annulation locale pour ne pas confondre connexion échouée et action bloquée.

L'agent actif doit avoir du mal à réécrire les preuves. Stockage en ajout seul, auteurs limités et contrôles d'intégrité gardent le journal utile après la mise en cause d'une machine ou d'un processus. Sallyport consigne sessions et appels dans un même journal d'audit chiffré, chaîné par hachage et illisible par son auteur, puis `sp audit verify` vérifie la chaîne hors ligne sur le texte chiffré sans clé. Il garde aussi les identifiants API et SSH dans une chambre forte chiffrée et exécute les actions sans révéler les secrets à l'agent.

## Remplacez l'autorisation large dans l'exécuteur

Vous pouvez garder une interface HTTP générique sans garder une autorité générique. Déplacez l'injection et l'application des règles dans un exécuteur fiable, puis demandez au modèle une requête sans identifiant accompagnée d'une référence opaque.

Une migration pratique commence par l'inventaire des appels réels. Regroupez les requêtes récentes par identifiant, origine, méthode et opération. Les identifiants larges et destinations qui n'apparaissent jamais ensemble peuvent être séparés. Les routes portant plusieurs modes destructeurs dans un corps ont besoin de contraintes propres ou d'identifiants distincts.

Placez ensuite le trafic existant en évaluation informative. Canonicalisez chaque requête, résolvez la destination et montrez si l'autorisation proposée l'accepterait ou non, sans changer encore l'exécution. Examinez les correspondances surprenantes. Une règle qui semble autoriser la lecture de tickets peut aussi admettre les exports, les éléments supprimés ou un autre locataire via un hôte partagé.

Appliquez d'abord les frontières simples : identifiants détenus par l'exécuteur, origines HTTPS exactes, aucune redirection automatique, méthodes explicites et refus des adresses privées sauf intégration précise. Ajoutez des contraintes de chemin, requête, en-têtes et corps autour des opérations à fort impact. Gardez une voie d'exception avec approbation par appel et entrée d'audit visible, sans retour silencieux à l'ancien outil illimité.

Enfin, testez la frontière avec des variations de requêtes valides. Changez le suffixe de l'hôte, le port, la réponse DNS, la redirection, la référence d'identifiant, la méthode, le type, le locataire, l'opération et le chemin encodé. Chaque variation doit correspondre à une autorisation volontaire ou échouer avant l'ajout du secret. Vérifiez aussi que les octets journalisés, approuvés et envoyés partagent la même empreinte d'action.

La liste des outils reste utile pour aider l'agent à choisir de bonnes opérations. Ce n'est simplement pas l'endroit où arrêter l'autorisation. Gardez l'interface pratique si les développeurs l'apprécient, mais exigez une permission pour chaque effet réseau une fois l'identifiant, la destination, la méthode et les détails connus.
