# Les limites de taille décodée protègent-elles un agent contre les API compressées ?

Une réponse compressée n'est pas petite parce qu'elle arrive sous une petite taille. Elle ne l'est que jusqu'à ce que votre client la décode, l'analyse, la stocke, la journalise et, peut-être, la transmette à un agent qui la considère comme une preuve. Si vous imposez une limite uniquement aux octets reçus du réseau, vous avez mesuré la partie la moins intéressante de la transaction.

J'ai vu des équipes ajouter une limite HTTP apparemment raisonnable de 5 Mo, se féliciter du test, puis découvrir que quelques kilo-octets de données répétées compressées pouvaient encore produire une réponse assez grande pour bloquer un processus ou polluer l'exécution d'un agent. Aucun exploit sophistiqué n'était nécessaire. Un endpoint renvoyait un rapport dont un champ se répétait bien plus que prévu, la compression faisait remarquablement son travail, et le client faisait ce qu'il ne fallait pas : il accumulait le corps décodé avant de décider s'il le voulait.

Les limites de taille décodée doivent s'appliquer au flux décodé, avant qu'un analyseur, un journal, un cache ou le contexte d'un agent ne consomme le corps. Gardez une limite distincte pour les octets sur le réseau, car elle arrête les transferts lents ou anormalement gros. Ces deux limites répondent à des défaillances différentes. Les confondre transforme une vérification défensive en faux sentiment de sécurité.

## La taille sur le réseau ne raconte qu'une partie de l'histoire

Content-Length décrit normalement le corps du message HTTP tel qu'il est transféré. Quand la réponse comporte Content-Encoding: gzip ou Content-Encoding: br, ce nombre décrit donc des octets compressés. Un corps de 40 Ko peut atteindre des dizaines ou des centaines de mégaoctets après décodage si l'entrée contient suffisamment de répétitions. Le ratio exact n'est pas l'essentiel. Tout ratio qui dépasse votre budget d'allocation ou de contexte suffit à créer un problème.

La RFC 9110 considère l'encodage de contenu comme une transformation appliquée à une représentation. Cette formulation compte. La représentation utilisée par votre application est la forme décodée, tandis que le transport véhicule une forme encodée. Un client qui fonde sa limite applicative sur la forme de transport a placé sa protection du mauvais côté de la transformation.

Transfer-Encoding complique encore la situation. Un transfert segmenté n'offre pas de Content-Length final utile sur lequel le client puisse s'appuyer, et une réponse HTTP/2 ou HTTP/3 n'utilise pas le transfert segmenté de la même manière. Même lorsqu'un en-tête est présent, un serveur peut envoyer une valeur erronée. Servez-vous de l'en-tête comme indice de rejet précoce, jamais comme preuve que le corps est sûr.

Trois valeurs méritent d'être enregistrées pour une réponse : les octets lus sur le réseau, les octets décodés produits et les octets conservés pour l'appelant. Elles peuvent parfois être identiques. Elles ne le sont souvent pas. Une réponse JSON peut se décoder en 12 Mo, demander bien plus de 12 Mo lors de l'analyse, puis devoir être ramenée à 64 Ko avant qu'un agent puisse l'utiliser sans risque.

Cette distinction évite aussi une discussion fréquente autour d'un réglage unique de « taille maximale de réponse ». Un ingénieur parle des octets du socket. Un autre parle des octets décodés. Un troisième parle du texte inséré dans un résultat d'outil. Donnez à chaque limite son propre nom et appliquez-la à la frontière qu'elle décrit.

## Décodez avant toute mise en mémoire non bornée

L'ordre sûr est simple : limitez le flux de réponse brut, choisissez et initialisez le décodeur, limitez le flux décodé, puis analysez ou conservez uniquement ce dont l'appelant a besoin. N'appelez pas une méthode pratique qui lit toute la réponse décodée dans une tranche d'octets avant d'en vérifier la longueur. À ce stade, le décodeur a déjà consommé la mémoire que vous cherchiez à préserver.

