# Écrivez des journaux d’audit que les agents IA ne peuvent pas modifier

Un agent IA peut rédiger une explication convaincante de son travail après avoir pris une mauvaise décision. Cette explication n’est pas une preuve. Si l’agent peut voir, sélectionner, modifier ou éviter les enregistrements utilisés par les réviseurs pour l’évaluer, la piste d’audit devient une nouvelle surface de sortie que l’agent peut optimiser.

Les journaux d’audit aveugles à la lecture résolvent un problème précis mais important : le processus qui demande une action externe ne doit pas contrôler l’enregistrement de cette action. Le système d’action enregistre ce qu’il a observé, renvoie à l’agent le résultat dont il a besoin et garde l’enregistrement destiné à la revue hors de sa portée. Cela ne rend pas un agent honnête. Cela réduit la capacité d’un agent malhonnête, confus ou compromis à arranger l’histoire après coup.

Cette distinction compte particulièrement lorsqu’un agent peut déployer du code, appeler des API payantes, modifier des paramètres cloud, envoyer des e-mails ou exécuter des commandes SSH. Dans ces situations, une transcription de conversation est une preuve faible. Elle enregistre des affirmations et une sélection de sorties d’outils. Un enregistrement créé au point d’application des règles peut consigner la demande, l’autorisation, l’exécution et le résultat, même lorsque l’agent préférerait le silence.

## L’agent ne doit contrôler ni l’action ni la preuve

Un agent doit avoir moins de pouvoirs sur les preuves que sur la tâche. Lui donner un fichier nommé `audit.jsonl` et lui demander d’y ajouter des enregistrements crée un journal d’activité, pas un journal d’audit. Il peut oublier une ligne, supprimer un argument gênant, écrire une réussite avant l’échec de l’appel ou créer une seconde copie qu’un réviseur ne verra jamais.

Le problème existe même avec des agents qui suivent les instructions. Un agent décide souvent quels outils appeler, quelle exception intercepter et quel contexte conserver. Une injection de prompt peut lui demander d’éviter la journalisation d’une requête. Un bug peut faire passer une requête par une bibliothèque non instrumentée. Une dépendance malveillante peut écrire directement sur le réseau. Si le même processus possède l’action et l’enregistrement, chacune de ces défaillances peut laisser une piste propre mais incomplète.

Séparez explicitement ces capacités :

- L’agent demande une action et reçoit un résultat limité.
- L’exécuteur détient l’identifiant, envoie la requête et crée l’enregistrement de l’événement.
- Le journal accepte les enregistrements par un chemin que l’agent ne peut ni lire ni modifier.
- Un réviseur lit et vérifie les enregistrements par une interface distincte.

Il s’agit d’une limite de capacités, pas d’une promesse sur le comportement. L’agent ne doit pas avoir accès au fichier du journal, d’API de requête pour ses entrées, d’identifiants permettant de réécrire le journal ni de moyen de définir l’horodatage de l’événement. S’il doit expliquer son raisonnement, stockez cette explication dans un artefact d’exécution séparé et identifiez-la comme un contenu fourni par l’agent. Ne la mélangez pas avec l’enregistrement d’exécution.

Les équipes confondent souvent intention et preuve. L’intention correspond à ce que l’agent dit avoir voulu faire : « redémarrer le service de staging ». La preuve correspond à ce qu’un composant d’exécution a observé : l’agent a demandé une commande précise à un moment donné, une décision d’approbation a été prise, l’exécuteur s’est connecté à une référence d’hôte nommée et la commande s’est terminée avec un statut précis. Les deux sont utiles. Elles répondent à des questions différentes et doivent avoir des niveaux de confiance différents.

## L’aveuglement à la lecture sépare les capacités, il ne retarde pas l’accès

L’aveuglement à la lecture signifie que l’acteur peut provoquer la création d’un enregistrement sans accéder à l’enregistrement stocké ni au contexte de sa future revue. Cela ne signifie pas que l’agent ne reçoit aucun retour. Une tâche SSH a toujours besoin de stdout, de stderr et d’un statut de sortie. Une tâche HTTP a besoin du statut et du corps de la réponse, ou d’une extraction limitée. Supprimer les résultats rendrait l’agent inutile sans renforcer les preuves.

