# Les envois de fichiers par les agents IA ont besoin de limites strictes

Un point d’envoi est un canal de sortie de données, même lorsque les ingénieurs parlent d’une fonctionnalité de pièce jointe. Dès qu’un agent IA peut joindre un journal, un export, une capture d’écran ou un fichier client, une instruction vague comme « envoyez ceci au support » peut se transformer en divulgation irréversible.

Les envois de fichiers par agent IA ont besoin de limites strictes concernant les octets, le contenu et les récepteurs. Placez ces limites dans l’action qui réalise l’envoi, pas dans le prompt de l’agent et pas dans la capacité d’un réviseur à repérer un mauvais nom de fichier en fin de journée, après des heures chargées.

## Une action d’envoi doit déclarer ce qui peut sortir

Un workflow sûr commence par traiter chaque fichier comme un objet dont l’objectif est déclaré. Cet objectif détermine sa taille maximale, les formats acceptés, le récepteur autorisé, la durée de conservation prévue et la nécessité éventuelle d’une approbation humaine avant l’envoi. Si votre API accepte un corps multipart arbitraire et une URL fournie par l’appelant, vous avez créé une route générale d’exfiltration de données, simplement dotée d’une interface agréable pour les développeurs.

On confond souvent la réception de fichiers provenant d’un client non fiable et l’envoi de fichiers au nom d’un agent. Les recommandations traditionnelles sur les téléversements cherchent surtout à protéger votre serveur contre les fichiers malveillants. Les envois effectués par des agents nécessitent aussi cette protection, mais le risque le plus immédiat consiste à protéger les données contre un envoi trop large. Un PDF peut être parfaitement sûr à analyser et totalement inapproprié pour un système de gestion de tickets.

Définissez des objectifs d’envoi nommés au lieu d’une action générique `upload_file`. Un objectif comme `diagnostic_bundle` peut autoriser un paquet de support compressé vers un seul récepteur du support. Un objectif comme `invoice_export` peut autoriser un fichier CSV vers le récepteur de la comptabilité. Aucun de ces objectifs ne devrait accepter dans la même requête un chemin, une URL de récepteur et un fichier arbitraire.

Un petit contrat rend la limite visible :

```json
{
  "purpose": "diagnostic_bundle",
  "file_path": "/private/tmp/app-diagnostics-2025-03-08.zip",
  "destination_id": "support-case",
  "case_reference": "CASE-1842"
}
```

L’appelant choisit un objectif approuvé et fournit les métadonnées nécessaires à l’action métier. Le service d’envoi associe `support-case` à un récepteur qu’il contrôle. Il ne permet pas à l’appelant de remplacer ce récepteur par une URL intégrée dans un commentaire de ticket, un document ou une réponse d’outil.

Contrôlez aussi les chemins de fichiers. Un agent devrait uniquement sélectionner des fichiers dans des répertoires de préparation désignés, ou le service devrait créer lui-même la pièce jointe à partir de données connues. Permettre à un agent de nommer n’importe quel chemin lisible transforme une demande de joindre des diagnostics en demande de lecture de fichiers de configuration, de clés SSH, de données de navigateur ou de l’export d’un autre utilisateur.

## Les limites de taille nécessitent deux mesures

Une limite de fichier doit refuser un corps excessif avant que votre service ne le stocke, ne l’analyse ou ne le transfère. Appliquez-la à la périphérie HTTP avec `Content-Length` lorsqu’il est présent, puis comptez les octets pendant la lecture, car un client peut omettre cet en-tête ou mentir sur sa valeur.

La limite doit correspondre à l’objectif. Une limite de 25 Mo pour un CSV client et une limite de 25 Mo pour un paquet de diagnostic compressé ne sont pas des décisions équivalentes. Le CSV peut se transformer en une allocation mémoire bien plus importante lorsqu’un parseur le lit. L’archive peut se décompresser à une taille plusieurs fois supérieure à celle du transport. Définissez donc une limite sur la taille transmise et une autre sur la taille après traitement.

