# Enquêter sur des journaux d'audit d'API lorsque les enregistrements d'un agent se contredisent

Un fournisseur d'API affirme qu'une requête a modifié des données de production. L'enregistrement de l'agent indique qu'il n'est jamais allé jusque-là. Les deux affirmations peuvent être vraies. Traiter l'un ou l'autre journal comme un verdict transforme une divergence limitée en mauvaise réponse à incident.

Enquêtez sur l'action comme sur une chaîne d'observations. Déterminez qui a lancé l'exécution, ce que l'agent a tenté, ce qui a franchi la frontière des identifiants, ce que le fournisseur a accepté et ce qui a changé ensuite. Les horodatages aident à ordonner cette chaîne. Les identifiants de requête la relient. Les résultats et l'état indiquent si l'action a eu un effet. Les événements manquants sont eux aussi des éléments de preuve, mais seulement après avoir écarté les raisons ordinaires pour lesquelles des enregistrements peuvent disparaître.

J'ai vu des personnes commencer par un tableur et trier immédiatement par heure. C'est l'ordre inverse. Un horodatage est souvent le champ de rapprochement le moins fiable. Commencez par des identifiants stables et des exports immuables, puis utilisez le temps pour vérifier que la séquence proposée tient debout.

## Préservez les enregistrements avant que quelqu'un n'actualise un tableau de bord

Capturez les preuves originales avant de filtrer, de recommencer une requête, de révoquer un accès ou de demander une enquête au support du fournisseur. Les tableaux de bord interactifs changent, les tâches de conservation s'exécutent et une nouvelle tentative peut créer le second événement qui brouille le premier.

Ouvrez un dossier de cas avec un identifiant de cas et recueillez des exports bruts, pas seulement des captures d'écran. Incluez l'enregistrement de session de l'agent, les enregistrements de chaque action, l'export d'audit du fournisseur, les journaux de l'application du système concerné et tout enregistrement de sortie réseau disponible. Notez l'heure de collecte en UTC, la personne qui a effectué la collecte, le compte ou le rôle utilisé et le filtre ayant servi à générer chaque export.

Hachez chaque fichier après la collecte. Une commande shell suffit si votre système d'exploitation fournit un utilitaire SHA-256 standard :

```
$ shasum -a 256 provider-events.json agent-activity.json
81b5777b8416320fe26cb8a8dddb6a9e736fab4f5e7aa5812bf6afeffc5f4e82  provider-events.json
a1e98c01992b51104fbc8c5fcbaa78e65db31f1edb3e546f4c14d0e6d3673ba  agent-activity.json
```

Placez les hachages dans une note de cas simple. Le hachage ne prouve pas que l'export du fournisseur est complet. Il prouve que votre copie de travail n'a pas changé silencieusement depuis sa collecte. Ce sont deux affirmations différentes, que les rapports d'incident confondent souvent.

Ne « nettoyez » pas le JSON dans un tableur pour créer la première copie. La normalisation peut supprimer des champs en double, l'ordre des tableaux, les fractions de seconde, les valeurs vides et le corps exact de la requête qui expliquera plus tard une divergence. Conservez un export intact et créez un fichier de travail analysé séparément.

Si la divergence peut impliquer une fuite d'identifiants ou une utilisation non autorisée, limitez l'accès d'une façon qui préserve la séquence. Révoquez une session d'agent active ou bloquez le chemin d'action si vous le pouvez. Évitez de renouveler l'identifiant du fournisseur avant d'avoir recueilli ses enregistrements d'audit récents, sauf si un abus en cours exige un renouvellement immédiat. Ce renouvellement peut être nécessaire, mais il risque d'effacer la dernière piste permettant d'attribuer l'action.

## Un identifiant de requête prime sur un horodatage

Reliez les enregistrements à l'aide d'identifiants qui survivent aux frontières : identifiant de requête du fournisseur, identifiant de corrélation fourni par le client, clé d'idempotence, identifiant d'objet renvoyé par l'écriture et identifiant de trace si le fournisseur en documente un. Conservez tous les identifiants, car un fournisseur peut en exposer des différents dans les en-têtes, les événements d'audit, les exports du support et les corps d'erreur.

