# Les horodatages d’audit d’un agent résistent-ils aux changements d’horloge ?

L’heure de l’horloge est une preuve, pas une garantie de tri. Un agent peut effectuer deux appels parfaitement légitimes alors que l’horloge de l’hôte recule, répète une heure lors du changement d’heure ou est corrigée après un long sommeil. Si votre outil d’audit trie ces appels uniquement selon l’heure affichée, il peut raconter une histoire convaincante mais fausse.

Utilisez un numéro de séquence pour répondre à la question « quel enregistrement ce journal a-t-il accepté en premier ? ». Utilisez un horodatage d’horloge pour répondre à la question « quelle heure calendaire le système d’enregistrement a-t-il signalée ? ». Ce sont deux questions différentes. Les équipes rencontrent des problèmes lorsqu’elles font comme si un seul champ répondait aux deux.

Pour les agents autonomes, cette distinction est concrète. Un réviseur peut devoir établir si une commande SSH a suivi une approbation, si un appel HTTP a été relancé ou si une révocation a eu lieu avant l’action suivante. La réponse doit résister à une mauvaise horloge d’ordinateur portable et à un changement d’heure délicat, pas seulement avoir l’air nette dans un tableau.

## Les horodatages ne peuvent pas établir un ordre fiable des événements

Un horodatage d’horloge ne peut pas prouver qu’un événement a eu lieu avant un autre lorsque l’horloge peut changer. Il indique uniquement la valeur observée par l’horloge au moment où le logiciel a écrit l’enregistrement.

Considérez cette séquence provenant d’une machine qui avançait de dix minutes jusqu’à ce que la synchronisation horaire la corrige :

```text
seq 841  2026-11-03T14:10:12.481Z  agent requested deploy status
seq 842  2026-11-03T14:00:13.107Z  agent requested deploy status
seq 843  2026-11-03T14:00:14.052Z  approval recorded
```

Un tri par horodatage place 842 et 843 avant 841. Le journal a pourtant accepté 841 en premier. Aucune de ces vues n’est une faute de frappe. Elles répondent à des questions différentes.

Cela reste important même lorsque les horodatages augmentent. Une horloge peut avancer lentement, sauter vers l’avant ou être corrigée par un utilisateur. Deux enregistrements dont l’heure calendaire augmente peuvent être plus éloignés, ou plus proches, que ne le laissent entendre leurs valeurs. Un horodatage fournit une coordonnée observée sur une échelle de temps civil. Il ne fournit pas une mesure continue de la durée.

RFC 3339 clarifie le stockage : les horodatages doivent comporter un décalage lorsque l’heure locale est utilisée, et l’UTC exprimé avec `Z` supprime l’ambiguïté du décalage lors des échanges. C’est une bonne pratique, mais RFC 3339 ne transforme pas l’horloge de l’hôte en témoin infaillible. La norme définit une notation, pas la vérité.

Donnez à chaque flux d’audit en ajout uniquement un `seq` qui augmente de façon monotone. Attribuez-le dans le composant qui sérialise les écritures, pas dans chaque processus d’agent. Si deux processus peuvent attribuer localement des numéros puis téléverser leurs enregistrements, vous avez créé deux ordres en prétendant n’en avoir qu’un.

Le numéro de séquence permet d’affirmer quelque chose de limité mais utile :

- Dans un même journal, un `seq` inférieur est entré dans le journal en premier.
- Les trous obligent l’enquêteur à expliquer les enregistrements manquants, retenus ou volontairement exclus.
- Un doublon signifie que le rédacteur, l’importateur ou la couche de stockage a échoué.
- La valeur ne dit rien, à elle seule, sur un événement survenu sur une autre machine.

Ce dernier point est souvent oublié parce qu’un entier qui semble global inspire confiance. Pourtant, ce n’est pas le cas. Un numéro de séquence a besoin d’un espace de noms. `journal_id=macbook-17, seq=841` est une affirmation avec des limites. `seq=841` dans une feuille de calcul invite à tirer des conclusions excessives.

## Le changement d’heure répète les heures locales

Dans de nombreuses régions, le changement d’heure fait répéter une heure à l’horloge locale. Lors du passage automnal, 01:15 se produit une fois avec un décalage UTC, puis une seconde fois avec un autre. Un journal qui ne stocke que `2026-11-01 01:15:00` a supprimé l’information nécessaire pour les distinguer.

Ne tentez pas de réparer cela plus tard en devinant quelle occurrence l’opérateur voulait dire. Capturez suffisamment de contexte lors de l’écriture de l’événement :

