# Minimiser les réponses API pour sécuriser le contexte des agents IA

Les agents IA n'ont pas besoin d'une copie de chaque objet qu'ils manipulent. Ils ont besoin de suffisamment d'informations pour prendre la décision suivante, exécuter l'action et rendre compte du résultat. Lorsqu'une API renvoie à un agent un dossier client complet, une facture, un ticket, un paramètre de dépôt ou un incident, alors que celui-ci n'a besoin que d'un identifiant et d'un état, l'API a déjà aggravé le problème d'exposition des données.

C'est facile à manquer, car la requête peut être en lecture seule, authentifiée et envoyée via TLS. Rien de tout cela ne change ce qui se passe ensuite. La réponse peut entrer dans une transcription d'agent, une trace d'outil, une requête de modèle, un cache local, un rapport de bug ou une file de revue humaine. Si l'agent peut la lire, partez du principe que le contexte de l'agent la contient désormais.

La solution pratique est la minimisation des réponses API : définir la plus petite réponse utile pour chaque tâche de l'agent, rendre cette forme facile à demander et faire des données étendues un cas exceptionnel soumis à un examen humain. Il ne s'agit pas de rendre le JSON plus élégant. Il s'agit de réduire le nombre d'endroits où des données personnelles, financières et opérationnelles peuvent apparaître après un appel automatisé ordinaire.

## Une lecture authentifiée peut tout de même divulguer beaucoup trop de données

L'accès en lecture limite les écritures. Il ne limite pas la copie, le résumé, la citation ni l'envoi accidentel de données vers un autre outil. Les équipes qualifient souvent un agent de « lecture seule », comme si cela suffisait à régler le problème. Cela ne règle qu'une catégorie de risques.

Prenons un agent chargé d'identifier les factures en retard et d'ouvrir une tâche de suivi. Il a besoin de l'identifiant de la facture, de l'identifiant du compte, de la date d'échéance, du montant, de la devise et de l'état du recouvrement. Un endpoint de facture classique peut aussi renvoyer les adresses de facturation et de livraison, les identifiants fiscaux, les références du prestataire de paiement, les descriptions des lignes, la note interne d'un utilisateur de la finance et l'historique complet des paiements. Chaque champ supplémentaire crée une information que l'agent peut répéter alors qu'il n'en a pas besoin.

Les données opérationnelles posent le même problème. Une tâche qui vérifie si un déploiement est terminé peut nécessiter le nom du service, l'identifiant du build, l'état et la catégorie de l'échec. Elle a rarement besoin d'un export complet de l'environnement contenant des noms d'hôtes, des adresses internes, la sortie de commandes, des commentaires d'incident ou une configuration sans rapport.

La distinction souvent brouillée est importante : l'autorisation indique si un appelant peut accéder à une ressource ; la minimisation indique quelle partie de cette ressource l'appelant reçoit pour ce travail précis. Un jeton autorisé à lire `invoice:123` peut donc être correctement autorisé et recevoir malgré tout une représentation dangereuse de cette facture.

La RFC 9110 de l'IETF décrit les représentations comme des informations destinées à refléter l'état actuel ou souhaité d'une ressource. Elle n'impose pas une représentation maximale unique pour chaque ressource. C'est une marge de conception utile. Une ressource peut avoir une représentation résumée, une représentation opérationnelle et une représentation financière, à condition que l'API définisse clairement chaque contrat.

Ne comptez pas sur une instruction de prompt telle que « ignore les données personnelles ». Les prompts influencent le comportement ; la forme de la réponse contrôle l'exposition. Si un endpoint envoie une adresse personnelle, l'agent l'a déjà reçue avant de décider de l'ignorer.

## Commencez par le travail que l'agent doit accomplir

Un contrat de réponse sûr commence par la décision que l'agent doit prendre, pas par un modèle de base de données existant. Décrivez la tâche en une phrase, puis listez les faits qui modifient l'action. Tout le reste doit justifier sa présence.

Par exemple, un agent qui relance des jobs de build échoués peut avoir besoin de cette réponse :