Pour une réponse gzip en Go, le limiteur décodé doit entourer le lecteur gzip. Cet assistant lit volontairement un octet au-delà de la quantité autorisée. Sans cet octet supplémentaire, une réponse dont la taille réelle est exactement la limite ne se distingue pas d'une réponse plus grande coupée à cette limite.

```go
var ErrDecodedBodyTooLarge = errors.New("decoded response exceeds limit")

func readGzipBody(r io.Reader, limit int64) ([]byte, error) {
    zr, err := gzip.NewReader(r)
    if err != nil {
        return nil, err
    }
    defer zr.Close()

    bounded := &io.LimitedReader{R: zr, N: limit + 1}
    body, err := io.ReadAll(bounded)
    if err != nil {
        return nil, err
    }
    if int64(len(body)) > limit {
        return nil, ErrDecodedBodyTooLarge
    }
    return body, nil
}
```

Appliquez aussi une limite brute avant `gzip.NewReader`. Elle ne remplace pas la limite décodée. Elle empêche un pair d'envoyer un flux compressé énorme et limite le travail accepté par le client avant que le décodeur ait suffisamment d'entrée pour progresser.

Ne tronquez pas silencieusement pour continuer. Un document JSON tronqué échoue généralement à l'analyse, mais un protocole texte ou à lignes peut sembler crédible. Si vous conservez un aperçu à des fins de diagnostic, indiquez qu'il s'agit d'un aperçu dans un champ qui ne peut pas être confondu avec le corps complet. L'action elle-même doit échouer, car le client n'a pas obtenu de réponse complète et autorisée.

Un décodeur peut ne détecter une somme de contrôle corrompue qu'à la fin du flux. Lorsque la limite décodée est atteinte, arrêtez la lecture et rejetez la réponse. Il est inutile de terminer la vérification d'un corps surdimensionné que vous avez déjà décidé d'abandonner. Si une réponse reste sous la limite, lisez jusqu'à EOF et laissez le décodeur signaler normalement une troncature ou une corruption.

## Une réponse peut épuiser le contexte avant la mémoire

La protection de la mémoire est nécessaire, mais les outils d'agent ont un autre budget : la quantité de texte de résultat qui peut entrer sans risque dans la conversation de l'agent. Une réponse JSON de 2 Mo peut être inoffensive pour un ordinateur de bureau et rester un très mauvais résultat d'outil. Elle peut évincer la tâche, pousser l'agent à suivre des enregistrements sans intérêt ou forcer le modèle à raisonner sur une représentation partielle qui semble complète.

N'utilisez pas le budget de contexte comme prétexte pour relever la limite du corps décodé. Ils protègent des opérations différentes. La limite décodée permet au transport et à l'analyseur de finir en sécurité. La limite de présentation contrôle ce que l'agent reçoit après un décodage réussi et, le cas échéant, une analyse structurée.

Pour des données structurées, choisissez une projection plutôt que de couper des octets arbitrairement. Si un endpoint renvoie une liste d'enregistrements, conservez un nombre limité d'enregistrements et une quantité limitée de texte par champ. N'indiquez le nombre total d'enregistrements que si l'analyseur l'a obtenu sans conserver toute la liste. Dites que le résultat a été réduit et précisez la règle de sélection, par exemple « les 50 premiers enregistrements classés par horodatage ». Le résultat peut ainsi être audité et l'agent ne prendra pas un fragment pour une recherche exhaustive.

Pour du texte brut, conservez des lignes complètes quand c'est possible. Un aperçu de journal qui s'arrête au milieu d'une ligne est moins utile et peut masquer le champ qui l'explique. Fixez un budget d'octets, lisez ligne par ligne avec une limite de longueur de ligne, puis indiquez le nombre conservé et la raison pour laquelle du contenu supplémentaire a été omis. Un lecteur de lignes sans limite de taille de jeton ne fait que déplacer le problème d'allocation dans un autre assistant.

C'est ici que l'idée selon laquelle « le modèle peut le résumer » s'effondre. Le modèle peut résumer les données que vous avez délibérément sélectionnées. Il ne peut pas rendre sûr un transport non borné, et il ne devrait pas décider quelle quantité de sortie distante brute votre processus alloue avant que vous l'ayez contrôlée.

## L'encodage de contenu doit être explicite dans le contrat client