```json
{
  "journal_id": "build-mac-07",
  "seq": 842,
  "event_id": "01JXYZ...",
  "recorded_at_utc": "2026-11-01T08:15:00.000Z",
  "local_offset": "-07:00",
  "time_zone": "America/Los_Angeles",
  "event_type": "agent.http.requested"
}
```

Lors de l’occurrence suivante, le même affichage local peut porter `local_offset: "-08:00"` et une valeur UTC décalée d’une heure. Le décalage numérique conserve le fait observé. Le nom du fuseau horaire aide à expliquer pourquoi ce décalage existait, mais il ne doit pas remplacer le décalage dans l’enregistrement.

Les règles des fuseaux horaires changent. Des gouvernements ont modifié les dates de début et de fin, voire l’existence même du changement d’heure, sans se soucier de votre analyseur de journaux. Si vous stockez une date locale et un nom de fuseau, puis recalculez le décalage des années plus tard avec une base de fuseaux mise à jour, vous pouvez afficher les preuves historiques autrement que ne les avait vues la machine. Conservez la valeur UTC originale et le décalage capturé. N’utilisez les règles actuelles pour l’affichage que si l’interface indique clairement qu’il s’agit d’une conversion actuelle.

Le passage au printemps produit un autre problème. Certaines heures locales n’existent jamais. Un système qui accepte une échéance locale saisie par un humain à 02:30 pendant l’heure supprimée doit la refuser ou demander une résolution explicite. La déplacer silencieusement à 03:30 est une décision produit déguisée en calcul calendaire.

Pour les enregistrements d’audit, l’UTC doit être canonique. Affichez l’heure locale lorsqu’elle aide le lecteur, mais indiquez le décalage dans le même champ. `2026-11-01 01:15:00 -08:00` est moins élégant que `01:15`, mais c’est aussi une preuve.

## Une correction manuelle doit créer un nouvel enregistrement

Les modifications manuelles révèlent si une piste d’audit conserve sa valeur ou se transforme en fil d’activité bien présenté. Si un administrateur corrige un horodatage sur place, l’observation originale disparaît. L’enquêteur ne peut plus savoir si l’ancienne horloge était fausse, si les données de l’événement étaient erronées ou si quelqu’un voulait donner une autre apparence à l’historique.

Conservez l’enregistrement original, immuable. Ajoutez un enregistrement de correction qui identifie l’événement antérieur, indique le champ contesté, enregistre la valeur corrigée proposée et explique son fondement. La correction n’efface pas le premier enregistrement. Elle ajoute un fait ultérieur : quelqu’un a formulé et justifié une affirmation à son sujet.

Une charge utile de correction peut rester simple tout en remplissant son rôle :

```json
{
  "seq": 913,
  "event_type": "audit.timestamp_corrected",
  "corrects_event_id": "01JXYZ...",
  "original_recorded_at_utc": "2026-11-03T14:10:12.481Z",
  "asserted_occurred_at_utc": "2026-11-03T14:00:12.481Z",
  "basis": "host time service report and neighboring journal entries",
  "actor": "admin-identifier"
}
```

Utilisez `asserted_occurred_at_utc` uniquement lorsque vous disposez d’éléments justifiant la nouvelle heure. Ne renommez pas l’original `recorded_at_utc` : il décrit la valeur indiquée par l’horloge de l’enregistreur et reste historiquement exact, même si cette valeur était incorrecte.

Séparez ces trois notions dans le schéma comme dans le vocabulaire :

1. `occurred_at` est l’heure à laquelle une action s’est produite, si l’acteur peut l’établir.
2. `observed_at` est l’heure à laquelle un collecteur donné a vu l’action.
3. `recorded_at` est l’heure à laquelle le rédacteur d’audit a validé son entrée.

Elles peuvent être identiques. C’est souvent le cas. Elles ne méritent pas le même nom de champ.

La recommandation courante consistant à « corriger les mauvaises données » vient des systèmes de reporting, où un nombre erroné doit disparaître du tableau de bord. Les systèmes d’audit ont une autre mission. Ils conservent le chemin qui va de l’observation à la conclusion. Une correction visible est moins pratique pour les lecteurs occasionnels, mais elle empêche le système de transformer une incertitude en certitude.

## Les corrections de l’heure réseau peuvent déplacer l’horloge