Ne permettez pas à un agent de diviser un fichier refusé en de nombreuses requêtes autorisées, sauf si le récepteur prend explicitement en charge les envois par morceaux et si votre service suit le total. Sinon, une règle de 10 Mo devient un transfert en 100 parties qui n’offre aucune des protections que vous pensiez avoir.

Refusez tôt et renvoyez une réponse qui permet à l’agent de récupérer la situation sans danger :

```json
{
  "error": "attachment_too_large",
  "purpose": "diagnostic_bundle",
  "observed_bytes": 12582911,
  "max_bytes": 8388608,
  "safe_alternatives": [
    "create_redacted_diagnostic_bundle",
    "attach_selected_log_window"
  ]
}
```

Cette réponse compte. Si le refus se limite à « l’envoi a échoué », l’agent peut essayer une autre destination, compresser le fichier ou continuer à réessayer. Indiquez-lui quelle action est autorisée ensuite, tout en refusant de révéler le fichier rejeté dans un message de débogage.

La mise en mémoire tampon est une autre source discrète d’échec. De nombreux frameworks analysent une requête multipart en mémoire ou dans un répertoire temporaire avant que le code applicatif ne voie sa taille. Configurez le serveur web, le parseur du framework, le proxy inverse et le lecteur applicatif pour qu’ils appliquent une limite cohérente. La plus petite limite l’emporte, mais une couche dont la limite est étonnamment élevée peut tout de même consommer de l’espace disque avant que la couche plus restrictive ne refuse la requête.

Mesurez la taille après traitement pour la normalisation du texte, la conversion d’images, l’extraction de documents et la décompression d’archives. Une limite qui ne couvre que la pièce jointe originale ne contrôle ni l’utilisation des ressources ni le potentiel de divulgation des éléments que vous produisez ensuite.

## Un nom de fichier et un en-tête MIME ne prouvent presque rien

Les contrôles de type de fichier ont besoin de preuves indépendantes, car l’extension et l’en-tête `Content-Type` viennent de l’expéditeur. Un agent peut relayer une étiquette trompeuse sans intention malveillante. Un outil de support peut appeler chaque pièce jointe `application/octet-stream`. Dans les deux cas, le récepteur doit décider à partir des octets et de la structure autorisée.

La fiche File Upload Cheat Sheet d’OWASP recommande d’autoriser les extensions par liste, de ne pas faire confiance à l’en-tête `Content-Type`, de générer les noms de fichiers côté serveur et de stocker les envois en dehors de la racine web. Ces recommandations restent valables, mais les workflows d’agents nécessitent une règle supplémentaire : valider le type par rapport à l’objectif nommé avant que le service ne contacte le récepteur distant. Un PDF valide n’est pas automatiquement valable pour tous les objectifs d’envoi.

Utilisez plusieurs contrôles qui répondent chacun à une question différente :

- L’extension indique ce que l’expéditeur affirme avoir.
- Les octets de signature indiquent si le contenu commence comme le format annoncé.
- Un parseur limité indique si les octets respectent suffisamment le format pour être traités sans danger.
- L’inspection du contenu indique si le fichier contient des éléments interdits pour cet objectif.

Pour un export CSV, acceptez une petite liste autorisée comme `.csv` et du texte UTF-8, analysez un échantillon limité et refusez les données binaires intégrées ou les lignes dont la largeur est anormalement importante. Pour un PDF, vérifiez la signature `%PDF-`, appliquez une limite de taille et utilisez un parseur limité dans le temps et en mémoire si vous devez examiner les pages. Pour les images, décodez les dimensions avant le traitement : une image de taille modeste peut tout de même nécessiter beaucoup trop de mémoire une fois décodée.

Évitez un type générique « archive ». ZIP, TAR et GZIP diffèrent, et chacun impose son propre travail d’inspection. Si un processus métier n’a pas besoin d’une archive, refusez-la. Accepter un format parce que les utilisateurs pourraient parfois le vouloir est la manière dont se construisent les points d’envoi de pièces jointes génériques.

