Skip to main content

Documentation Index

Fetch the complete documentation index at: https://snakysec.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

LOGGING — Journalisation de preuve numérique

Spécification du système de journalisation de la plateforme MSSP SnakySec. Objectif : produire des journaux recevables comme preuve numérique au sens du Code civil art. 1366 et du règlement eIDAS 910/2014, sans jamais exposer de secret ou d’information sensible en clair. Deux pipelines coexistent :
PipelineRôleRecevabilité juridique
PlatformAuditLog (DB)Journal d’évidence horodaté + chaîné + signéOui — eIDAS / Code civil / NF Z42-020
Pino / stdout / SentryExploitation, debug, observabilitéNon — complément opérationnel

1. Cadre normatif

Norme / texteExigenceImplémentation
Code civil art. 1366Écrit électronique admis comme preuve si l’auteur est identifiable et l’intégrité préservéeChaîne SHA-256 + signature Ed25519 + actor typé
Règlement eIDAS 910/2014 art. 3(35), 32-42Cachet électronique avancé/qualifié, horodatage qualifiéSignature Ed25519 (avancé) — ancrage TSA RFC 3161 (qualifié, Q3 2026)
RGS ANSSI v2.0Fonction de hachage : SHA-2 min. — signature : ECDSA/EdDSASHA-256 (chaîne) + Ed25519 (ancrages)
NF Z42-020Coffre-fort électronique : intégrité, traçabilité, pérennitéChaîne immuable + ancrage quotidien + rétention ≥ 6 ans
ISO 27037 / 27041 / 27042Identification, collecte, préservation et analyse de preuves numériquesSchéma v1 figé, horodatage serveur, ordre strict seq
NIS 2 art. 21 (d)Journalisation sécuritéPipeline complet couvert
RGPD art. 5 (1.f) · art. 32Intégrité et confidentialitéScrubbing obligatoire avant écriture

2. Politique zéro donnée sensible

Règle absolue : aucune information sensible en clair ou exposable ne doit être journalisée. Le journal de preuve sert la recevabilité juridique (imputabilité + intégrité), pas la récupération de secrets.

Denylist (appliquée par le writer, non-contournable)

Source de vérité : platform/src/lib/log-redact.ts.
  • Clés matching : *secret*, *token*, *password*, *key (sauf allowlist), *auth*, cookie, authorization, bearer, vault*, encryption_key, pem, private_key, hmac_key, nonce, refresh_token, access_token, session, credential, client_secret
  • Patterns valeur : JWT, PEM block, Vault token (hvs.*, hvb.*), GitLab PAT (glpat-*), GitHub PAT (ghp_*, ghs_*), Slack token, base64 long (≥ 64 chars sans séparateurs)
  • Allowlist identifiants publics : keyId, keyVersion, publicKeyId, sessionId, tokenId, secretId, keyPrefix

Truncation

Blob > 2 KB → [TRUNCATED]. Profondeur objet > 8 niveaux → refus. Protection anti-bombe sérialisation.

Whitelist par resourceType

Chaque type de ressource déclare un jeu de champs loggables (RESOURCE_LOGGABLE_FIELDS). Tout champ hors whitelist est supprimé avant hachage. Rationale : limiter la surface d’exposition même en cas de régression du scrubber.

Scope unifié

Le même module log-redact.ts alimente :
  • PlatformAuditLog (journal d’évidence)
  • Pino (logs stdout) via PINO_REDACT_PATHS
  • Sentry via beforeSend hook
Aucune divergence possible entre les trois surfaces.

3. Schéma d’événement (PlatformAuditLog)

Version figée schemaVersion: "1". Toute modification = nouvelle version + migration explicite.

Qui · Quoi · Où · Comment · Changement · Destination