```json
{
  "job_id": "job_4821",
  "state": "failed",
  "retryable": true,
  "failure_class": "transient_dependency",
  "attempts_remaining": 1
}
```

Il n'a pas besoin du journal de build complet pour décider qu'une nouvelle tentative est autorisée. Si une personne a besoin de diagnostics plus tard, fournissez un endpoint distinct, destiné à un public plus restreint, avec une raison explicite pour le consulter. Un endpoint de journaux doit aussi prendre en charge des plages limitées, car les journaux complets contiennent plus souvent qu'on ne l'admet des jetons, des saisies client, des chemins et des fragments de configuration.

Construisez une petite matrice des tâches avant de modifier les endpoints. Elle force les discussions qui restent sinon vagues :

| Tâche de l'agent | Champs de décision | Champs d'action | Champs exclus par défaut |
| --- | --- | --- | --- |
| Créer un suivi pour le support | identifiant du ticket, priorité, catégorie | identifiant du compte, file d'affectation | contenu du message, pièces jointes, notes internes |
| Relancer un job | identifiant du job, état, relançable | jeton de relance ou identifiant du job | journal complet, valeurs d'environnement |
| Signaler une facture en retard | identifiant de la facture, date d'échéance, montant, état | identifiant du compte | adresse, données fiscales, références de paiement |
| Vérifier l'état d'un service | identifiant du service, état, classe d'erreur | identifiant de l'incident | détails de l'hôte, diagnostics bruts |

Un champ ne doit figurer dans la réponse que s'il modifie la branche choisie par l'agent, apparaît dans la requête d'action ou doit apparaître dans son rapport destiné à l'utilisateur. « Cela pourrait être utile plus tard » n'est pas une bonne raison. C'est ainsi que les endpoints de liste se retrouvent avec cinquante champs sans que personne ne sache lesquels sont utilisés.

L'exercice fait aussi apparaître les champs qui devraient être calculés plutôt que divulgués. Un agent n'a pas besoin d'un dossier de paie pour savoir si l'approbation d'une dépense nécessite un responsable. Retournez `approval_required: true`. Il n'a pas besoin de connaître tous les droits pour savoir si un déploiement peut continuer. Retournez `deployment_permitted: false` et un code de motif stable.

Ce n'est pas de la sécurité par l'obscurité. C'est un contrat API conçu volontairement pour fournir aux appelants le résultat dont ils ont besoin sans leur remettre l'enregistrement sous-jacent.

## Les objets par défaut doivent être des résumés, pas des lignes de base de données

La conception la plus fiable donne aux appels ordinaires de liste et de recherche un résumé sûr par défaut. Rendez les représentations détaillées explicites, soumises à une autorisation distincte et peu fréquentes. Demander à chaque appelant de se souvenir d'une option de requête restrictive finira par échouer, surtout lorsqu'une bibliothèque ajoute une méthode pratique qui l'omet.

Un résumé client peut ressembler à ceci :

```json
{
  "id": "cus_7f31",
  "display_name": "Northwind Parts",
  "account_state": "active",
  "open_invoice_count": 2,
  "support_tier": "standard"
}
```

Ne retournez pas `email`, `phone`, l'adresse postale, l'identifiant fiscal, les métadonnées de l'instrument de paiement ou les notes libres simplement parce qu'une ligne client les contient. Certains de ces champs peuvent être nécessaires à une application de facturation. Ils n'ont pas leur place dans un contrat de résumé utilisé par un agent opérationnel.

Deux approches fonctionnent. Un endpoint de résumé séparé, comme `GET /customers/{id}/summary`, est direct et facile à auditer. Un paramètre de projection, comme `GET /customers/{id}?view=summary`, peut convenir s'il repose sur un ensemble fixe et documenté de vues. Les deux valent mieux qu'un endpoint qui renvoie tout et demande à chaque client d'ignorer ce dont il n'avait pas besoin.

