Aller au contenu principal

Journal d’audit

Le journal d’audit enregistre chaque action administrative sur votre tenant : qui a fait quoi, quand, depuis quelle IP, sur quelle ressource. La conservation se fait sur deux niveaux : un niveau chaud interrogeable avec le contenu complet des événements (acteur, action, fields_json) pendant 90 / 180 / 365 / 730 jours, par défaut 90 (offre standard) ou 365 (offres enterprise), configurable par tenant sous Politique d’authentification → Conservation du journal d’audit ; et une archive de chaîne cryptographique de 3 ans dans T Cloud Public OBS en mode WORM, qui conserve les objets d’ancrage de la chaîne de hachage après le caviardage du contenu du niveau chaud. Les exports de conformité couvrent la fenêtre interrogeable.

Pour la chaîne de traçabilité cryptographique (chaîne d’ancrage, engagements merkle stockés dans OBS), voir Intégrité de la chaîne d’audit.

Ce qui est enregistré

Classe d’événementsExemples
IdentitéConnexion, step-up SSO, réinitialisation MFA, provisionnement SCIM, changement de rôle
DépôtDépôt créé, transféré, archivé, supprimé ; protection de branche modifiée
SecretsSecret créé, renouvelé, supprimé (nom uniquement, jamais la valeur)
WebhooksDestination ajoutée, renouvelée, retirée ; livraison rejouée
PolitiqueRègle de protection des push ajoutée ou dérogation appliquée ; dérogation de l’analyse antimalware ; dérogation SAST
FacturationChangement d’offre, mise à jour du moyen de paiement, changement de plafond
Export de donnéesExport mis en file, téléchargé, expiré

Notablement absentes : les lectures ordinaires (clone, consultation, ouverture de fichier). Le journal d’audit est un journal des actions administratives, pas un journal d’accès. L’exception concerne les auditeurs en lecture seule, dont les lectures de vos paramètres sont enregistrées (voir Activité des auditeurs). L’accès aux registres de paquets et de conteneurs (chaque téléchargement, envoi et suppression, avec l’acteur et l’IP du client) figure dans le journal d’accès au registre, distinct, que vous pouvez lire via l’API, transférer vers votre SIEM et inclure dans l’export de données. Les clones et fetch Git ne sont pas encore journalisés requête par requête.

Événements de paquets et de releases

La publication, le marquage, la suppression et le refus des artefacts internes figurent dans le journal d’audit, chaînés par hachage comme tout autre événement et présents dans votre flux SIEM. Deux vues les écrivent :

  • La vue des versions, depuis le registre lui-même : une version est apparue ou a disparu.
  • La vue des requêtes, depuis le proxy d’envoi placé devant chaque écriture dans le registre : un événement par écriture acceptée ou refusée, avec le job derrière un jeton de job et, pour les tags, ce vers quoi le tag pointait auparavant.
ActionQuand
package.publishedUne version de paquet est apparue (son premier fichier est arrivé).
package.deletedUne version de paquet et ses fichiers ont disparu.
package.upload.acceptedUne écriture dans le registre a réussi : un fichier, une publication npm, un fichier supplémentaire dans une version existante, un manifeste de conteneur poussé par digest.
package.upload.refusedfremforge a refusé une écriture : malware, analyseur indisponible, trop volumineux pour être analysé, contrôle sur une couche de conteneur.
package.delete.acceptedUne suppression a réussi (un fichier, une version, un manifeste de conteneur par digest).
package.delete.refusedUne suppression a été refusée (autre que celle d’un robot de registre, ci-dessous).
package.tag.createdUn tag de conteneur ou un dist-tag npm qui n’existait pas désigne désormais un digest.
package.tag.movedUn tag de conteneur désigne désormais un autre digest. previous_digest est celui qu’il désignait auparavant.
package.tag.setUn tag a été écrit et fremforge n’a pas pu déterminer ce qu’il désignait auparavant, ou il désignait le même digest (unchanged: true). Les dist-tags npm sont toujours set.
package.tag.deletedUn tag a été supprimé ; digest est ce qu’il désignait.
package.immutable.refusedL’écrasement d’une version publiée a été refusé (releases immuables) ; les deux sha256.
registry_robot.delete_refusedUn robot de registre a tenté une suppression ; les robots publient mais ne suppriment jamais.
package.files.recorded, package.files.removedLes fichiers d’une version avec leur sha256 (organisations avec politiques de paquets).
forgejo.release.*, forgejo.ref.create, forgejo.ref.deleteUne release publiée, modifiée ou supprimée (avec son identifiant, sa cible, ses indicateurs de brouillon et de pré-release et ses fichiers joints) ; un tag ou une branche git créé (avec son commit) ou supprimé.