La limite comporte quatre éléments pratiques. Premièrement, l’agent utilise une interface de requête, pas un identifiant. Deuxièmement, l’exécuteur crée un enregistrement à partir de ses propres observations au lieu de faire confiance à un objet JSON fourni par l’agent. Troisièmement, l’agent ne peut ni lister, ni récupérer, ni modifier, ni supprimer les enregistrements. Quatrièmement, un réviseur peut examiner l’enregistrement plus tard sans dépendre de la mémoire de l’agent ou de l’historique de conversation.

Une conception fondée sur une lecture différée ne respecte pas ce standard. Supposons qu’un agent écrive des événements dans une base de données et reçoive la promesse de ne pas pouvoir interroger la table avant la fin de la session. Il connaît toujours le schéma. Il peut adapter les chaînes d’arguments, choisir des omissions, provoquer des échecs de transaction et utiliser les différences de temps dans les erreurs pour savoir si un enregistrement a été écrit. Il peut même détenir des identifiants de base de données qu’un autre chemin de code pourrait exploiter.

L’aveuglement à la lecture est plus solide lorsque le chemin des événements ne renvoie aucune réponse exposant l’état du journal. L’exécuteur peut confirmer la demande d’action, mais il ne doit pas renvoyer d’identifiants d’événement, de numéros de séquence, d’annotations de réviseur, d’étiquettes de risque ni de confirmation indiquant qu’un enregistrement particulier est devenu permanent. Ces informations donnent à l’acteur de quoi optimiser son comportement.

Cela ne nécessite pas de traiter chaque agent comme hostile. Un agent peut devenir dangereux pour des raisons banales : une instruction injectée, une boucle incontrôlée, un bug d’outil, une dépendance obsolète ou un opérateur qui accorde une tâche trop large. La conception doit résister à autre chose qu’aux bonnes intentions.

## Un journal en ajout uniquement ne suffit pas

Un stockage en ajout uniquement détecte certaines formes de manipulation. Il ne prouve pas que l’enregistreur a vu chaque action pertinente et n’empêche pas un agent d’organiser des enregistrements trompeurs avant leur stockage. Les équipes achètent souvent un stockage immuable, puis laissent des identifiants réseau directs dans l’environnement de l’agent. La faille centrale reste alors intacte.

Considérez deux chemins de déploiement. Dans le premier, un agent lit un jeton API dans une variable d’environnement et appelle directement le service de déploiement. Une bibliothèque distincte tente d’enregistrer la requête. L’agent peut contourner la bibliothèque, lancer un autre processus ou utiliser un autre client HTTP. Le journal peut rester parfaitement en ajout uniquement tandis que le déploiement n’y apparaît jamais.

Dans le second, l’agent demande à un exécuteur de déployer, mais ne reçoit jamais le jeton. L’exécuteur possède la seule route capable d’ajouter l’identifiant. Il enregistre la demande avant la transmission et met à jour l’événement après la réponse. Le journal ne peut décrire que les actions passées par l’exécuteur, mais l’agent ne dispose d’aucune route autorisée pour le contourner.

RFC 6962, la spécification Certificate Transparency, décrit un arbre de hachage de Merkle qui permet de prouver efficacement qu’une feuille appartient à un arbre et qu’un arbre plus récent prolonge un arbre plus ancien. C’est un modèle d’intégrité utile. Il ne prétend pas qu’un arbre prouve la capture complète des événements réels. Une demande de déploiement qui n’est jamais entrée dans l’arbre ne peut pas apparaître dans une preuve d’inclusion.

La même limite s’applique à une chaîne de hachage linéaire. Un enregistrement peut inclure le condensat de l’enregistrement précédent. Si quelqu’un modifie, supprime ou réorganise les enregistrements stockés, la vérification échoue à l’endroit concerné. Pourtant, une chaîne intacte peut ne couvrir qu’un sous-ensemble soigneusement sélectionné des actions. Placez l’enregistreur sur le chemin de l’action avant de vous féliciter de la chaîne.