Évitez un commutateur générique `expand=*` ou `include=all` pour les identifiants destinés aux agents. Il devient la solution la plus simple pendant le débogage, puis reste en production parce que sa suppression semble risquée. Si une représentation détaillée est nécessaire, nommez-la d'après la tâche : `view=collections`, `view=deployment_status` ou `view=case_triage`. Les noms de tâches obligent à examiner la conception. « Tout » permet de l'éviter.

Une objection courante est que les vues séparées dupliquent le code. Elles dupliquent effectivement une partie du code de conversion. Ce coût est faible comparé à l'enquête nécessaire lorsqu'une transcription d'outil contient un numéro fiscal ou une note interne d'incident. La couche de conversion est aussi l'endroit où vous documentez la responsabilité et testez la promesse selon laquelle une vue d'agent exclut les colonnes sensibles.

## La sélection des champs doit utiliser une liste d'autorisation, pas une astuce de parsing

Un paramètre `fields` peut réduire efficacement les réponses, mais uniquement si le serveur le traite comme une liste d'autorisation stricte. Un parseur permissif transforme une fonction pratique en interface d'extraction de données.

Cette requête est raisonnable :

```text
GET /v1/invoices?state=overdue\u0026fields=id,account_id,due_date,amount,currency,collection_state\u0026limit=25
```

Le serveur doit renvoyer uniquement les champs autorisés pour cet endpoint et ces identifiants. Si un appelant demande `billing_address` ou `payment_reference`, refusez la requête avec une erreur claire. N'ajoutez pas silencieusement de champs sensibles et n'acceptez pas de chemins imbriqués arbitraires tels que `customer.*`.

Un contrat de réponse peut préciser le comportement :

```json
{
  "error": {
    "code": "unsupported_field",
    "message": "Field 'payment_reference' is not available in the agent invoice view",
    "allowed_fields": [
      "id",
      "account_id",
      "due_date",
      "amount",
      "currency",
      "collection_state"
    ]
  }
}
```

L'erreur elle-même doit être maîtrisée. N'incluez jamais la valeur du champ refusé, les données voisines de l'enregistrement, une trace d'exécution, le texte brut d'une requête provenant d'un autre service ni une erreur de base de données. Les corps d'erreur deviennent souvent une seconde API accidentelle, surtout lorsque les ingénieurs les rendent détaillés pour accélérer la résolution d'un incident.

GraphQL mérite la même attention. Les clients ne peuvent en principe demander que les champs qu'ils nomment, ce qui aide, mais un schéma peut tout de même exposer des champs sensibles, les relations imbriquées peuvent multiplier les enregistrements et les alias peuvent rendre une requête difficile à analyser. Définissez des limites de profondeur et de complexité, désactivez ou restreignez l'introspection lorsque l'environnement le justifie et autorisez les champs plutôt que les seuls objets de premier niveau. Surtout, créez un schéma pour agents ou des requêtes persistées pour les quelques tâches approuvées. Un schéma large accompagné d'une consigne polie ne constitue pas une interface étroite.

L'OWASP API Security Top 10 signale les problèmes d'autorisation au niveau des propriétés des objets. Le problème est souvent présenté comme la récupération par un appelant d'une propriété à laquelle il ne devrait jamais accéder. L'utilisation par un agent ajoute un autre mode d'échec : l'appelant peut techniquement y avoir accès, mais la tâche n'a pas besoin de cette propriété et ne devrait pas la diffuser dans le contexte du modèle. Conservez ces deux tests. Demandez « cet identifiant peut-il la lire ? », puis « pourquoi cette tâche en a-t-elle besoin maintenant ? »

## La pagination contrôle le volume, mais les filtres contrôlent la pertinence

Une réponse contenant dix enregistrements n'est pas automatiquement une petite réponse. Si chaque enregistrement contient un gros objet imbriqué ou un long champ textuel, la pagination ne fait que diviser la fuite en pages bien ordonnées.

Utilisez des filtres qui expriment le travail de l'agent. Un agent de recouvrement doit rechercher les factures en retard dans un état et une plage de dates définis. Il ne doit pas lister toutes les factures pour déterminer localement lesquelles sont pertinentes. Un agent de déploiement doit demander un service et sa version actuelle, pas interroger tous les environnements avant de parcourir le résultat.