Chaque événement de paquet porte :

ChampSignification
org, package_type, package_name, package_version, fileCe qui a été écrit. Une requête nomme ce que son chemin nomme ; la vue des versions nomme la version.
digestsha256:<hex> : le digest d’un manifeste de conteneur, ou le sha256 d’un corps d’envoi brut (le sha256 du fichier lui-même pour tous les types sauf npm et Cargo, dont les corps d’envoi encapsulent le fichier).
actor_kindhuman, robot (un robot de registre), job (un jeton de job Forgejo Actions), bot (un bot système de fremforge), system ou anonymous.
actor_login, actor_idQui le registre a authentifié.
job_repo, job_run_id, job_run_number, job_idPour un jeton de job : le dépôt et l’exécution (/<owner>/<repo>/actions/runs/<job_run_number>).
decision, reasonLors d’un refus, ou d’une écriture qu’un contrôle en mode observation a laissée passer.
access_event_idLa ligne correspondante du journal d’accès au registre, qui contient aussi l’IP du client.

Les événements de la vue des requêtes existent depuis le 2026-10-08. La promotion entre dépôts n’est pas une notion de registre dans fremforge : il n’y a donc pas d’événement de promotion.

Accès aux paquets

L’accès par paquet et les noms protégés écrivent :

ActionQuand
package_access.settings.updatedLa valeur par défaut de l’organisation a changé (default_mode avant et après).
package_access.package.updatedLe paramètre d’accès d’un paquet a été créé ou modifié (mode, inherit_repo, changed).
package_access.package.removedLe paramètre et les autorisations d’un paquet ont été retirés (grants_removed).
package_access.grant.setUne autorisation d’équipe ou d’Actions a été ajoutée ou modifiée (grantee_kind, grantee_name, permission, previous_permission).
package_access.grant.removedUne autorisation a été retirée.
package_protection.rule.created, .updated, .deletedUne règle de nom protégé a changé (package_type, name_pattern, push, delete).
package_access.deniedUne requête a été refusée par l’accès par paquet ou par un nom protégé (access, needed, permission, reason, et les champs d’acteur ci-dessus). Au plus une fois par heure pour le même acteur, paquet et accès.
package_access.would_denyLa même chose, lorsque fremforge exécutait le contrôle en mode observation et a servi la requête.

Analyse des modèles

L’analyse des modèles écrit :

ActionQuand
security_policy.model_scan.updatedLa politique de modèles de l’organisation a changé (changed, et diff avec le from et le to de chaque paramètre).
security_policy.model_scan.dismissedUn résultat a été ignoré pour un fichier, ou sa dérogation renouvelée (sha256, verdict, reason, expires_at, replaced).
security_policy.model_scan.dismissal_revokedUne dérogation a été révoquée (sha256).

Un pull refusé par la politique de modèles est un événement registry.pull.blocked avec gate: model, ci-dessous.

Contrôles sur les pulls de conteneurs

Les contrôles d’images du registre écrivent leurs décisions dans le journal d’audit, si bien qu’elles atteignent votre SIEM comme tout autre événement :

ActionQuand
registry.pull.blockedUn pull de conteneur a été refusé
registry.pull.would_blockUn pull a été servi alors qu’un contrôle l’aurait refusé : votre action de blocage de l’analyse des images est warn, ou le contrôle s’exécute en mode observation

L’acteur est system:registry-pull-gate. Les fields sont :