Le meilleur cas est simple. L'enregistrement de l'action indique que l'agent a appelé `POST /v1/invoices` ; les en-têtes de réponse contiennent `x-request-id: req_72M...` ; l'export du fournisseur inclut `req_72M...` ; et la facture créée porte l'identifiant `inv_4P...`. Vous disposez alors d'un rapprochement entre l'intention, la transmission, le traitement par le fournisseur et l'état durable.

Les cas difficiles sont plus fréquents. Un fournisseur peut attribuer un identifiant de requête seulement après avoir analysé la requête. Une défaillance TLS n'a alors aucun identifiant de requête du fournisseur, car la requête n'a jamais atteint l'application. Une passerelle peut générer un identifiant, puis le service en aval un autre. Une API asynchrone peut renvoyer un identifiant de tâche, puis écrire l'objet demandé quelques minutes plus tard. Notez quelle frontière a émis chaque identifiant au lieu de les aplatir dans une seule colonne `request_id`.

Utilisez un tableau de rapprochement qui rend l'incertitude visible :

| Champ | Enregistrement local de l'action | Enregistrement du fournisseur | Système concerné |
| --- | --- | --- | --- |
| Identifiant de corrélation client | `run-18-call-42` | `run-18-call-42` | absent |
| Identifiant de requête du fournisseur | `req_72M...` dans la réponse | `req_72M...` | absent |
| Méthode et chemin | `POST /v1/invoices` | `POST /v1/invoices` | facture créée |
| Résultat | `504 timeout` | `202 accepted` | tâche `job_91...` terminée |
| Heure de l'événement | `10:04:03.219Z` | `10:04:03Z` | `10:04:11.802Z` |

Ce tableau révèle une panne classique : l'appelant a expiré, mais le fournisseur a accepté l'écriture et l'a traitée après que l'appelant a abandonné. Il serait faux de qualifier l'action d'« échouée » à cause du résultat de l'appelant. Il serait tout aussi faux de considérer le journal du fournisseur comme la preuve que l'agent en avait l'intention. Les éléments disponibles indiquent que l'agent a envoyé une requête acceptée par le fournisseur, puis que l'appelant n'a pas reçu de réponse à temps.

Si le fournisseur autorise une clé d'idempotence pour les écritures, utilisez-la. Le projet de document Internet de l'IETF consacré à `Idempotency-Key` décrit bien l'objectif pratique : un client peut recommencer une opération HTTP non sûre sans créer accidentellement deux fois le même effet. Le comportement varie selon les fournisseurs, consultez donc leur documentation sur la conservation et les règles de correspondance. Ne supposez pas qu'une simple correspondance du point de terminaison suffit.

Pour les API qui acceptent des en-têtes personnalisés, générez un identifiant de corrélation avant l'appel et envoyez-le dans un en-tête documenté, comme `X-Client-Request-ID`. Enregistrez-le avec l'événement local. Ne placez jamais de secrets, d'instructions, de données utilisateur ou de jetons bruts dans cet identifiant. Une valeur sûre n'a aucun sens en dehors du cas, par exemple `case-2025-041-run7-call18`.

## Le temps peut réfuter une histoire, mais la prouve rarement

Utilisez les horodatages pour encadrer les événements et détecter les ordres impossibles. N'en faites pas un champ d'identité principal lorsque chaque source dispose d'un meilleur identifiant.

RFC 3339 définit un format courant d'horodatage Internet et recommande la forme UTC en majuscules terminée par `Z`, comme `2025-03-08T10:04:03.219Z`. Conservez la chaîne originale même après l'analyse. La différence entre `10:04:03Z` et `10:04:03.219Z` compte lorsqu'une source arrondit à la seconde et qu'une autre indique les millisecondes.

Créez quatre champs temporels pour chaque événement pertinent :

- l'horodatage de la source exactement tel qu'il a été exporté
- l'horodatage UTC normalisé
- le type d'événement, par exemple envoyé, accepté, terminé ou journalisé
- le propriétaire de l'horloge, par exemple le Mac local, la périphérie du fournisseur, le processus de travail du fournisseur ou la base de données