La synchronisation horaire réseau améliore la précision de l’horloge, mais la correction elle-même peut rendre les horodatages surprenants. Un client peut ajuster progressivement sa vitesse ou déplacer directement l’horloge lorsque l’écart est suffisamment important ou que la politique l’autorise. Un utilisateur qui règle l’heure manuellement, une machine virtuelle qui sort de veille et une horloge firmware mal réglée peuvent produire le même résultat visible : l’heure calendaire change entre deux entrées d’audit.

La documentation de POSIX `clock_gettime` distingue l’horloge temps réel de l’horloge monotone. `CLOCK_REALTIME` suit l’heure calendaire et peut être réglée. `CLOCK_MONOTONIC` n’a pas d’origine calendaire utile, mais n’est pas réglée par `clock_settime` ; c’est le type de source adapté à la mesure d’un intervalle dans un système en fonctionnement.

Cette distinction donne une règle pratique pour les données. Enregistrez l’heure calendaire pour l’enquêteur. Enregistrez aussi une valeur monotone lorsque vous devez raisonner sur la durée écoulée, le comportement d’un délai ou l’ordre autour d’une correction. Ne sérialisez pas une valeur monotone comme s’il s’agissait d’une date. Sa valeur absolue n’a de sens que par rapport à son démarrage et à son domaine d’horloge.

RFC 8633, les recommandations de l’IETF pour l’exploitation de NTP, traite explicitement des grands décalages horaires et indique que les opérateurs ne doivent pas contourner aveuglément le seuil de panique NTP lors des démarrages à froid. La tentation habituelle consiste à considérer chaque correction comme une simple opération d’entretien. Elle ne l’est pas lorsque l’heure contrôle la validité des jetons, la conservation des données, la reconstitution d’un incident ou la détection des relectures.

Votre système d’audit doit signaler les anomalies temporelles comme des faits d’audit. Il ne doit pas tenter de les dissimuler avec une règle de tri. Un événement utile peut contenir la dernière valeur de l’horloge, la nouvelle valeur, l’écart estimé, la source du changement si elle est connue et le processus qui l’a détecté. Si le système d’exploitation n’expose pas la cause, dites-le. Une fausse explication vaut moins qu’une explication absente.

Prévoyez une petite tolérance pour les fluctuations ordinaires. Ne créez pas un événement spectaculaire de changement d’heure parce que des valeurs voisines diffèrent de quelques millisecondes dans une direction inattendue entre des rédacteurs concurrents. Le `seq` sérialisé fournit l’ordre. Signalez une discontinuité lorsque l’horloge recule au-delà de la précision annoncée ou lorsqu’un saut vers l’avant contredit l’activité attendue et mérite un examen.

## Les numéros de séquence ont besoin d’un périmètre défini

Un numéro de séquence ne fonctionne que dans le journal qui l’a attribué. Le traiter comme un ordre global après la sortie des enregistrements de ce journal produit de fausses conclusions dans les systèmes distribués d’agents.

Supposons qu’un agent de programmation sur un Mac demande une action d’API et qu’un hôte de compilation distant écrive une action SSH. Chaque hôte possède son propre journal :

```text
build-mac-07  seq 842  14:00:13Z  HTTP action requested
build-host-2  seq  91  14:00:14Z  SSH action accepted
build-mac-07  seq 843  14:00:15Z  HTTP result received
```

Vous pouvez dire que 842 précède 843 dans `build-mac-07`. Vous pouvez dire que 91 a été enregistré par `build-host-2` à l’heure signalée par cet hôte. Vous ne pouvez pas prouver, à partir de ces seuls champs, si l’hôte distant a accepté l’action SSH avant ou après la première requête HTTP en temps réel.

Pour établir des relations entre systèmes, ajoutez un lien explicite. Un identifiant de requête transmis de l’appelant au récepteur peut relier l’événement de requête à l’événement de réception. Un accusé de réception signé par le récepteur peut fournir une preuve plus solide. Un collecteur central peut attribuer une séquence lors de la réception des enregistrements, mais cette séquence prouve l’ordre d’arrivée chez le collecteur, pas l’ordre d’apparition sur les sources.

Ne tirez pas de conclusions excessives :

- Un identifiant de requête prouve la corrélation lorsque les deux côtés le conservent. Il ne prouve pas l’heure de livraison.
- Une séquence d’ingestion centrale prouve l’ordre d’ingestion. Le délai réseau peut inverser l’ordre d’arrivée.
- Une horloge synchronisée réduit l’incertitude. Elle ne la supprime pas.
- Une horloge logique distribuée peut exprimer la causalité si chaque participant la transporte correctement. Elle ne fournit pas l’heure calendaire.