ChampSignification
gateimage_scan (résultats de vulnérabilité égaux ou supérieurs à votre seuil de blocage), image_malware (paquets malveillants), registry_scan (une couche de l’image que ClamAV a trouvée infectée ou n’a pas pu analyser), registry_blob (la même chose, pour une couche récupérée directement par digest), signature (signatures d’images : reason vaut unsigned, untrusted ou invalid) ou model (analyse des modèles : reason vaut model-malicious, model-suspicious ou model-unscannable)
image, ref, digestL’image, le tag ou digest demandé, et le digest servi
reasonCe que le contrôle a trouvé, par exemple layer-infected ou image-malware
modeenforce, warn (votre action de blocage) ou shadow (mode observation)
block_severity, finding_countimage_scan uniquement : le seuil et le nombre de résultats ouverts égaux ou supérieurs à celui-ci
window_startL’heure que représente cet événement
previous_window_start, previous_window_countLa dernière heure antérieure ayant un événement pour la même clé, et le nombre total de décisions qu’elle a comptées ; null pour la première

Un événement par heure. Un job CI qui récupère une image bloquée en boucle remplirait sinon le journal. Il y a donc un événement par digest d’image, contrôle et décision par heure UTC, écrit à la première décision de l’heure. Les décisions suivantes de cette heure sont comptées, et le compte est indiqué dans l’événement suivant pour la même clé, sous previous_window_count. Le compte d’une heure atteint donc votre SIEM avec l’événement suivant pour ce digest, pas à la fin de l’heure ; le compte de la dernière heure ayant eu des pulls n’est pas envoyé. Un blocked et un would_block pour le même digest, ou deux contrôles, sont des clés distinctes.

Un événement registry.pull.blocked déclenche aussi votre webhook d’alerte security.pull.blocked, si vous en avez abonné un. Chaque pull, autorisé ou non, figure dans le journal d’accès au registre.

Activité des auditeurs

Les membres de l’équipe Auditors de votre organisation peuvent lire les paramètres de votre organisation sans les modifier. Leurs lectures sont enregistrées, pour que vous puissiez voir ce qu’un auditeur a consulté :

ActionQuand
tenant.auditor.readUn auditeur a ouvert une page d’administration ou appelé un point de terminaison de lecture de l’API. Au plus un événement par auditeur et par route toutes les 10 minutes
tenant.auditor.write_refusedUn auditeur a envoyé une requête qui modifierait quelque chose, et elle a été refusée. Chaque tentative
tenant.auditor.read_refusedUn auditeur a demandé une page ou un point de terminaison fermé aux auditeurs, comme l’export de données, et la demande a été refusée. Chaque tentative
compliance.evidence.fetchedQuelqu’un, auditeur ou non, a récupéré le paquet signé de preuves de conformité via l’API. Chaque récupération

Sur les trois événements tenant.auditor.*, l’acteur est le nom d’utilisateur de l’auditeur, et les fields portent la route (le modèle de route, comme /:slug/_admin/members, jamais la chaîne de requête), la method, le status sur tenant.auditor.read, via (portal, oauth ou token) et, pour un jeton d’API, son token_id.

Lire le journal

Administration du tenant → Rapports → Journal d’audit. Filtrez par acteur (<username>, ou un agent tel que mcp-client:<client id> ; le filtre correspond aussi à la personne pour laquelle un agent a agi), par action (secret.created, role.changed, …), par période ou par ressource concernée. La vue par défaut couvre les 7 derniers jours.

Cliquez sur une ligne pour déplier le contenu JSON. Les champs sensibles (valeurs de secrets, jetons OIDC, pièces jointes contenant des données personnelles) sont caviardés au moment de l’écriture : une ligne dépliée n’affiche donc jamais une valeur que vous ne voudriez pas montrer à l’auditeur d’un client.

Export CSV

Cliquez sur Exporter en CSV sur n’importe quelle vue filtrée pour obtenir un instantané. Le CSV contient les lignes actuellement filtrées, dans la limite de 50 000 lignes par export. Pour des fenêtres plus larges, mettez en file un export de données complet depuis Rapports → Export de données.

Via l’API REST

Le journal d’audit est disponible sur l’API publique. Utilisez-la pour envoyer les événements en continu vers votre propre pipeline d’analyse, construire un tableau de bord de conformité ou piloter des requêtes d’audit depuis un script.

  • Interroger le journal GET /_app/api/v1/orgs/:slug/audit (filtrez avec ?actor_kind= et ?since=)
  • Vérifier l’intégrité de la chaîne GET /_app/api/v1/orgs/:slug/audit/integrity
  • Modifier la fenêtre de conservation PATCH /_app/api/v1/orgs/:slug/auth-policy corps : { "audit_full_payload_retention_days": 365 }