Traitez Content-Encoding comme une entrée qui choisit un décodeur, et non comme un habillage autour d'un flux d'octets par ailleurs identique. N'acceptez que les encodages pris en charge par votre client et rejetez clairement les valeurs inconnues. Si un service renvoie `gzip`, utilisez le chemin gzip. S'il renvoie `br`, utilisez un décodeur Brotli entouré du même limiteur d'octets décodés. S'il ne renvoie aucun encodage de contenu, entourez le corps brut de ce limiteur décodé, car les octets bruts sont alors aussi des octets décodés.

N'acceptez pas sans précaution plusieurs encodages de contenu. HTTP autorise une liste d'encodages, et les transformations indiquées ont un ordre. Prendre en charge `gzip, br` signifie que vous devez décoder dans l'ordre inverse avec une protection de taille après chaque étape d'expansion. Une unique limite finale est plus faible qu'elle n'en a l'air, car une étape intermédiaire peut grossir avant que la dernière ne la réduise. Si vos intégrations n'ont pas besoin d'encodages empilés, rejetez-les jusqu'à disposer de tests et d'une implémentation réfléchie.

La décompression automatique mérite d'être examinée. De nombreuses bibliothèques HTTP ajoutent Accept-Encoding, décodent gzip et masquent ce changement au code applicatif. C'est pratique pour les requêtes courantes, mais le code peut alors compter les octets au mauvais niveau. Déterminez si le corps de réponse fournit des octets réseau ou décodés, si la bibliothèque retire Content-Encoding et si elle expose le nombre d'octets compressés. Écrivez un test qui prouve le comportement pour votre configuration client exacte.

Brotli mérite le même traitement que gzip. Il est tentant d'écrire un test gzip parce que chaque environnement dispose d'une commande gzip, puis de supposer que le travail est fait. Un autre décodeur implique une autre gestion des erreurs, un autre comportement de mise en tampon et une autre prise en charge selon les versions de dépendances. La limite doit entourer le lecteur renvoyé par chaque décodeur, pas se trouver dans un assistant appelé seulement par le chemin gzip.

Un serveur peut aussi annoncer un encodage avec un corps invalide. Conservez ce cas comme une erreur de décodage, distincte d'une réponse décodée trop volumineuse. Les opérateurs doivent savoir si le côté distant a envoyé de mauvaises données, si le budget configuré est trop faible ou si le client n'a pas de décodeur pour l'encodage annoncé.

## Testez l'expansion avec des fichiers inspectables

Un fichier de test utile commence par un contenu décodé que vous pouvez reconnaître et mesurer. Des octets répétés donnent un cas d'expansion évident. Les commandes suivantes créent un corps texte de 32 Mio, puis en produisent des versions gzip et Brotli lorsque la commande Brotli est installée.

```sh
python3 -c 'open("repeat.txt", "wb").write(b"A" * (32 * 1024 * 1024))'
wc -c repeat.txt
gzip -9 -c repeat.txt > repeat.txt.gz
brotli -f repeat.txt -o repeat.txt.br
wc -c repeat.txt.gz repeat.txt.br
```

La première sortie de `wc` devrait avoir la forme `33554432 repeat.txt`. Les fichiers compressés devraient être nettement plus petits que la source, car l'entrée répète un seul octet. Ne faites pas dépendre un test d'une taille compressée précise. Les versions et les réglages de compression peuvent la modifier. Vérifiez la longueur décodée, le résultat du client et le fait que le client n'a conservé aucun corps complet après son rejet.

Construisez des cas limites à partir de la même source : un corps décodé un octet sous la limite configurée, exactement à la limite et un octet au-dessus. Exercez chaque cas via un gestionnaire HTTP local qui définit correctement Content-Encoding. Un test de décodeur de fichier est utile, mais il ne peut pas détecter une bibliothèque cliente qui décompresse automatiquement avant l'exécution de votre code de limitation.