La pagination par curseur nécessite également une forme de réponse soigneusement conçue. Un curseur doit être opaque et ne pas intégrer une adresse e-mail, un nom de compte, des valeurs de filtre non chiffrées ou une clé de base de données interne révélant l'ordre. Les clients placeront les curseurs dans des journaux et des tickets. Considérez-les comme des données qui circulent.

Conservez des limites de page prudentes pour les identifiants d'agents. Une petite limite ne réduit pas seulement l'utilisation de tokens. Elle crée un point d'arrêt où l'agent peut examiner un résumé, sélectionner un enregistrement pertinent et effectuer un appel de suivi ciblé. Cette séquence est plus sûre que le chargement de tout l'historique d'un compte parce qu'une tâche a commencé par les mots « examinez ce client ».

Ne confondez pas un endpoint de recherche avec l'autorisation de renvoyer tous les détails correspondants. Une recherche doit généralement retourner une carte de résultat : identifiant stable, libellé, état et éventuellement motif de correspondance. L'appelant peut ensuite récupérer une vue détaillée autorisée après avoir sélectionné un enregistrement. Ce modèle en deux appels est moins pratique qu'un énorme objet de résultat, mais il rend le transfert d'informations sensibles visible et vérifiable.

## Le texte libre et les enregistrements imbriqués ont besoin de leur propre frontière

Les champs structurés sont plus faciles à classer que les textes écrits par des humains. Les champs libres absorbent les noms, les numéros de téléphone, les identifiants collés par erreur, les accusations, les informations de santé, les conseils juridiques et les opinions internes. La `description` d'un ticket semble inoffensive lors d'une revue de schéma, jusqu'à ce qu'on lise une semaine réelle de tickets.

Pour les flux autonomes, considérez par défaut les commentaires, notes, descriptions, pièces jointes, journaux et corps de messages comme sensibles. Retournez plutôt une catégorie, une classification courte générée par le serveur ou un nombre, lorsque cela suffit pour choisir une action. Par exemple, un agent peut avoir besoin de `has_customer_reply: true` et de `latest_message_at`, pas du message lui-même.

Ne demandez pas au modèle de masquer un texte arbitraire après sa récupération. Cette approche est populaire parce qu'elle semble permettre de conserver un endpoint large unique. Elle échoue de deux façons. Premièrement, le contenu brut est déjà entré dans le contexte de l'agent avant le masquage. Deuxièmement, le masquage produit par un modèle est probabiliste, si bien qu'un nom partiel, un numéro de compte ou une citation peut subsister.

Si une tâche nécessite réellement du texte, imposez des limites strictes à la requête. Récupérez un message par son identifiant plutôt qu'un fil complet. Demandez une limite de caractères connue et appliquée par le serveur. Supprimez le contenu des pièces jointes, sauf si un utilisateur a approuvé cette récupération précise. Indiquez ce que le client recevra en cas de troncature, par exemple `content_truncated: true`, afin que l'agent n'invente pas les détails manquants.

Les données imbriquées créent une version plus discrète du même problème. Une réponse qui inclut `customer`, `contacts`, `invoices`, `payments` et `events` peut sembler être un seul objet dans le code de l'application. Du point de vue de l'exposition, il s'agit d'un ensemble de jeux de données indépendants. Exigez des endpoints séparés ou des expansions explicites autorisées pour chaque relation. Testez ensuite la requête ordinaire la plus défavorable, pas seulement le scénario idéal qui renvoie un enregistrement peu fourni.

## La gestion des erreurs et l'observabilité peuvent recréer la fuite

Les équipes réduisent souvent la réponse réussie, puis copient la charge utile originale dans les journaux de débogage, les attributs de traçage, les files de nouvelle tentative et les rapports d'exception. Les données ont changé de place, mais leur exposition n'a pas diminué.