Nécessite un Personal Access Token (PAT) avec la portée audit:read (ou policy:write pour modifier la fenêtre de conservation) :

curl -H "Authorization: Bearer ${FREMFORGE_PAT}" \
  "https://frem.sh/_app/api/v1/orgs/acme/audit?actor_kind=human&since=2026-01-01T00:00:00Z"

Voir la référence de l’API REST publique pour la spécification OpenAPI complète.

Transfert vers un SIEM

Chaque événement d’audit est aussi émis vers un point de réception HTTPS propre au tenant si vous configurez le transfert vers un SIEM. Le point de réception reçoit les événements en temps réel (quelques secondes après l’action) dans un format JSON compact ; le format est stable et versionné.

Conservation

Les enregistrements d’audit sont conservés sur deux niveaux :

  1. Niveau du contenu interrogeable : le nom de l’acteur et le contenu fields_json de chaque événement peuvent être interrogés dans l’interface d’administration, l’export CSV et le flux SIEM pendant la fenêtre de conservation ci-dessous.
  2. Niveau de la chaîne infalsifiable : le hachage cryptographique et le lien de chaîne de chaque événement sont conservés 3 ans (ancrés en WORM avec le verrouillage d’objets OBS). C’est ce qui rend la chaîne démontrablement intacte pour les audits ; elle reste intacte même après que le caviardage du niveau du contenu a marqué une ligne.

Conservation du contenu interrogeable

OffrePar défautPréréglages modifiables par le client
fremforge / starter90 jours90 / 180 / 365 / 730
enterprise / enterprise_trial365 jours90 / 180 / 365 / 730

Le plafond de 730 jours (≈2 ans) maintient la taille de la table active audit_events bornée. Au-delà, c’est le rôle du niveau WORM ; si vous avez besoin d’un historique interrogeable au-delà de 730 jours, mettez en file un export de données chaque trimestre et archivez-le dans votre SIEM.

L’échéance s’applique aux colonnes de l’acteur et de fields_json : les lignes plus anciennes que la fenêtre conservent leur hachage, leur horodatage et le nom de leur action (si bien que le reporting SOC 2 / DORA fonctionne toujours), mais l’acteur est pseudonymisé en redacted et le contenu est remplacé par {}. La chaîne cryptographique n’est pas rompue par cette opération ; le caviardage est une pseudonymisation sur place, pas une suppression.

La fenêtre interrogeable correspond à la classe audit_payload de la conservation des preuves, où se règlent aussi les autres classes de preuves. Une conservation légale suspend le caviardage.

Modifier la fenêtre interrogeable

Administration du tenant → Sécurité → Politique d’authentification → carte Conservation du journal d’audit. Choisissez 90 / 180 / 365 / 730 jours. Remarques sur le comportement :

  • Allonger la fenêtre (par exemple 90 → 365) : les lignes nouvellement émises restent interrogeables plus longtemps. Les lignes déjà caviardées sous la fenêtre précédente restent caviardées ; fremforge ne les rétablit pas.
  • Raccourcir la fenêtre (par exemple 365 → 90) : le balayage quotidien de caviardage suivant caviarde chaque ligne plus ancienne que la nouvelle fenêtre. Le caviardage ne peut pas être annulé une fois appliqué ; la chaîne WORM n’est pas affectée.

Action du journal d’audit enregistrée pour ce paramètre : tenant.auth_policy.audit_retention.updated (avec les nombres de jours previous et next).

Engagements de conformité

L’annexe A.7 du DPA engage sur le socle de 90 jours + 3 ans en WORM. Le SLA enterprise engage sur 365 jours interrogeables + 3 ans en WORM. Descendre sous la valeur par défaut de l’offre est permis dans l’interface mais enfreindrait le DPA : fremforge n’impose pas le plancher au niveau de la colonne, c’est le contrat qui le fait. Une défaillance du balayage quotidien de caviardage lui-même (indépendamment d’un client qui modifie la conservation) est surveillée et fait l’objet d’alertes en continu.