GroupeChampsSens
WHOactorType, actorId, actorDisplay, actorIp, actorUserAgent, authMethod, impersonatedByImputabilité forte. authMethod précise la preuve d’authentification (sso.entra, api-key, worker.vault-approle, system)
WHENtimestamp (événement métier), recordedAt (insertion DB), seq (BigInt auto-incrémenté unique)Deux horodatages distincts — le gap révèle les anomalies. seq garantit l’ordre strict
WHATaction (énuméré domain.verb), outcome (SUCCESS/FAILURE/DENIED), severityTaxonomie stable — voir ACTION_LABELS
WHEREresourceType, resourceId, resourceLabel, clientIdIdentification de la cible, rattachement tenant
HOWsourceService (next-app, worker:chain, worker:digest, webhook-gitlab…), requestId, sessionId, routeContexte d’exécution
CHANGEchangeSummary, before, after, reasonDiff sémantique — before/after passent par la whitelist resourceType
DESTINATIONdestinationKind, destinationRefQui/quoi est impacté en aval (email envoyé, pipeline déclenchée, document généré)
INTEGRITYpreviousHash, entryHash, chainHash, preChainChaîne de hachage — voir §4

Actions normalisées (extrait)

auth.login · auth.logout · auth.mfa_challenge
client.create · client.update · client.delete · client.secret.rotate
audit.trigger · audit.cancel · audit.complete
control.review · control.override
grc.document.generate · report.download
vault.read · vault.rotate
audit_log.verify · audit_log.tampering_detected · audit_log.tampering_acknowledged

Exemple — MFA challenge réussi

{
  "seq": 184221,
  "timestamp": "2026-04-14T09:12:04.021Z",
  "recordedAt": "2026-04-14T09:12:04.087Z",
  "actorType": "USER",
  "actorId": "user_01HR...",
  "actorDisplay": "alice@snakysec.fr",
  "actorIp": "203.0.113.42",
  "actorUserAgent": "Mozilla/5.0 ...",
  "authMethod": "sso.entra",
  "action": "auth.mfa_challenge",
  "outcome": "SUCCESS",
  "severity": "INFO",
  "resourceType": "User",
  "resourceId": "user_01HR...",
  "sourceService": "next-app",
  "requestId": "req_01HR...",
  "route": "/api/auth/callback/entra-id",
  "previousHash": "a3f9...c21b",
  "entryHash": "7d4e...0089",
  "chainHash":   "12af...ffab",
  "preChain": false
}

Exemple — Rotation de secret client

{
  "action": "client.secret.rotate",
  "outcome": "SUCCESS",
  "severity": "WARNING",
  "resourceType": "ClientSecret",
  "resourceId": "cs_01HR...",
  "resourceLabel": "SnakySec — Graph cert",
  "clientId": "cl_01HR...",
  "changeSummary": "X.509 cert rotated",
  "before": { "keyId": "kid_2025", "keyVersion": 3 },
  "after":  { "keyId": "kid_2026", "keyVersion": 4 },
  "reason": "annual rotation",
  "destinationKind": "VAULT",
  "destinationRef": "mssp/clients/snakysec"
}
Aucune partie privée ou empreinte exploitable du cert n’apparaît — uniquement les identifiants publics keyId / keyVersion (allowlist).

4. Chaîne d’intégrité

Canonicalisation

Tout événement est sérialisé via RFC 8785 JCS (JSON Canonicalization Scheme) avant hachage. Garantit qu’une représentation unique existe pour un contenu donné, quel que soit l’ordre de champs ou l’encodage des nombres.

Hachage

  • entryHash = SHA-256( JCS(payload) )
  • chainHash = SHA-256( previousChainHash || entryHash )
  • Genèse : genesisHash( "mssp-platform" ) (ou process.env.DEPLOYMENT_ID)

Sérialisation des écritures

Postgres advisory lock (pg_advisory_xact_lock(4815162342008813)) acquis dans la transaction d’insertion. Garantit qu’une seule écriture produit le chainHash suivant — pas de course, pas de divergence.

Ancrage quotidien Ed25519