Un horodatage de périphérie du fournisseur peut précéder un horodatage local indiquant la « réception de la réponse » sans contradiction. La fin du traitement par un processus du fournisseur peut suivre l'arrêt du processus de l'agent. Une horloge locale décalée peut faire croire qu'une action a eu lieu avant le début de la session. Ce sont des mécanismes ordinaires, pas des preuves de falsification.

Construisez une fenêtre autour d'un point d'ancrage connu, généralement un identifiant de requête ou le début d'une session. Commencez avec une fenêtre assez étroite pour éviter les rapprochements accidentels. Élargissez-la seulement lorsque vous pouvez en donner la raison : le fournisseur n'enregistre que les secondes, l'opération est asynchrone ou vous avez mesuré un décalage d'horloge par rapport à une référence fiable. Inscrivez la fenêtre choisie dans la note du cas. « Nous avons cherché à peu près à cette heure » n'est pas une méthode.

Soyez attentif à l'heure d'ingestion des journaux. De nombreux systèmes exposent à la fois `event_time` et `created_at`. Le premier indique quand l'événement s'est produit selon le système émetteur. Le second peut indiquer quand un agrégateur l'a reçu ou indexé. Une arrivée tardive ne signifie pas une exécution tardive. Si un événement apparaît après le début d'un incident, examinez les deux champs avant de construire votre récit.

Un test d'ordre utile cherche seulement à savoir si l'histoire proposée est possible. Un événement du fournisseur à 10:04:03 associé à un envoi local à 10:04:03.219 peut être possible si les horloges diffèrent ou si le fournisseur arrondit à l'inférieur. Une fin annoncée à 10:02 alors que le fournisseur affirme avoir accepté la tâche à 10:04 est impossible, sauf si vous avez mélangé deux événements ou mal compris le champ.

## Distinguez tentative, transmission, acceptation et achèvement

Les équipes réduisent souvent quatre états différents au mot « appelé ». Ce raccourci provoque la plupart des désaccords sur les journaux.

Un agent peut tenter une action en construisant une requête. Un composant local peut transmettre des octets à un point de terminaison distant. Le fournisseur peut accepter la requête. Un processus en aval peut produire l'effet attendu. Chaque étape possède son propre enregistrement et son propre mode d'échec.

La spécification HTTP Semantics, RFC 9110, précise que le code d'état décrit la réponse du serveur, et non l'expérience complète de l'appelant. `202 Accepted` indique explicitement que le traitement a été accepté mais n'est pas terminé. `204 No Content` indique que le serveur a traité la requête avec succès, sans expliquer à lui seul tous les effets en aval. Un délai d'attente réseau peut ne produire aucune réponse HTTP alors que le serveur a tout de même traité la requête.

Classez chaque événement contesté avec un statut comme ceux-ci :

- **Tentative uniquement** : un enregistrement d'action local existe, mais rien ne prouve la transmission réseau.
- **Transmise, résultat inconnu** : la requête a quitté la frontière locale, mais l'appelant n'a reçu aucune réponse fiable et le fournisseur ne dispose pas encore d'un enregistrement consultable.
- **Acceptée, effet en attente** : le fournisseur a renvoyé une acceptation ou une référence de tâche, sans état terminé.
- **Terminée** : un résultat du fournisseur et une modification d'état observée concordent.
- **Contredite** : les sources formulent des affirmations qui ne peuvent être vraies simultanément après prise en compte du sens de leurs champs.

« Résultat inconnu » est une conclusion légitime. Ne le transformez pas en échec simplement parce que l'agent a reçu une exception. Pour une opération d'écriture, cette exception doit interrompre les nouvelles tentatives automatiques, sauf si un mécanisme d'idempotence ou une vérification après lecture rend la nouvelle tentative sûre.

L'erreur inverse est tout aussi grave : une réponse `200` ne signifie pas que le résultat métier attendu s'est produit. Un point de terminaison peut renvoyer un succès pour une requête syntaxiquement valide alors qu'une validation ultérieure, une tâche asynchrone ou une dépendance en aval refuse la modification attendue. Inspectez l'objet renvoyé, l'état de la tâche ou l'événement du système cible qui représente l'achèvement selon le contrat de l'API.