Pour beaucoup de pistes d’audit d’agents, vous n’avez pas besoin d’un vaste système d’ordre distribué. Vous avez besoin de limites honnêtes. Conservez une séquence locale pour chaque journal de confiance, incluez des identifiants de corrélation aux limites des actions et exposez le journal source dans chaque export. Le réviseur peut ainsi voir où les preuves sont solides et où elles deviennent une déduction.

## Stockez suffisamment de preuves temporelles pour expliquer un désaccord

Un simple champ `timestamp` n’est pas un schéma d’audit. C’est une préférence d’affichage qui s’est retrouvée dans le stockage.

Utilisez une structure qui sépare l’ordre, l’heure calendaire, l’identité de la source et les éléments d’intégrité. Les noms exacts des champs vous appartiennent, mais les concepts doivent survivre à l’export et à la conservation :

```json
{
  "journal_id": "build-mac-07",
  "seq": 842,
  "event_id": "01JXYZ...",
  "recorded_at_utc": "2026-11-03T14:00:13.107Z",
  "recorded_offset": "-08:00",
  "time_zone": "America/Los_Angeles",
  "monotonic_ns": 3982188001123,
  "boot_id": "boot-identifier",
  "event_type": "agent.http.requested",
  "correlation_id": "request-identifier",
  "actor_process": "process-identifier",
  "previous_hash": "hex-value",
  "record_hash": "hex-value"
}
```

`boot_id` évite une erreur fréquente avec les valeurs monotones. Un compteur monotone peut recommencer après un redémarrage ; `3982188001123` provenant d’un démarrage ne peut donc pas être comparé à la même valeur issue d’un autre sans contexte supplémentaire. Ne le stockez que si vous comptez l’utiliser et documentez son unité. Un champ nommé `monotonic_time` qui laisse le lecteur deviner s’il s’agit de nanosecondes, de millisecondes ou de graduations propres à la plateforme gaspille cet espace.

`recorded_offset` est le décalage en vigueur lorsque le rédacteur a enregistré l’événement. Il ne remplace pas l’UTC. Il permet de voir le contexte de l’heure locale de la source et à un formateur de conserver l’interprétation civile originale. N’incluez un fuseau horaire nommé que si la plateforme peut le fournir de manière fiable. Un décalage fixe suffit pour l’ordre et la reconstitution.

Les champs de hachage ont besoin de règles tout aussi claires. Calculez le hachage d’un enregistrement sur une représentation canonique de chaque champ dont la modification changerait le sens de l’enregistrement, notamment `seq`, les horodatages, le type d’événement, l’identité de l’acteur et le condensat de la charge utile. Ne hachez pas un objet JSON joliment formaté si différents sérialiseurs peuvent modifier les espaces, l’ordre des propriétés ou le format des nombres. Canonicalisez d’abord.

Une chaîne de hachage détecte la modification d’un enregistrement lorsque la vérification part d’une ancre de confiance et que chaque enregistrement s’engage sur le précédent. Elle ne prouve pas que l’horloge source était exacte. Elle ne prouve pas qu’un événement s’est produit hors de la machine. Elle établit une affirmation plus limitée mais importante : l’historique vérifié a conservé les relations cryptographiques produites par le rédacteur.

C’est pourquoi la chaîne et la séquence vont ensemble. La séquence donne l’ordre local. La chaîne rend visible une réécriture ultérieure. L’horodatage ajoute le contexte calendaire. Aucun de ces éléments ne peut remplacer les autres.

## Enquêtez sur les retours en arrière sans inventer d’histoire

Lorsqu’une séquence ultérieure possède un horodatage antérieur, commencez par les preuves déjà disponibles. Ne concluez pas immédiatement à une relecture, à un bug de l’agent ou à une altération.

Suivez cette courte procédure :

1. Vérifiez la continuité de la séquence du journal et le résultat d’intégrité avant d’interpréter les valeurs temporelles.
2. Comparez les valeurs UTC antérieure et ultérieure, les décalages locaux, les identifiants de démarrage et les éventuelles mesures monotones.
3. Vérifiez si l’hôte a franchi une limite de changement d’heure, redémarré, quitté un état de veille ou enregistré une correction du service de temps.
4. Suivez les identifiants de corrélation dans les journaux voisins, mais présentez le temps entre hôtes comme une estimation, sauf si un accusé de réception ou un mécanisme d’ordre partagé le prouve.
5. Ajoutez un enregistrement d’annotation si les preuves justifient une correction ou la constatation d’une anomalie temporelle.