Utilisez une deuxième famille de fichiers avec une structure réaliste. Générez du JSON délimité par des retours à la ligne où chaque enregistrement comporte un champ de charge répétée, puis servez-le sous gzip et Brotli. Cela détecte le code qui gère correctement une tranche d'octets mais laisse un analyseur JSON ou un scanner de lignes construire un tableau non borné après décodage. Ajoutez un enregistrement plus long que votre limite de champ ou de ligne, car un attaquant n'a pas besoin de répéter de courtes lignes pour forcer un analyseur à allouer.

Conservez les fichiers dans le dépôt uniquement si leurs formes compressées sont petites et si leur source peut être générée pendant les tests. Un script générateur qui produit une longueur décodée connue est plus facile à examiner qu'un mystérieux blob binaire. Indiquez la taille décodée prévue dans le nom du test, pas seulement dans un commentaire que personne ne voit lorsqu'un cas limite échoue.

## La défaillance commence généralement par une méthode pratique

Prenez un agent qui appelle une API pour obtenir des données d'incident. L'endpoint renvoie habituellement une petite page JSON. Le client envoie `Accept-Encoding: gzip`, reçoit une réponse dont Content-Length vaut 18 Ko et inscrit cet en-tête comme preuve que le résultat est modeste. Son assistant HTTP décompresse ensuite le corps de façon transparente et appelle `ReadAll` avant l'analyse JSON.

Une mauvaise réponse en amont contient un grand champ de description répété dans chaque enregistrement. Le transfert de 18 Ko se dilate bien au-delà des données normalement contenues dans la page. Le processus alloue la tranche d'octets, crée ensuite des chaînes et des maps lors de l'analyse, puis sérialise les enregistrements retenus pour l'agent. Chaque étape conserve une copie différente pendant au moins une partie de la requête. Une limite placée après l'analyse ne constate le problème qu'une fois le travail coûteux effectué.

La première correction ajoute souvent `if len(body) > limit` après `ReadAll`. Ce test fait passer le test unitaire au vert tout en conservant le pic d'allocation. La deuxième correction entoure le lecteur décodé, ce qui va dans la bonne direction, mais envoie encore les premiers `limit` octets à l'agent comme solution de repli. L'agent peut alors agir à partir d'une demi-réponse, sans que la piste d'audit ne montre un échec net.

La correction complète rejette tôt au niveau du lecteur décodé, abandonne le contenu partiel, enregistre l'encodage et le budget d'octets, puis donne à l'appelant une erreur qu'il peut classer. Si l'endpoint a réellement besoin de gros exports, déplacez ce cas d'usage vers un chemin de téléchargement explicite avec une limite approuvée plus élevée, une destination choisie par l'utilisateur et aucune insertion automatique du fichier dans le contexte de l'agent.

Cette séparation compte, car le téléchargement d'un rapport et la recherche par un outil d'agent sont deux opérations différentes. Le fait que les deux soient des requêtes HTTP ne rend ni leur risque ni leur budget identiques.

## Les limites ont besoin d'un propriétaire d'endpoint et d'une raison

Un paramètre global par défaut est un point de départ, pas une politique adaptée à chaque action HTTP. Attribuez une limite de réponse décodée selon le résultat attendu de l'endpoint. Une vérification d'état peut n'avoir besoin que de quelques kilo-octets. Une recherche paginée peut renvoyer une réponse structurée de taille modérée. Un export binaire peut nécessiter une limite plus élevée, mais il devrait aller vers un chemin de fichier plutôt que vers un résultat conversationnel.

Écrivez la limite à côté de la définition de l'action, avec sa raison. « La documentation de l'API indique que la taille de page est de 100 » n'est pas une raison suffisante, car un seul enregistrement peut tout de même être énorme. Une bonne raison nomme la représentation attendue et l'usage de l'appelant, par exemple « analyser un objet d'état et renvoyer des champs sélectionnés » ou « enregistrer une archive demandée par l'utilisateur après approbation explicite ».

Ne déduisez pas le plafond d'un seul paramètre de taille de page fourni par le serveur. Les serveurs peuvent ignorer un paramètre, un agent peut demander une requête très large et un seul champ texte peut dominer la réponse. Limitez la taille de page, l'étendue de la requête, les octets décodés, les enregistrements analysés et les octets du résultat d'agent là où chaque frontière a du sens. Ces contrôles se recouvrent volontairement, car chacun intercepte un type différent de mauvaise requête ou de mauvaise réponse.