Renommez les fichiers acceptés côté service. Conservez le nom d’origine comme métadonnée d’affichage après l’avoir nettoyé, mais ne l’utilisez pas comme chemin de système de fichiers, clé de stockage objet ou valeur `Content-Disposition` sans échappement. Les noms peuvent contenir des caractères de contrôle, des caractères Unicode trompeurs, des séparateurs de chemin et des chaînes qui modifient les journaux en aval.

## Les contrôles de destination doivent résister aux redirections

Une liste de récepteurs autorisés doit identifier l’endroit précis qui peut recevoir le fichier. Autoriser `https://example.com` est insuffisant si le client HTTP suit des redirections vers un autre hôte, résout une adresse interne ou accepte un autre port.

Stockez les destinations dans des enregistrements côté serveur avec un schéma, un hôte, un port, un préfixe de chemin, une identité d’identifiants et des objectifs acceptés fixes. L’action reçoit `destination_id`, jamais un point de terminaison libre. Si un récepteur exige un identifiant de ticket dans le chemin, construisez-le à partir d’un identifiant contraint au lieu d’accepter une URL complète fournie par l’agent.

Pour chaque envoi, le client HTTP doit appliquer les contrôles suivants :

1. Exiger HTTPS, sauf exception interne documentée.
2. Comparer l’hôte et le port demandés à l’enregistrement de destination avant la connexion.
3. Désactiver les redirections par défaut. Si un récepteur en a besoin, valider chaque cible de redirection avec le même enregistrement avant d’envoyer un nouvel octet.
4. Refuser les adresses IP littérales, les adresses de bouclage, les adresses link-local et les plages privées, sauf si cette destination existe explicitement pour un service interne contrôlé.
5. Verrouiller le préfixe de chemin et la méthode HTTP autorisée au lieu d’autoriser un hôte entier.

Le quatrième point est souvent décrit comme une protection contre les SSRF, et c’en est une. Il empêche aussi les divulgations accidentelles provoquées par un agent qui suit une URL provenant d’une description de tâche. Un client d’envoi ayant accès aux réseaux internes ne doit jamais considérer le texte d’une tâche comme l’autorisation de contacter une adresse.

Ne transférez pas le contexte d’autorisation d’origine. L’identifiant utilisé pour envoyer un fichier à une API de gestion de tickets doit appartenir uniquement à ce récepteur et à cette action. Une passerelle d’envoi qui copie des en-têtes arbitraires de l’agent crée une voie plus simple pour l’injection d’en-têtes et permet à un agent de sélectionner indirectement des identifiants.

Enregistrez l’URL finale après les redirections, mais masquez les valeurs des paramètres dans les journaux opérationnels. Les chaînes de requête contiennent souvent des jetons d’envoi signés. Journaliser la destination est utile ; reproduire un jeton d’autorisation utilisable est imprudent.

## Les journaux ont besoin d’un chemin d’export conçu à cet effet

Les journaux sont la catégorie de pièces jointes que les équipes sous-estiment le plus. Ils capturent des en-têtes de requête, des identifiants client, des fragments SQL, des traces d’erreur, des noms d’hôtes internes et parfois des corps de requête ou de réponse complets. Le fait qu’un journal se trouve dans le répertoire d’un développeur ne le rend pas sûr à envoyer par e-mail ou à téléverser.

Ne cherchez pas à résoudre le problème avec une instruction demandant à l’agent de « supprimer les secrets ». Les agents peuvent manquer certains formats, masquer trop de données ou considérer à tort qu’une chaîne suspecte constitue un contexte inoffensif. Créez un générateur de paquet de diagnostic qui lit des fichiers connus, applique des filtres déterministes et produit un nouvel artefact dans un répertoire de préparation.