Il existe une deuxième limite : les administrateurs peuvent eux aussi menacer la piste. Si un opérateur peut modifier la base du journal et réinitialiser la racine de la chaîne sans témoin externe, la conception protège surtout contre les changements occasionnels. La vérification hors ligne, les points de contrôle protégés et la séparation des accès donnent aux réviseurs une base de confiance plus solide. Aucune de ces mesures ne transforme un journal en récit complet de tout ce qui s’est passé sur une machine.

## Placez l’enregistreur sur le chemin d’exécution

L’enregistreur doit observer l’action là où les identifiants sont ajoutés et où la requête externe quitte le système contrôlé. Journaliser au niveau du framework de l’agent est trop tôt. Journaliser uniquement dans le service distant est souvent trop tard et peut omettre le contexte d’autorisation. La passerelle d’exécution voit à la fois la demande acceptée et le résultat reçu.

Pour HTTP, cette passerelle doit accepter une requête limitée, sélectionner un identifiant stocké, ajouter elle-même cet identifiant, envoyer la requête et renvoyer la partie autorisée de la réponse. L’agent ne doit pas soumettre d’en-têtes `Authorization` bruts. Il doit faire référence à un identifiant par un nom interne que la passerelle résout.

La forme de la requête peut rester assez petite pour être inspectée et enregistrée :

```json
{
  "action": "http.request",
  "credential_ref": "deploy-api",
  "method": "POST",
  "url": "https://deploy.example.internal/releases",
  "headers": {"content-type": "application/json"},
  "body": {"revision": "a18f3c"}
}
```

L’exécuteur doit rejeter les en-têtes d’identification fournis par l’appelant pour cette route. Cela empêche un agent de dissimuler un autre jeton dans la requête tout en laissant une `credential_ref` trompeuse dans le journal. Il doit également normaliser l’URL avant de l’enregistrer, car une URL brute peut contenir des informations d’authentification, des manipulations de chemin encodées ou un secret placé par erreur dans un paramètre de requête.

La même règle s’applique à SSH. L’agent demande l’exécution d’une commande sur une référence d’hôte. L’exécuteur choisit l’identité configurée, le comportement de vérification de l’hôte et la méthode de connexion. Si l’agent peut lire une clé privée et lancer son propre client SSH, le système d’audit ne peut plus prétendre couvrir ses actions distantes.

Ne confondez pas une passerelle avec un proxy généraliste. Un proxy tente d’observer un trafic étendu. Une passerelle d’action propose un ensemble défini d’actions et détient l’autorité nécessaire pour les exécuter. Cette forme plus limitée permet d’énoncer clairement ce que signifie l’enregistrement : cet exécuteur a accepté cette action avec cette autorisation et a obtenu ce résultat.

## Enregistrez l’événement avant toute réponse extérieure

Un enregistrement fiable nécessite un événement de début avant l’envoi de l’action par l’exécuteur, puis un événement de fin après réception du résultat. Si vous n’écrivez qu’après une réussite, un délai d’attente, un crash, l’arrêt du processus ou une rupture réseau peut faire disparaître une demande importante de l’historique.

Utilisez un identifiant d’action généré par l’exécuteur, pas par l’agent. L’enregistrement de début doit contenir l’identifiant de session, l’identité du processus appelant, le type d’action, la cible normalisée, le condensat sûr de la requête, la décision d’autorisation et un horodatage généré par l’enregistreur. Son état doit être `started`.

Après réception d’une réponse ou d’une erreur, l’exécuteur écrit un enregistrement de fin qui fait référence au même identifiant d’action. Il doit inclure une catégorie de résultat comme `succeeded`, `rejected`, `timed_out`, `transport_error` ou `executor_error`, ainsi qu’un condensat de la réponse et des métadonnées limitées. Le réviseur peut alors distinguer « la passerelle l’a rejetée » de « le système distant l’a peut-être reçue, mais aucune réponse n’est revenue ».

Cette distinction a des conséquences opérationnelles. Imaginez qu’un agent envoie `POST /releases` et perde sa connexion après l’acceptation de la requête par le service distant. Réessayer aveuglément peut produire deux versions. Qualifier la première tentative d’échec parce que le client n’a reçu aucune réponse est incorrect. Le journal doit montrer que l’exécuteur a tenté l’appel, que le transport a échoué après le début de la transmission et que l’état distant final reste inconnu jusqu’à confirmation par une autre source.