Lorsque vous rejetez une réponse, enregistrez suffisamment de détails pour le diagnostic : méthode HTTP, hôte ou identifiant d'action, code d'état s'il est disponible, encodage de contenu annoncé, octets compressés observés, limite décodée et indication que le décodeur avait commencé à produire une sortie. N'enregistrez pas le corps rejeté par défaut. Le journaliser peut recréer le problème de mémoire et de données sensibles dans un chemin prétendument destiné au diagnostic.

Une approbation humaine ne rend pas une réponse inoffensive. Elle peut autoriser une action auprès d'un service distant, mais elle ne prédit pas le volume de données que ce service renverra. Gardez les bornes de réponse appliquées après l'approbation et avant que l'agent ne reçoive des données.

## L'annulation et les délais couvrent un autre type de défaillance

Un plafond de taille décodée arrête la croissance lorsque le décodeur produit assez de sortie. Il n'arrête pas un pair qui envoie des octets avec une lenteur extrême, un décodeur qui consomme trop de CPU sur un flux forgé ou une réponse qui ne se termine jamais. Définissez des délais de requête et assurez-vous que l'annulation ferme le corps de réponse et interrompt le lecteur.

Gardez séparés dans les métriques et les erreurs le temps, les octets compressés, les octets décodés et le nombre d'éléments analysés. Si tous les échecs apparaissent comme « échec de la requête », quelqu'un augmentera la limite de taille pour résoudre un délai, ou augmentera le délai pour résoudre un échec d'analyse. Ces changements rendent les incidents plus difficiles à diagnostiquer et élargissent souvent la surface d'attaque.

Testez l'annulation contre un endpoint qui envoie un préfixe compressé valide puis se met en pause. Le client doit renvoyer son erreur de délai ou d'annulation sans laisser une goroutine attendre sur le décodeur. Testez ensuite un corps qui dépasse rapidement le plafond décodé. Ce chemin doit produire l'erreur de taille sans tarder, même si le serveur continuerait à envoyer des octets compressés.

La réutilisation de connexion demande de l'attention après un rejet précoce. Dans de nombreux clients, fermer le corps suffit à libérer les ressources, mais la connexion peut ne pas être réutilisable si le client n'a pas consommé le reste de la réponse. C'est acceptable. La correction et l'usage borné des ressources valent mieux que d'extraire une connexion keep-alive de plus d'une réponse hostile ou défectueuse.

Évitez de réessayer automatiquement une réponse trop volumineuse. Une erreur réseau transitoire peut justifier une nouvelle tentative dans le cadre d'une politique bornée. Une réponse qui dépasse une limite connue est généralement déterministe. La répéter gaspille de la bande passante et peut multiplier la pression sur un processus déjà confronté à une action surdimensionnée.

## Les limites d'analyse viennent après le décodage, elles ne le remplacent pas

Un analyseur JSON en flux peut vous empêcher de stocker chaque enregistrement, mais il ne supprime pas le plafond d'octets décodés. Les analyseurs ont encore besoin de tampons, les chaînes individuelles peuvent être énormes et le signalement d'erreurs peut conserver des fragments de source. Placez le plafond d'octets devant l'analyseur, puis ajoutez des limites de format adaptées aux données acceptées.

Pour JSON, envisagez une profondeur d'imbrication maximale, une longueur maximale de chaîne, un nombre maximal d'enregistrements et un schéma strict pour les champs utilisés par l'agent. Pour CSV ou les protocoles à lignes, fixez une longueur maximale de ligne et un nombre maximal de lignes. Pour XML, désactivez le traitement des entités externes et définissez les limites de l'analyseur lorsque la bibliothèque le permet. Ce sont des règles d'analyse, tandis que le plafond du corps décodé est une frontière de transport. Ne forcez pas l'un à se faire passer pour l'autre.

Validez avant le rendu. Un champ nommé `instructions`, `command` ou `message` provenant d'un service distant reste du contenu distant. Sa taille peut respecter le plafond décodé et il peut tout de même être inadapté au flux de contrôle d'un agent. Sélectionnez les champs selon le schéma et encodez-les comme des données. La limite de taille empêche une catégorie de défaillances, elle n'établit pas la fiabilité de ce que disent les octets.