Une politique de filtrage pratique peut supprimer des champs entiers au lieu de rechercher chaque motif de secret possible. Supprimez par défaut `Authorization`, `Cookie`, `Set-Cookie`, les champs de clés API, les identifiants de session et les corps de requête. Remplacez les identifiants client par des jetons locaux stables lorsqu’une corrélation est nécessaire. Limitez les horodatages à la fenêtre de l’incident au lieu d’envoyer plusieurs semaines d’historique.

Par exemple, cette structure est plus sûre pour un enregistrement de diagnostic qu’une trace HTTP brute :

```json
{
  "time": "2025-03-08T14:22:11Z",
  "request_id": "local-7f3c",
  "method": "POST",
  "route": "/v1/reports",
  "status": 502,
  "upstream": "reporting-service",
  "authorization": "[removed]",
  "body": "[omitted]"
}
```

Le générateur de paquets doit produire un manifeste contenant les noms de fichiers, le nombre d’octets, les empreintes et les filtres appliqués. Le réviseur dispose ainsi d’éléments concrets à examiner sans ouvrir chaque pièce jointe. Le récepteur peut aussi détecter un envoi tronqué ou modifié.

Les exports de bases de données exigent une règle plus stricte. Un agent ne devrait pas joindre un export de production simplement parce qu’un ticket dit « envoyez un échantillon ». Générez un artefact contenant uniquement le schéma, une requête conçue pour l’objectif avec des colonnes approuvées ou une reproduction synthétique. Si un incident exige de véritables données client, faites-en une action distincte avec une destination, un périmètre et une approbation humaine explicites.

Les captures d’écran méritent la même méfiance. Elles peuvent contenir des comptes client, des messages, des onglets de navigateur, des notifications et des chemins locaux. Recadrez-les ou produisez une capture ciblée avec un outil contrôlé au lieu de remettre à un agent un vaste répertoire de captures.

## Les archives et les documents bureautiques cachent plus d’un fichier

Une archive crée un second ensemble de fichiers à l’intérieur du premier. Avant de l’envoyer, inspectez la liste de ses éléments et imposez des limites sur le nombre d’éléments, les octets compressés, les octets développés, la profondeur des chemins et l’imbrication. Refusez les entrées utilisant des chemins absolus, la traversée `..`, des noms en double ou des liens symboliques.

Un fichier ZIP de 2 Mo sur le disque peut atteindre une taille suffisante après expansion pour épuiser un processus ou un récepteur. On parle généralement de bombe de décompression, mais le problème ne se limite pas aux entrées malveillantes. Les systèmes de compilation peuvent produire d’énormes archives par erreur, et un agent peut joindre le premier fichier qui ressemble à un export.

Les documents bureautiques exigent une vigilance similaire. Les formats de documents modernes contiennent souvent des conteneurs ZIP, des médias intégrés, des métadonnées, des commentaires, des modifications suivies et des relations externes. Un document peut sembler expurgé à l’écran tout en conservant l’ancien texte ou des informations sur l’auteur dans son paquet. Si le workflow n’a besoin que du contenu rendu, produisez un nouveau PDF à partir de données approuvées au lieu de transférer l’original modifiable.

Ne tentez pas de « nettoyer » récursivement des documents arbitraires dans le chemin de la requête. L’analyse et la réécriture de formats complexes comportent leurs propres coûts de sécurité et de fiabilité. Pour un workflow étroit, acceptez un ensemble étroit d’artefacts générés. Pour un document exceptionnel, orientez-le vers un processus de revue humaine capable d’inspecter le fichier réel et sa destination.

## Le contrat de l’action doit rendre les requêtes dangereuses impossibles

Un outil destiné à un agent doit proposer des choix correspondant à vos contrôles, et non un générateur de requêtes HTTP brutes. Si l’outil propose `url`, `headers`, `file_path` et `method`, la politique a déjà perdu l’essentiel de sa structure.

Utilisez un schéma de requête où les champs non fiables décrivent l’intention et où des enregistrements fiables fournissent l’autorité. Cet exemple garde le destinataire, les identifiants et la classe de fichier autorisée hors du contrôle de l’agent :