Inspectez tout le chemin de l'appel. Examinez au minimum le wrapper de l'outil agent, le mode de débogage du client HTTP, l'enregistreur de requêtes, la configuration du traçage distribué, le service de remontée d'erreurs, la file de tâches, le stockage local des transcriptions et le processus de support. Les composants qui affirment ne journaliser « que les métadonnées » méritent un test direct, pas une confiance aveugle.

Faites passer un enregistrement témoin dans un environnement hors production. Donnez-lui des valeurs factices distinctives dans les champs qui ne doivent jamais atteindre le contexte de l'agent, comme `CANARY_BILLING_ADDRESS_927` et `CANARY_INTERNAL_NOTE_927`. Exécutez la véritable tâche de l'agent, puis recherchez ces chaînes dans tous les journaux et magasins de traces autorisés. Répétez l'opération pour une requête en échec, un délai d'attente et une réponse malformée. Les tests du chemin nominal ne détectent pas la plupart des captures accidentelles de charges utiles.

Un journal d'appel utile enregistre l'action sans dupliquer le contenu :

```json
{
  "time": "2025-03-08T14:03:12Z",
  "caller": "release-agent",
  "operation": "GET /v1/jobs/{id}/retry-status",
  "resource_id": "job_4821",
  "response_view": "retry_status",
  "field_set": ["job_id", "state", "retryable", "failure_class"],
  "result_count": 1,
  "outcome": "200"
}
```

N'enregistrez les identifiants que si vos propres règles de conservation et d'accès l'autorisent. Dans les systèmes présentant un risque élevé, stockez plutôt une référence liée ou un identifiant de corrélation à courte durée de vie. Un hachage peut aussi fuir si la valeur d'origine appartient à un domaine réduit et facile à deviner. Ne considérez donc pas le hachage comme un masquage sans examiner ce qu'un attaquant peut énumérer.

Nettoyez les messages d'erreur sortants à la frontière du serveur. Un pilote de base de données peut exposer un fragment SQL ayant échoué. Un service en amont peut envoyer un enregistrement complet dans une enveloppe d'erreur. Votre API doit convertir ces échecs en codes publics stables, conserver les diagnostics détaillés dans un espace restreint et n'inclure par défaut aucun corps de réponse dans les erreurs destinées aux agents.

## Séparez la capacité de l'agent de la divulgation dans la passerelle

Une passerelle d'action doit conserver l'identifiant et exécuter la requête, mais elle ne doit pas considérer qu'une réponse accessible avec cet identifiant est adaptée au contexte de l'agent. L'isolation des secrets et la minimisation des réponses résolvent deux aspects différents du même appel.

Sallyport conserve les secrets API et SSH dans son coffre chiffré et renvoie les résultats des actions à l'agent sans exposer le secret lui-même. Cela protège l'identifiant, mais le propriétaire de l'API doit toujours décider si le résultat contient un dossier de compte, une sortie de commande ou un détail opérationnel inutile.

Donnez à chaque tâche d'agent un modèle de requête nommé lorsque c'est possible. Le modèle fixe la méthode, l'hôte, la forme du chemin, les champs de requête autorisés, la limite de page et la vue de réponse acceptée. Un modèle d'état de déploiement peut autoriser un seul identifiant de service et renvoyer un court objet d'état. Il ne doit pas accepter une URL arbitraire et une expression `fields` arbitraire simplement parce qu'il est facile de les transmettre.

C'est là qu'une conception fondée sur un proxy large pose problème. Un relais HTTP générique peut être utile pour le développement, mais il ne peut pas exprimer la différence entre « vérifier ce déploiement » et « télécharger tous les journaux d'artefacts ». Placez l'intention dans l'action appelable. Lorsqu'une nouvelle tâche a besoin de davantage de données, exigez une modification de l'API ou un nouveau modèle. Cette friction est précisément le but : quelqu'un doit expliquer pourquoi ces données supplémentaires doivent entrer dans le contexte.