Cette séparation simplifie la gestion des erreurs. Un corps qui franchit la limite décodée ne doit jamais atteindre l'analyseur. Un corps qui tient dans la limite mais contient trop d'enregistrements doit renvoyer une erreur de limite d'analyse ou d'application. Un corps conforme au schéma mais qui dépasse le budget de présentation de l'agent doit être réduit selon une règle explicite de résultat. Chaque issue indique à un opérateur ce qui doit changer, s'il y a lieu.

## Traitez l'enregistrement d'audit comme un rapport de frontière

Une entrée d'audit utile indique ce que le processus a tenté et pourquoi le client a refusé de continuer. Elle n'a pas besoin du contenu rejeté. Conservez l'identité de l'action, le contexte d'autorisation, la destination, l'heure de la requête, l'état de la réponse lorsqu'il est connu, l'encodage, le nombre d'octets réseau, le nombre d'octets décodés ou une borne inférieure, ainsi que la limite qui a arrêté l'appel.

Lors d'un rejet précoce, le nombre d'octets décodés peut être `limit + 1` plutôt que la taille finale réelle, car le client a volontairement cessé de lire. Enregistrez-le honnêtement. Prétendre connaître la taille décodée complète laisse croire que vous avez consommé le flux que vous deviez limiter. Une borne inférieure suffit à expliquer la décision.

C'est particulièrement utile lorsqu'un agent répète une action après qu'un opérateur a modifié une requête. Vous voyez que le premier appel a dépassé le budget de présentation ou de décodage et que le second a réussi avec une requête plus précise. Sans cette distinction, un appel d'outil échoué est indiscernable d'un problème d'authentification ou de réseau, et les personnes cherchent la mauvaise correction.

La piste d'activité de Sallyport peut conserver le chemin du résultat d'action sans donner l'identifiant API à l'agent. L'appelant doit tout de même signaler un résultat surdimensionné comme un échec borné plutôt que de transformer l'enregistrement d'activité en seconde copie de la réponse.

## Faites du cas surdimensionné une condition de publication

Ne laissez pas les contrôles de décompression dans un test de sécurité exécuté seulement quand quelqu'un y pense. Ajoutez un fichier de limite gzip et un fichier de limite Brotli à la suite normale de tests client. Exécutez les mêmes assertions via chaque chemin HTTP qui peut renvoyer des données à un agent, y compris les redirections si votre client les suit et les réponses d'erreur s'il lit leur corps pour le diagnostic.

Les assertions doivent être concrètes. Pour un corps sous la limite, attendez un résultat décodé complet et une validation de somme de contrôle réussie. À la limite exacte, attendez la même chose. Un octet au-dessus de la limite, attendez l'erreur de taille typée, aucun objet analysé, aucun résultat pour l'agent et un corps de réponse fermé. Pour un encodage mal formé, attendez une erreur de décodage. Pour un encodage non pris en charge, attendez une erreur explicite d'encodage non pris en charge avant le début de l'analyse.

Ajoutez un test pour un corps compressé de faible taille sur le réseau et de taille décodée supérieure au plafond. C'est le test qui détecte l'erreur initiale. Un gros corps non compressé prouve seulement qu'un limiteur d'octets ordinaire fonctionne. Il ne dit rien de la frontière du décodeur.

Examinez toute modification qui introduit une nouvelle bibliothèque HTTP, un nouvel assistant de récupération pratique ou un nouveau chemin de journalisation de réponse. Ces changements contournent régulièrement le lecteur soigneusement limité, car ils semblent inoffensifs pris isolément. La question de revue de code est simple : où les octets décodés deviennent-ils disponibles pour la première fois, et quelle limite entoure ce lecteur précis ?

Si la réponse n'est pas claire, l'implémentation n'est pas prête pour un appelant autonome. Une petite réponse sur le réseau n'a gagné aucune confiance particulière. Faites-lui prouver sa taille après décodage, avant qu'elle puisse consommer votre mémoire ou l'attention de votre agent.