Une paire minimale d’enregistrements pourrait ressembler à ceci :

```json
{"seq":481,"action_id":"01HV...","state":"started","session":"run-72","type":"http.request","target":"deploy.example.internal/releases","request_sha256":"...","authorized":true,"previous_sha256":"..."}
{"seq":482,"action_id":"01HV...","state":"completed","result":"transport_error","sent":true,"response_sha256":null,"previous_sha256":"..."}
```

Les champs ci-dessus illustrent une structure, pas un schéma universel. Gardez la séquence et le condensat sous le contrôle de l’enregistreur. L’agent peut fournir une étiquette de tâche, mais identifiez-la comme une donnée non fiable et limitez sa longueur. Sinon, l’agent peut placer des secrets, des instructions destinées aux réviseurs ou un texte trompeur dans un champ qui semble officiel.

Un crash entre les deux enregistrements laisse une action inachevée. Conservez-la. Un enregistrement inachevé est une preuve à examiner, pas un encombrement à supprimer pendant la récupération. Au redémarrage, le système peut le marquer comme non résolu s’il dispose d’un moyen fiable de le faire, mais il ne doit pas réécrire l’historique en réussite ou en échec bien ordonné.

## Le schéma doit conserver l’intention et le résultat

Un journal qui stocke seulement « commande exécutée » ne répond presque à aucune question pendant un incident. Un journal qui stocke chaque octet de chaque requête et réponse devient un dépôt de données sensibles. Le juste milieu consiste à enregistrer suffisamment de contexte structuré pour identifier l’action et évaluer l’autorisation, tout en réduisant les secrets et les charges utiles incontrôlées.

Enregistrez l’identité du processus appelant avec des informations que le système d’exploitation peut établir. Sur macOS, l’autorité de signature de code fournit un meilleur point d’ancrage de session qu’un nom d’agent déclaré par l’agent lui-même. Les identifiants de processus sont réutilisés. Un nom affiché peut mentir. Conservez assez de contexte de session pour qu’un réviseur puisse déterminer si un nouveau processus a démarré l’exécution et si l’approbation s’appliquait à ce processus.

Pour une action HTTP, conservez la méthode, l’autorité et le chemin normalisés, la référence de l’identifiant sélectionné, les en-têtes de requête autorisés, le condensat du corps, le résultat de la décision et le statut de réponse lorsqu’il est disponible. Pour une action SSH, conservez la référence de l’hôte, la référence du compte distant si nécessaire, la commande ou son condensat selon sa sensibilité, la référence de l’identité sélectionnée, le résultat de la vérification de l’hôte, le statut de sortie et le condensat de la sortie.

Ne stockez pas les identifiants bruts. Ne laissez pas un jeton bearer se glisser dans une URL, un en-tête personnalisé, des arguments de commande ou une sortie capturée. La suppression après stockage est moins sûre que la prévention de la collecte, car le secret est déjà entré dans les sauvegardes, les répliques ou les exports destinés aux réviseurs.

Les condensats demandent de la prudence. Un condensat ne prouve l’égalité qu’avec un contenu que le réviseur possède déjà. Il ne dit pas à une personne ce qui s’est passé. Pour une charge utile de déploiement, un condensat stocké associé à une révision de dépôt peut bien fonctionner. Pour une commande de base de données destructive, une représentation normalisée de la commande peut être nécessaire, car l’instruction exacte est la preuve dont les réviseurs ont besoin.

Conservez un champ distinct pour la règle ou la décision d’approbation qui a permis l’action. Un réviseur ultérieur doit pouvoir répondre à la question suivante : l’exécuteur a-t-il autorisé cette action parce que la session était approuvée, parce que l’opérateur avait approuvé cette utilisation particulière ou parce que le coffre était ouvert ? Ne réduisez pas ces situations à un vague `allowed: true` si le modèle de contrôle humain dépend de cette distinction.

## La revue nécessite des accès séparés et une vérification indépendante

Les réviseurs ont besoin de plus qu’une table consultable. Ils doivent pouvoir déterminer si la séquence des enregistrements est cohérente et si le système a conservé ses limites. La personne qui examine une exécution ne devrait pas avoir besoin que l’agent résume d’abord son propre comportement.