```json
{
  "action": "send_attachment",
  "purpose": "customer_export",
  "destination_id": "finance-import",
  "artifact_id": "exp_8c4e1a",
  "note": "March reconciliation correction"
}
```

Le service résout `artifact_id` vers un objet préparé qu’il a créé ou accepté par l’intermédiaire d’un flux d’entrée distinct. Il calcule lui-même l’empreinte et le type détecté. Il résout `destination_id` vers un enregistrement de récepteur fixe. Il ajoute les identifiants du destinataire au moment de l’envoi. L’agent ne voit jamais cet identifiant et ne peut pas remplacer le destinataire après l’approbation.

Le résultat de la vérification préalable doit exposer suffisamment de détails pour permettre une décision éclairée :

```json
{
  "decision": "approval_required",
  "artifact": {
    "name": "reconciliation-2025-03.csv",
    "bytes": 482913,
    "detected_type": "text/csv",
    "sha256": "a4d1...c09e"
  },
  "destination": {
    "label": "Finance import",
    "host": "imports.example.internal",
    "path": "/v2/reconciliation"
  },
  "reason": "customer_export requires approval"
}
```

Ne montrez pas au réviseur uniquement le nom du fichier et un bouton d’approbation. Affichez la taille mesurée, le type détecté, l’hôte de destination, le libellé de destination et l’objectif. Si le fichier est sensible, affichez un résultat de classification échantillonné ou un manifeste, et non son contenu complet dans l’écran d’approbation.

Faites en sorte que les identifiants d’artefacts soient de courte durée et associés à un seul objectif. Un paquet de diagnostic préparé ne doit pas rester réutilisable comme objet d’envoi générique après la clôture de son ticket de support. Associez-le à l’objectif et au récepteur lors de sa création, puis faites-le expirer après une courte période opérationnelle.

Les nouvelles tentatives nécessitent l’idempotence. Les défaillances réseau sont courantes et les agents réessaient agressivement. Générez un jeton d’idempotence au niveau de l’action, associez-le à l’empreinte de l’artefact et à la destination, et ne réutilisez-le que pour le même envoi prévu. Une nouvelle tentative avec un chemin ou une empreinte différents ne doit pas hériter de l’autorisation précédente.

## L’approbation humaine est utile lorsqu’elle marque une vraie limite

L’approbation ne remplace pas la validation. Une personne ne peut pas identifier de manière fiable une bombe ZIP, un fichier déguisé ou une faille de redirection à partir d’une fenêtre modale. L’approbation sert à prendre la décision que la politique ne peut pas prendre automatiquement : déterminer si cet export client doit être envoyé à ce récepteur pour cet incident.

Utilisez une approbation par appel pour les envois très sensibles, les nouveaux récepteurs, les exports de production et les artefacts de diagnostic volumineux. Les artefacts courants et étroitement définis peuvent suivre un objectif approuvé si leur destination et leur contenu sont contraints. Demander à une personne d’approuver chaque rapport de test lui apprend à cliquer sans lire.

L’enregistrement d’approbation doit être lié à l’empreinte de l’artefact, à l’objectif déclaré et à l’enregistrement de destination. Si l’un de ces éléments change, supprimez l’approbation. Un nom de fichier ne suffit pas, car deux fichiers peuvent avoir le même nom et contenir des octets différents.

Sallyport peut empêcher l’agent d’accéder à l’identifiant utilisé pour un envoi HTTP, et son option d’approbation par clé convient aux envois qui doivent être décidés par une personne à chaque utilisation. Cela ne supprime pas le besoin d’un contrat d’artefact au niveau de l’application, car la passerelle ne peut pas deviner si un CSV client doit être placé dans un ticket de support.