## Les événements manquants exigent une explication délimitée

Un enregistrement manquant peut signifier que la requête n'a jamais eu lieu, mais aussi que vous avez interrogé le mauvais service, utilisé le mauvais périmètre de compte, cherché dans le mauvais niveau de conservation ou attendu un enregistrement que le fournisseur ne promet jamais d'émettre.

Examinez les événements manquants dans un ordre fixe. Vérifiez d'abord le compte, le projet, la région, l'environnement et le produit API exacts. Les fournisseurs séparent souvent les vues d'audit selon un ou plusieurs de ces champs. Recherchez ensuite chaque identifiant, puis une fenêtre temporelle et un point de terminaison documentés. Vérifiez si le fournisseur enregistre les requêtes acceptées, refusées, les appels du plan de données, ceux du plan de contrôle ou seulement les actions administratives. Contrôlez enfin la conservation et le délai d'export. Demandez-vous aussi si un proxy, un SDK ou une file asynchrone crée un événement fournisseur différent de celui attendu.

Un cas concret mérite d'être retenu. Un agent envoie `POST /exports` et obtient un délai d'attente de connexion. L'équipe cherche l'identifiant client local dans les journaux d'audit du fournisseur et ne trouve rien. Elle recommence, puis reçoit deux notifications d'achèvement de l'export.

La première requête était destinée à un point d'ingestion régional. L'écran d'audit consulté n'affichait que les événements du plan de contrôle. Le fournisseur avait enregistré la tâche sous un identifiant d'export généré, et non sous l'en-tête client, puis le service de tâches l'avait terminée après le délai d'attente. Rien dans cette séquence ne nécessitait une activité malveillante. Le doublon venait d'une nouvelle tentative d'écriture effectuée avant de vérifier la présence d'une clé d'idempotence, d'un point de consultation de tâche ou d'un marqueur métier.

Ce cas montre aussi pourquoi l'absence doit être formulée avec soin. Dites : « L'export que nous avons recueilli ne contient aucun événement correspondant du plan de données pour cette fenêtre », plutôt que : « Le fournisseur n'a aucun enregistrement. » La première phrase identifie la preuve et sa limite. La seconde avance une affirmation que vous ne pouvez souvent pas étayer.

Lorsqu'un journal attendu est absent, conservez les paramètres de recherche et la documentation du fournisseur indiquant la couverture attendue des événements. Une demande au support sans identifiant de requête exact, périmètre de compte, fenêtre UTC, point de terminaison et hachages des preuves fera perdre des jours.

## Les résultats doivent être inspectés au-delà des codes d'état

Comparez l'intention déclarée de la requête avec le contenu de la réponse et l'effet observable. Les codes d'état renseignent sur un échange de protocole. Ils ne disent pas si la requête était correctement ciblée, si le fournisseur a appliqué une valeur par défaut ou si un agent a envoyé un identifiant obsolète.

Pour chaque action, recueillez les champs suivants lorsque l'API les expose : méthode HTTP, chemin normalisé, identifiant de requête, clé d'idempotence, identité de l'acteur ou des identifiants, code d'état, hachage du corps de réponse, identifiant de l'objet renvoyé et éventuel identifiant de tâche asynchrone. Masquez les identifiants et les données sensibles avant tout partage élargi, mais conservez un original protégé si les règles l'autorisent.

Un hachage du corps de réponse aide à distinguer deux enregistrements `200` qui semblent identiques. Calculez-le sur les octets bruts de la réponse avant toute mise en forme. Si l'API renvoie du JSON et que l'ordre des champs change entre les couches, conservez à la fois les octets bruts et une copie analysée canonique. Ne prétendez pas que des codes d'état identiques signifient des réponses identiques.