L'approbation humaine reste utile pour les exceptions. Si un agent a besoin du contenu d'un seul message de support pour résoudre un dossier, une personne peut approuver cet appel précis après avoir vu sa destination et son périmètre. L'approbation ne doit pas devenir le substitut habituel aux réponses étroites. Les personnes approuvent rapidement les cartes familières, surtout pendant un incident, et les approbations répétées les habituent à ne plus lire.

## Testez l'absence des champs dans le cadre du contrat

La plupart des tests API vérifient que les champs attendus existent. Les API destinées aux agents ont également besoin de tests vérifiant que les champs interdits n'existent pas, y compris lorsqu'une branche de secours est utilisée.

Conservez un test de liste de refus à côté de chaque vue de réponse. Utilisez des noms de champs réalistes, y compris des relations imbriquées et du texte libre. Le test doit échouer si la sérialisation en ajoute un plus tard via une valeur par défaut d'ORM, un DTO partagé ou une relation chargée automatiquement.

```python
forbidden = {
    "email",
    "phone",
    "billing_address",
    "tax_id",
    "payment_reference",
    "internal_note",
    "attachments",
}

body = get_invoice_agent_view("inv_1042")
assert forbidden.isdisjoint(body.keys())
assert "customer" not in body
assert "events" not in body
```

Ce test simple ne détecte que les champs de premier niveau. Ajoutez des tests de sérialisation qui parcourent toute l'arborescence JSON et testez séparément les endpoints de liste, de recherche, d'erreur et d'export. La fuite la plus importante vient souvent d'une réponse de collection qui réutilise un sérialiseur détaillé complet pour économiser quelques lignes de code.

Les tests de contrat doivent aussi vérifier des limites de taille de réponse. Une limite stricte en octets ne conviendra pas à tous les objets, mais un plafond raisonnable vous alerte lorsqu'un développeur ajoute un champ textuel sans limite ou une relation. Associez-le à une fixture contenant de longues notes et de nombreux enregistrements enfants, sinon le test donnera une fausse impression de sécurité.

Examinez les changements en posant trois questions directes : quelle tâche d'agent a besoin de ce champ ? Quelle vue de réponse l'inclut ? Quel test prouve qu'il reste absent partout ailleurs ? Si l'auteur ne peut pas répondre, n'intégrez pas le champ à un endpoint largement appelable.

## Rendez la récupération des détails exceptionnels visible et temporaire

Certains travaux nécessitent réellement des détails sensibles. L'examen d'une fraude, la récupération d'un compte, une investigation de sécurité et un dossier de support difficile ne peuvent pas fonctionner entièrement avec des résumés. Il ne faut pas prétendre le contraire. Il faut rendre cette récupération explicite, brève et limitée à l'enregistrement exact.

Utilisez un endpoint ou une action distincte qui reçoit un identifiant stable et un objectif déclaré. Retournez la plus petite portion nécessaire, par exemple un seul champ d'un paiement contesté ou un message client sélectionné. N'accordez pas l'accès à un export complet du compte parce qu'un seul paiement contesté figure dans le même dossier.

Pour les récupérations présentant un risque plus élevé, demandez à une personne d'approuver l'appel individuel et consignez l'appelant, l'objectif, la vue, la référence de l'enregistrement et le résultat. Conservez la trace d'audit séparément du corps de réponse sensible. Vous devez savoir qu'une récupération a eu lieu sans créer une autre copie facilement accessible de l'information.

Une API mature rend le chemin sûr facile à suivre. Les vues résumées doivent avoir des noms clairs, une bonne documentation et des champs stables. Les endpoints détaillés et larges doivent sembler délibérés, car ils impliquent davantage de responsabilités. Si votre agent a régulièrement besoin d'un champ sensible, ne normalisez pas l'exception. Revoyez la conception de la tâche et demandez-vous si une décision côté serveur ou une valeur dérivée et masquée ferait l'affaire.

Le premier audit utile porte généralement sur un endpoint de liste, pas sur celui que tout le monde redoute déjà. Capturez une tâche réelle de l'agent, marquez chaque champ qu'il a utilisé et comparez cette liste à la réponse reçue. La partie inutilisée est votre prochaine modification API.