La chaîne de hachage fournit un contrôle concret. Chaque enregistrement inclut le condensat de l’enregistrement stocké précédent, et l’enregistreur calcule le condensat de l’enregistrement canonique courant. Un vérificateur lit la séquence dans l’ordre, recalcule chaque condensat et vérifie que chaque référence au prédécesseur correspond. Modifier une ancienne cible, supprimer un échec gênant ou permuter deux enregistrements casse la chaîne suivante.

La canonisation est importante. Si un composant hache le texte JSON tandis qu’un autre l’analyse et le sérialise à nouveau, des espaces ou un ordre de champs sans importance peuvent provoquer un échec de vérification. Définissez l’ordre exact des champs, l’encodage des caractères, la gestion des valeurs nulles, la précision des horodatages et l’algorithme de condensat. Versionnez ensuite le format des enregistrements. Un vérificateur doit savoir s’il peut valider une version donnée au lieu de devoir la deviner.

La vérification doit fonctionner à distance du processus qui a créé les enregistrements. Si le contrôle d’intégrité nécessite le même service en cours d’exécution, un service compromis peut mentir au sujet d’une chaîne rompue. Copiez le flux chiffré des enregistrements sur la machine d’un réviseur ou dans une archive protégée, puis exécutez le vérificateur sur cette copie.

Sallyport produit ses journaux Sessions et Activity à partir d’un même journal d’audit chiffré et chaîné par hachage, et `sp audit verify` peut vérifier cette chaîne hors ligne sur le texte chiffré sans clé de coffre. Cette séparation va dans la bonne direction : le contrôle des preuves ne doit pas nécessiter l’exposition des secrets qui ont permis l’action.

La vérification de l’intégrité répond à une question limitée : ces enregistrements forment-ils la séquence attendue par le vérificateur ? Elle ne prouve pas que toutes les réponses du serveur étaient exactes, que le système cible n’avait aucun autre chemin d’accès ni qu’un administrateur n’a jamais remplacé l’intégralité du journal par une ancienne copie valide. Conservez des points de contrôle protégés ou envoyez les racines signées à un système de conservation indépendant si le retour en arrière est important dans votre environnement.

## Les erreurs de conception qui abîment discrètement la piste

Les conceptions les plus dommageables semblent souvent raisonnables dans une démonstration. Elles se brisent lors d’une nouvelle tentative, d’une panne ou lorsqu’un agent utilise la même autorité d’une manière inattendue.

La première erreur consiste à journaliser depuis le SDK de l’agent. Cette approche est populaire parce qu’elle demande quelques lignes de code et offre une vue de trace familière aux développeurs. Elle ne peut pas fournir un enregistrement d’exécution lorsque l’agent obtient des identifiants, lance un autre programme ou appelle une destination que le SDK n’encapsule pas. Considérez les traces du SDK comme des données de débogage.

La deuxième consiste à utiliser un seul enregistrement de réussite. Un enregistrement écrit uniquement après une réponse correcte efface l’ambiguïté. Les opérations réseau ont des résultats ambigus et les commandes distantes peuvent modifier l’état avant la fermeture de la connexion. Enregistrez séparément le début et la fin, puis conservez les débuts inachevés.

La troisième consiste à placer des secrets dans la charge utile d’audit. Les équipes le justifient parfois en affirmant que les réviseurs ont besoin d’une reproduction complète. Ils ont rarement besoin d’un jeton bearer pour comprendre une action, et les secrets copiés transforment le journal en cible de grande valeur. Stockez plutôt des références d’identifiants et des condensats de requêtes.

La quatrième consiste à donner aux réviseurs un index de recherche modifiable et à le traiter comme la source de vérité. Les index de recherche perdent des champs, expirent et permettent des corrections. Conservez un flux d’événements durable comme enregistrement de référence. Construisez une vue de recherche à partir de ce flux et permettez au réviseur de revenir à l’enregistrement original lorsque la vue semble suspecte.

