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énements | Exemples |
|---|---|
| Identité | Connexion, step-up SSO, réinitialisation MFA, provisionnement SCIM, changement de rôle |
| Dépôt | Dépôt créé, transféré, archivé, supprimé ; protection de branche modifiée |
| Secrets | Secret créé, renouvelé, supprimé (nom uniquement, jamais la valeur) |
| Webhooks | Destination ajoutée, renouvelée, retirée ; livraison rejouée |
| Politique | Règle de protection des push ajoutée ou dérogation appliquée ; dérogation de l’analyse antimalware ; dérogation SAST |
| Facturation | Changement d’offre, mise à jour du moyen de paiement, changement de plafond |
| Export de données | Export 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.
| Action | Quand |
|---|---|
package.published | Une version de paquet est apparue (son premier fichier est arrivé). |
package.deleted | Une version de paquet et ses fichiers ont disparu. |
package.upload.accepted | Une é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.refused | fremforge a refusé une écriture : malware, analyseur indisponible, trop volumineux pour être analysé, contrôle sur une couche de conteneur. |
package.delete.accepted | Une suppression a réussi (un fichier, une version, un manifeste de conteneur par digest). |
package.delete.refused | Une suppression a été refusée (autre que celle d’un robot de registre, ci-dessous). |
package.tag.created | Un tag de conteneur ou un dist-tag npm qui n’existait pas désigne désormais un digest. |
package.tag.moved | Un tag de conteneur désigne désormais un autre digest. previous_digest est celui qu’il désignait auparavant. |
package.tag.set | Un 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.deleted | Un tag a été supprimé ; digest est ce qu’il désignait. |
package.immutable.refused | L’écrasement d’une version publiée a été refusé (releases immuables) ; les deux sha256. |
registry_robot.delete_refused | Un robot de registre a tenté une suppression ; les robots publient mais ne suppriment jamais. |
package.files.recorded, package.files.removed | Les fichiers d’une version avec leur sha256 (organisations avec politiques de paquets). |
forgejo.release.*, forgejo.ref.create, forgejo.ref.delete | Une 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 :
| Champ | Signification |
|---|---|
org, package_type, package_name, package_version, file | Ce qui a été écrit. Une requête nomme ce que son chemin nomme ; la vue des versions nomme la version. |
digest | sha256:<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_kind | human, 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_id | Qui le registre a authentifié. |
job_repo, job_run_id, job_run_number, job_id | Pour un jeton de job : le dépôt et l’exécution (/<owner>/<repo>/actions/runs/<job_run_number>). |
decision, reason | Lors d’un refus, ou d’une écriture qu’un contrôle en mode observation a laissée passer. |
access_event_id | La 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 :
| Action | Quand |
|---|---|
package_access.settings.updated | La valeur par défaut de l’organisation a changé (default_mode avant et après). |
package_access.package.updated | Le paramètre d’accès d’un paquet a été créé ou modifié (mode, inherit_repo, changed). |
package_access.package.removed | Le paramètre et les autorisations d’un paquet ont été retirés (grants_removed). |
package_access.grant.set | Une autorisation d’équipe ou d’Actions a été ajoutée ou modifiée (grantee_kind, grantee_name, permission, previous_permission). |
package_access.grant.removed | Une autorisation a été retirée. |
package_protection.rule.created, .updated, .deleted | Une règle de nom protégé a changé (package_type, name_pattern, push, delete). |
package_access.denied | Une 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_deny | La 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 :
| Action | Quand |
|---|---|
security_policy.model_scan.updated | La 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.dismissed | Un 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_revoked | Une 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 :
| Action | Quand |
|---|---|
registry.pull.blocked | Un pull de conteneur a été refusé |
registry.pull.would_block | Un 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 :
| Champ | Signification |
|---|---|
gate | image_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, digest | L’image, le tag ou digest demandé, et le digest servi |
reason | Ce que le contrôle a trouvé, par exemple layer-infected ou image-malware |
mode | enforce, warn (votre action de blocage) ou shadow (mode observation) |
block_severity, finding_count | image_scan uniquement : le seuil et le nombre de résultats ouverts égaux ou supérieurs à celui-ci |
window_start | L’heure que représente cet événement |
previous_window_start, previous_window_count | La 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é :
| Action | Quand |
|---|---|
tenant.auditor.read | Un 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_refused | Un auditeur a envoyé une requête qui modifierait quelque chose, et elle a été refusée. Chaque tentative |
tenant.auditor.read_refused | Un 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.fetched | Quelqu’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-policycorps :{ "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 :
- Niveau du contenu interrogeable : le nom de l’acteur et le contenu
fields_jsonde 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. - 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
| Offre | Par défaut | Préréglages modifiables par le client |
|---|---|---|
| fremforge / starter | 90 jours | 90 / 180 / 365 / 730 |
| enterprise / enterprise_trial | 365 jours | 90 / 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.