Worker audit-chain-worker (BullMQ, cron 15 0 * * * UTC) :
  1. Sélectionne toutes les entrées de J-1 (firstSeq, lastSeq)
  2. Calcule rootHash = SHA-256( chainHash(firstSeq) || chainHash(lastSeq) )
  3. Signe le message canonique mssp-audit-anchor:v1|date|firstSeq|lastSeq|rootHash avec la clé privée Ed25519 chargée depuis Vault (mssp/platform/log_anchor_private_key)
  4. Insère une ligne LogAnchor { anchorDate, firstSeq, lastSeq, rootHash, signature, publicKeyId, algo, tsaToken? }
  5. Écrit un événement audit_log.anchor dans le journal (méta-trace)
Rationale Ed25519 vs HMAC : vérification par tiers possible sans partage de clé secrète. La clé publique est publiable (futur : transparency log public, ancrage blockchain optionnel). Futur (Q3 2026) : ancrage sur TSA RFC 3161 qualifiée (ANSSI qualification) → tsaToken, passage de “cachet avancé” à “cachet qualifié” eIDAS.

5. Vérification

Worker automatique — vérification quotidienne

audit-chain-worker, cron 10 0 * * * UTC :
  1. Parcourt toutes les entrées dans l’ordre seq asc
  2. Re-calcule entryHash et chainHash de chaque ligne
  3. Compare à la valeur stockée
  4. Vérifie chaque LogAnchor avec la clé publique Ed25519
  5. Rupture détectée → trigger lockdown (voir §6)

Endpoint manuel

POST /api/v1/admin/audit-log/verify
Authorization: Bearer <MSSP_ADMIN session>
Réponse :
{ "ok": true, "entriesVerified": 184221, "anchorsVerified": 97 }
En cas de rupture :
{
  "ok": false,
  "brokenAtSeq": 182014,
  "expectedHash": "7d4e...",
  "actualHash":   "3b2a...",
  "lockdown": true
}

Runbook — vérification à froid

# Export journal
docker exec -i platform-postgres-1 psql -U mssp -d mssp \
  -c "COPY (SELECT * FROM \"PlatformAuditLog\" ORDER BY seq) TO STDOUT WITH CSV HEADER" \
  > audit-log-$(date +%Y%m%d).csv

# Export ancres
docker exec -i platform-postgres-1 psql -U mssp -d mssp \
  -c "COPY (SELECT * FROM \"LogAnchor\" ORDER BY \"anchorDate\") TO STDOUT WITH CSV HEADER" \
  > log-anchors-$(date +%Y%m%d).csv

# Clé publique (à fournir au vérifieur tiers)
vault kv get -field=log_anchor_public_key mssp/platform > mssp-log-public.pem

# Vérification : script tiers utilise canonicalize + sha256 + ed25519-verify

6. Détection et gestion de tampering

Lockdown automatique

Sur rupture détectée, le worker :
  1. Upsert PlatformState { key: "audit_log_lockdown", value: { locked: true, detectedAt, brokenAtSeq, expectedHash, actualHash } }
  2. Le writer recordAuditEvent() vérifie isAuditLogLocked() avant chaque écriture et bloque (AuditLogLockedError) si verrouillé
  3. Une alerte Sentry critique est levée (audit_log.tampering_detected)
Effet : toutes les écritures applicatives sont stoppées — aucune action métier ne peut masquer l’incident. Les utilisateurs voient un écran d’incident explicite.

Reconnaissance (acknowledgement)

Seul un MSSP_ADMIN peut reprendre la chaîne :
POST /api/v1/admin/audit-log/acknowledge-tampering
Content-Type: application/json
{
  "reason": "Justification ≥ 10 caractères — cause identifiée, corrective action engagée"
}
Transaction atomique :
  1. Calcul newGenesisHash = SHA-256( "ack:<userId>:<detectedAt>:<brokenAtSeq>" )
  2. Signature Ed25519 du bloc ack
  3. Insertion AuditLogAcknowledgement { detectedAt, brokenAtSeq, expectedHash, actualHash, reason, acknowledgedById, newGenesisHash, signature }
  4. Déverrouillage PlatformState.audit_log_lockdown.locked = false
  5. Écriture audit_log.tampering_acknowledged comme première entrée de la nouvelle ère