La cinquième consiste à autoriser une large porte de sortie. Une commande comme « exécuter un shell arbitraire avec l’environnement de production » annule une gestion rigoureuse des identifiants et une modélisation précise des événements. Un opérateur peut parfois avoir besoin d’un accès exceptionnel. Faites de cet accès une action approuvée séparément, avec un enregistrement explicite, plutôt que de le dissimuler dans la route normale de l’agent.

## L’approbation humaine et l’aveuglement à la lecture répondent à des risques différents

L’approbation peut empêcher une action dangereuse avant son exécution. L’aveuglement à la lecture conserve les preuves après une demande d’action. Les équipes ont besoin des deux lorsqu’un agent utilise des identifiants aux conséquences importantes.

Une approbation de session convient lorsqu’une personne fait confiance à un processus d’agent précis pour effectuer un travail limité dans le temps. Une approbation par action convient à un identifiant ou à une opération qui mérite l’attention d’une personne à chaque utilisation. Aucun de ces types d’approbation ne remplace un enregistrement d’exécution. Une session approuvée peut tout de même effectuer un appel accidentel, et un agent peut encore décrire incorrectement ce qu’il a fait.

L’écran d’approbation doit identifier le processus appelant sous une forme qu’une personne peut évaluer. Une étiquette déclarée par l’agent comme « assistant de livraison » est peu fiable. Une autorité de signature de code établie fournit à l’opérateur un signal plus solide sur le programme qui a demandé le pouvoir. La décision d’approbation doit faire partie du contexte de l’événement, mais l’agent ne doit pas contrôler la manière dont le système décrit cette identité.

Gardez la protection du coffre distincte de l’approbation. Un coffre verrouillé doit refuser les actions même si une approbation de session antérieure existe. Cela crée un arrêt d’urgence simple : verrouillez l’autorité et l’exécuteur cesse d’utiliser les identifiants stockés. Le journal doit aussi enregistrer les refus. Une suite de demandes refusées peut montrer qu’un agent boucle, effectue des sondages ou continue après la révocation de l’accès par un opérateur.

Ne transformez pas cela en énorme langage de règles sauf si vous en avez réellement besoin. Des règles que personne ne peut examiner pendant un incident donnent une fausse impression de sécurité. Un petit ensemble de contrôles clairs, une interface d’action limitée et des enregistrements que l’agent ne peut pas façonner sont souvent préférables à une pile d’exceptions conditionnelles.

## Construisez la limite autour de l’action qui pourrait vous nuire

Commencez par l’action externe dont l’utilisation abusive entraînerait l’enquête la plus difficile. Il peut s’agir d’une commande SSH de production, d’un appel à une API de livraison, d’une modification de compte ou d’une requête payante. Retirez l’identifiant de l’environnement de l’agent, forcez cette action à passer par un exécuteur et testez les chemins d’échec avant de connecter d’autres outils.

Effectuez ce test rapide sur la conception :

1. Demandez une action, puis arrêtez le processus de l’agent pendant que la requête est en cours. Vérifiez que le journal conserve un événement de début.
2. Faites renvoyer une erreur par la destination distante et vérifiez que l’enregistrement de fin distingue le rejet, l’échec distant et l’échec du transport.
3. Tentez la même action avec un en-tête d’identification ou une clé privée fournis par l’appelant. Vérifiez que l’exécuteur la rejette.
4. Donnez à l’agent ses interfaces habituelles et tentez de lister, modifier ou supprimer les enregistrements d’audit. Vérifiez qu’il n’a aucun moyen de le faire.
5. Copiez le journal stocké ailleurs et vérifiez sa chaîne sans dépendre de l’état actif de l’exécuteur.

Ces tests révèlent des failles que dissimule une démonstration réussie. Ils obligent également à répondre précisément à une question inconfortable : quelles actions l’agent peut-il encore effectuer en dehors de la passerelle enregistrée ? Si la réponse inclut une autorité de production, l’enregistrement est incomplet par conception. Dites-le clairement, fermez la route ou réduisez les affirmations que vous faites sur la piste d’audit.

Un enregistrement d’audit utile commence avant le départ de la requête, se termine avec le meilleur résultat observé et reste hors du contrôle de l’agent. Construisez cette limite avant que le premier incident ne vous demande de faire confiance à la version des faits d’un agent.