Interrogez ensuite la ressource qui devrait exister ou avoir changé. Pour une création, récupérez l'objet renvoyé et comparez son créateur, sa date de création et ses attributs. Pour une mise à jour, récupérez une version, une révision ou une entrée d'audit si le service en fournit une. Pour une suppression, vérifiez que l'objet est absent et qu'un enregistrement d'audit du fournisseur attribue la suppression au même identifiant.

C'est ici que les identifiants très larges compliquent les investigations. Si de nombreux outils partagent un même jeton API, le fournisseur peut souvent indiquer que le jeton a agi, mais pas quel processus local ou quelle personne a lancé l'action. Considérez l'identité de l'identifiant comme une balise de frontière, pas comme l'identité de l'acteur.

## Un enregistrement de passerelle n'est utile que s'il documente la frontière

Une passerelle d'action offre un point d'observation clair entre un agent et l'opération utilisant des identifiants. Elle doit enregistrer le processus ou l'exécution appelante, l'état d'autorisation approuvé, l'opération demandée, le résultat renvoyé à l'agent et suffisamment d'identifiants pour relier les enregistrements du fournisseur. Elle ne doit pas remettre l'identifiant à l'agent, puis présenter la télémétrie locale qui en résulte comme une piste d'audit.

Sallyport conserve les identifiants API et SSH dans son coffre chiffré, exécute elle-même l'action et renvoie le résultat à l'agent, pas le secret. Ses journaux Sessions et Activity sont issus d'un journal d'audit chiffré, aveugle à l'écriture et chaîné par hachage, ce qui fournit à l'enquêteur des enregistrements au niveau de l'exécution et de l'appel. `sp audit verify` peut vérifier cette chaîne hors ligne sur le texte chiffré, sans avoir besoin de la clé du coffre.

Cette conception comble une lacune précise. Un journal fournisseur peut identifier un identifiant et une requête API. Il ne dit pas quel processus d'agent a reçu l'autorisation d'utiliser cet identifiant et ne prouve pas que l'agent n'a jamais vu le secret. Un enregistrement d'audit local ne peut répondre qu'en partie à cette question lorsque la frontière des identifiants se trouve réellement dans le composant qui produit l'enregistrement.

Ne surestimez pas l'enregistrement de la passerelle. Il ne peut pas signaler une requête qui l'a contournée et ne peut pas rendre précise une API fournisseur ambiguë. Il vous donne un point plus solide pour comparer les preuves et un moyen de révoquer une exécution d'agent connue pendant la poursuite de l'enquête.

## Rédigez la conclusion sous forme d'affirmations accompagnées de preuves et de limites

Une bonne conclusion permet à un autre ingénieur de reproduire votre raisonnement sans reprendre vos présupposés. Rédigez des affirmations distinctes sur l'invocation, l'autorisation, la transmission de la requête, le traitement par le fournisseur et l'effet observé. Joignez à chacune les identifiants, les horodatages, les fichiers sources et le sens des champs qui la soutiennent.

Employez un vocabulaire adapté au niveau de confiance. « Le journal d'action indique que le processus X a demandé `POST /v1/invoices` à cette heure. » « L'export du fournisseur contient une requête avec le même identifiant de requête fournisseur. » « La facture existe et ses attributs correspondent à la réponse enregistrée. » Ce sont des affirmations vérifiables. « L'agent a certainement créé la facture » ne peut être justifié que si les rapprochements et la frontière des identifiants l'étayent.

Lorsque les enregistrements divergent, laissez cette divergence visible dans le rapport final. Ne faites pas la moyenne des horodatages et ne supprimez pas la source gênante. Indiquez l'explication la plus probable, les hypothèses écartées et les preuves qui vous manquent encore. Si vous ne pouvez pas établir qu'une écriture est terminée, consignez son état comme inconnu et corrigez le chemin API avant d'autoriser les nouvelles tentatives automatiques.

Après un incident, le changement pratique reste généralement modeste et peu spectaculaire : exiger un identifiant de corrélation, conserver la bonne catégorie d'événements du fournisseur, préserver les horodatages UTC avec leurs fractions de seconde et utiliser l'idempotence pour les écritures. Ces contrôles transforment le prochain désaccord, qui aurait pu devenir un débat forensique, en un court rapprochement.