Garanties :
  • Les entrées pré-rupture restent immuables (leurs chainHash d’origine ne changent pas)
  • La rupture reste documentée (ligne AuditLogAcknowledgement signée, traçable)
  • La nouvelle ère démarre depuis un genesis déterministe dérivé de l’incident (pas d’effacement)
  • L’ack lui-même est scellé dans la nouvelle chaîne

7. Rétention

ClasseDuréeJustification
PlatformAuditLog, LogAnchor, AuditLogAcknowledgement≥ 6 ansPrescription quinquennale Code civil art. 2224 + marge 1 an. Prorogation sur contentieux actif
ControlReviewHistory6 ans (idem)Preuve de diligence — revues manuelles
Pino stdout30 j rotationExploitation uniquement
Sentry90 j (plan SaaS)Observabilité uniquement
Archivage froid : dump PostgreSQL chiffré (AES-256-GCM, clé en Vault) vers stockage WORM externe (SecNumCloud ou équivalent) — automatisé trimestriel P2.

8. Operational runbook

Rotation de la clé d’ancrage Ed25519

  1. Générer nouvelle paire (openssl genpkey -algorithm ed25519)
  2. vault kv put mssp/platform log_anchor_private_key=@new.pem log_anchor_public_key=@new.pub log_anchor_key_id=kid_YYYY-MM
  3. Redéploiement worker — publicKeyId utilisé par l’ancrage suivant est le nouveau
  4. Ne jamais supprimer l’ancienne clé publique — la vérification rétroactive en dépend. Archivage sous mssp/platform/archive/log_anchor_public_key_<kid>

Ajout / modification d’une action

  1. Étendre le type AuditAction + ACTION_LABELS dans audit-log.ts
  2. Revue : scope du champ before/after conforme à la whitelist RESOURCE_LOGGABLE_FIELDS ?
  3. Écrire au moins un call-site avec recordAuditEvent — jamais passer par logAction legacy

Revue à froid d’un incident (forensic)

  1. Snapshot DB (backup point-in-time)
  2. Export journal CSV (cf §5)
  3. Vérification chaîne complète (worker ou script tiers)
  4. Filtrage action, actorId, resourceId, timestamp range
  5. Corrélation avec journaux externes : Traefik access logs, GitLab pipeline logs, Sentry events
  6. Production du rapport d’analyse signé (DOCX + PDF via générateur GRC incident procedure)

9. Références implémentation

ComposantFichier
Schéma Prismaplatform/prisma/schema.prismaPlatformAuditLog, LogAnchor, AuditLogAcknowledgement, PlatformState
Writer scelléplatform/src/lib/audit-log.ts
Primitives hash/signatureplatform/src/lib/log-chain.ts
Scrubbingplatform/src/lib/log-redact.ts
Vérificationplatform/src/lib/audit-chain-verify.ts
Worker cronsplatform/src/jobs/audit-chain-worker.ts
Endpoints adminplatform/src/app/api/v1/admin/audit-log/verify/route.ts · acknowledge-tampering/route.ts

10. Limites connues

  • Ancrage TSA non-qualifié — Ed25519 signé par SnakySec suffit pour eIDAS “avancé”. Pour passer “qualifié” : intégration RFC 3161 avec TSA qualifiée ANSSI (Q3 2026)
  • Pas de transparency log public — la clé publique est publiée mais aucun registre indépendant n’atteste encore des ancres. Option Rekor / Trillian à évaluer
  • Horodatage serveur — basé sur NTP OS. Pour exigences renforcées : horodatage TSA systématique par entrée (coût vs bénéfice à évaluer)
  • Backfill pré-chaîne — entrées antérieures au déploiement du système portent preChain: true et sont exclues de la vérification chaîne (voir migration backfill)