Conservez deux vues d’audit : l’une indique quelle exécution d’agent a reçu l’autorisation d’agir, l’autre enregistre chaque tentative de transfert et son résultat. Un enregistrement d’audit doit inclure l’empreinte, le nombre d’octets mesuré, le verdict de type, l’objectif, l’enregistrement du destinataire, la décision, le statut de réponse et l’heure. Stockez les secrets et le contenu complet des fichiers ailleurs, si vous les conservez בכלל.

## Testez les refus avant qu’un agent ne les découvre

Une politique qui ne fonctionne que dans le scénario idéal n’est pas terminée. Créez un récepteur isolé qui enregistre la requête exacte qu’il reçoit, puis utilisez des fichiers de test qui forcent chaque limite à s’appliquer.

Commencez par une requête d’envoi simple vers votre récepteur de test :

```bash
curl -i -X POST https://receiver.test/attachments \
  -H 'Authorization: Bearer test-token' \
  -F 'file=@fixtures/diagnostic.zip;type=application/zip' \
  -F 'case_reference=CASE-1842'
```

Un test réussi doit confirmer la méthode, l’hôte final, le chemin, l’empreinte et la taille. Les tests utiles sont ceux qui prouvent que le récepteur n’a rien reçu. Vérifiez qu’un corps trop volumineux est refusé avant transfert, qu’un en-tête `text/plain` falsifié ne permet pas de faire passer une charge binaire et qu’une destination non répertoriée ne reçoit jamais de connexion.

Ajoutez ces cas à votre ensemble de fichiers de test :

- Une extension valide avec des octets de signature incompatibles.
- Une archive compressée dont la taille développée dépasse la règle.
- Une redirection d’un hôte autorisé vers un hôte interdit.
- Un fichier journal contenant un en-tête d’autorisation et une adresse e-mail client.
- Une nouvelle tentative utilisant le même jeton d’idempotence avec des octets de fichier différents.

Examinez les journaux du serveur pendant ces tests. Vous vérifiez deux choses : le transfert n’a pas eu lieu et vos propres journaux n’ont pas conservé le corps rejeté, le jeton bearer ou l’URL signée. Les équipes corrigent souvent le chemin réseau tout en laissant les mêmes données sensibles dans les traces d’exception.

Exécutez les tests avec la même interface d’outil d’agent que celle utilisée par les agents en production. Un client HTTP interne sécurisé ne suffit pas si le wrapper destiné à l’agent peut choisir une URL brute ou contourner le chemin de préparation des artefacts.

## Rendez le chemin sûr plus simple que le chemin brut

Les équipes contournent les contrôles d’envoi lorsque le chemin approuvé est lent, ambigu ou incapable de gérer le support courant. Créez un petit ensemble d’artefacts dont les utilisateurs ont réellement besoin : un paquet de diagnostic expurgé, un export limité, un rapport généré et une capture d’incident aux limites définies. Permettez à un agent de demander facilement chacun d’eux par son nom.

Retirez l’envoyeur brut générique de la liste des outils de l’agent. Si un ingénieur en a besoin pour un cas inhabituel, imposez un parcours opérationnel interactif dans lequel il choisit le fichier et le récepteur avec tout le contexte nécessaire. Cette contrainte est appropriée, car l’action ne dispose d’aucune classification automatisée fiable.

Examinez les envois refusés avec autant de sérieux que les envois réussis. Une série de paquets trop volumineux signifie que votre artefact de diagnostic est mal conçu. Des tentatives répétées d’envoi de journaux vers un nouvel hôte peuvent indiquer que la liste des destinations doit recevoir un ajout justifié, ou qu’un prompt d’agent essaie de contourner vos contrôles. La différence n’apparaît dans les enregistrements que si vous capturez ensemble l’objectif, le verdict sur le fichier et la décision concernant le récepteur.

Le premier changement que je ferais est simple : supprimer les URL libres et les chemins de fichiers arbitraires de l’action d’envoi de l’agent. Dès que l’action n’accepte plus que des artefacts préparés, des objectifs nommés et des identifiants de destination, les limites de taille et les contrôles de contenu disposent enfin d’un point d’application fiable.