Voici un incident qui apparaît dans les véritables revues. Un agent demande une action d’API à `09:02:04`, la machine sort de veille, le temps réseau fait reculer l’horloge de quatre minutes et le résultat de l’action est enregistré à `08:58:07`. Un tableau de bord trie chronologiquement et affiche le résultat avant la requête. Un opérateur conclut que le résultat a été rejoué, bloque l’agent et commence à renouveler les identifiants.

La séquence et les valeurs monotones racontent une histoire plus simple. La requête est la séquence 117. Le résultat est la séquence 118. Les deux ont le même identifiant de démarrage. Le compteur monotone avance d’environ trois secondes. L’horloge a changé entre les deux entrées. Dans ce journal, le résultat suit la requête et l’affichage calendaire est incorrect après la correction.

Cette conclusion laisse encore des questions utiles. Pourquoi l’horloge différait-elle de quatre minutes ? L’action dépendait-elle d’un identifiant avec une expiration limitée dans le temps ? Un autre système a-t-il reçu la requête avant la correction ? L’enquête doit répondre séparément à ces questions. Elle ne doit pas imposer un ordre temporel propre parce qu’un lecteur le préfère.

Si un contrôle d’intégrité échoue, cessez de traiter le journal comme une chronologie établie. Conservez l’export, consignez l’échec de vérification en dehors du journal concerné si possible et obtenez une nouvelle copie par un chemin indépendant. Une chaîne défaillante n’identifie pas la personne ou le processus qui a modifié les données. Elle indique que les preuves ne permettent plus d’affirmer que l’historique est resté inchangé.

## Une piste détectant les altérations doit pouvoir être vérifiée hors ligne

Un journal d’audit a peu de valeur médico-légale si sa seule preuve d’intégrité dépend du service actif qui l’a produit. Le réviseur doit pouvoir exporter les enregistrements, les transférer sur une autre machine et vérifier la chaîne sans utiliser le coffre ni interroger l’agent qui a effectué les actions.

Sallyport projette ses journaux Sessions et Activity depuis un journal d’audit chiffré et chaîné par hachage, et `sp audit verify` peut vérifier la chaîne hors ligne sur le texte chiffré sans clé de coffre.

Cette conception répond à un problème différent de la précision de l’horloge. La vérification hors ligne indique si l’historique chiffré reste cohérent avec le dispositif d’audit. Elle ne certifie pas l’horloge de l’hôte. Séparez ces deux affirmations dans les rapports : « la chaîne d’audit a été vérifiée » et « l’heure de l’événement a été corroborée par une source indépendante » sont toutes deux utiles, mais aucune n’implique l’autre.

Les procédures d’export doivent conserver l’identité du journal, la plage de séquences, le résultat de vérification et la version du logiciel qui a effectué cette vérification. Un fichier CSV contenant uniquement un horodatage et le texte de l’action est un rapport, pas une preuve d’audit. Il perd les champs qui permettraient ensuite de contester ou de confirmer le rapport.

## Concevez l’interface autour des désaccords, pas seulement du tri idéal

Une bonne interface d’audit ne dissimule pas les anomalies d’horloge. Elle trie par séquence du journal par défaut dans un même journal, affiche l’UTC avec l’heure locale lorsque l’utilisateur développe une ligne et marque un retour en arrière comme une discontinuité temporelle au lieu de réorganiser l’historique.

Proposez plusieurs vues, mais nommez-les avec précision. « Ordre du journal » signifie l’ordre des séquences. « Heure calendaire signalée » signifie l’ordre des horodatages et peut placer en premier des enregistrements arrivés causalement plus tard. « Ordre d’arrivée au collecteur » signifie l’ordre dans lequel un autre service a reçu les données. Évitez un tri générique par « heure », car il rend invisible un choix important pour l’analyse médico-légale.

L’interface doit aussi montrer les limites de sa certitude. Lorsque les événements proviennent de plusieurs journaux, regroupez-les par source ou affichez clairement la source à côté de chaque numéro de séquence. Si un identifiant de requête relie des enregistrements, présentez cette relation dans le modèle de données comme dans l’interface. Ne tracez pas une ligne temporelle continue entre les hôtes si votre système ne peut pas la défendre.

La première action est simple : examinez l’un de vos exports existants et cherchez un horodatage local sans décalage, un numéro de séquence sans identité de journal ou un chemin permettant une modification sur place. Un seul de ces éléments suffit à créer une chronologie d’incident trompeuse. Le corriger maintenant coûte moins cher que de devoir l’expliquer après qu’une action d’agent est devenue une preuve.
