> ## Documentation Index
> Fetch the complete documentation index at: https://docs.snakysec.com/llms.txt
> Use this file to discover all available pages before exploring further.

# LOGGING

# 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 :

| Pipeline                | Rôle                                         | Recevabilité juridique                    |
| ----------------------- | -------------------------------------------- | ----------------------------------------- |
| `PlatformAuditLog` (DB) | Journal d'évidence horodaté + chaîné + signé | **Oui** — eIDAS / Code civil / NF Z42-020 |
| Pino / stdout / Sentry  | Exploitation, debug, observabilité           | Non — complément opérationnel             |

***

## 1. Cadre normatif

| Norme / texte                                  | Exigence                                                                                    | Implémentation                                                        |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| **Code civil art. 1366**                       | Écrit électronique admis comme preuve si l'auteur est identifiable et l'intégrité préservée | Chaîne SHA-256 + signature Ed25519 + actor typé                       |
| **Règlement eIDAS 910/2014** art. 3(35), 32-42 | Cachet électronique avancé/qualifié, horodatage qualifié                                    | Signature Ed25519 (avancé) — ancrage TSA RFC 3161 (qualifié, Q3 2026) |
| **RGS ANSSI v2.0**                             | Fonction de hachage : SHA-2 min. — signature : ECDSA/EdDSA                                  | SHA-256 (chaîne) + Ed25519 (ancrages)                                 |
| **NF Z42-020**                                 | Coffre-fort électronique : intégrité, traçabilité, pérennité                                | Chaîne immuable + ancrage quotidien + rétention ≥ 6 ans               |
| **ISO 27037 / 27041 / 27042**                  | Identification, collecte, préservation et analyse de preuves numériques                     | Sché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. 32                | Inté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](../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

| Groupe          | Champs                                                                                                      | Sens                                                                                                                     |
| --------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **WHO**         | `actorType`, `actorId`, `actorDisplay`, `actorIp`, `actorUserAgent`, `authMethod`, `impersonatedBy`         | Imputabilité forte. `authMethod` précise la preuve d'authentification (sso.entra, api-key, worker.vault-approle, system) |
| **WHEN**        | `timestamp` (é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                                  |
| **WHAT**        | `action` (énuméré `domain.verb`), `outcome` (SUCCESS/FAILURE/DENIED), `severity`                            | Taxonomie stable — voir `ACTION_LABELS`                                                                                  |
| **WHERE**       | `resourceType`, `resourceId`, `resourceLabel`, `clientId`                                                   | Identification de la cible, rattachement tenant                                                                          |
| **HOW**         | `sourceService` (next-app, worker:chain, worker:digest, webhook-gitlab…), `requestId`, `sessionId`, `route` | Contexte d'exécution                                                                                                     |
| **CHANGE**      | `changeSummary`, `before`, `after`, `reason`                                                                | Diff sémantique — `before`/`after` passent par la whitelist `resourceType`                                               |
| **DESTINATION** | `destinationKind`, `destinationRef`                                                                         | Qui/quoi est impacté en aval (email envoyé, pipeline déclenchée, document généré)                                        |
| **INTEGRITY**   | `previousHash`, `entryHash`, `chainHash`, `preChain`                                                        | Chaî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

```jsonc theme={null}
{
  "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

```jsonc theme={null}
{
  "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

```http theme={null}
POST /api/v1/admin/audit-log/verify
Authorization: Bearer <MSSP_ADMIN session>
```

Réponse :

```json theme={null}
{ "ok": true, "entriesVerified": 184221, "anchorsVerified": 97 }
```

En cas de rupture :

```json theme={null}
{
  "ok": false,
  "brokenAtSeq": 182014,
  "expectedHash": "7d4e...",
  "actualHash":   "3b2a...",
  "lockdown": true
}
```

### Runbook — vérification à froid

```bash theme={null}
# 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 :

```http theme={null}
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

| Classe                                                     | Durée            | Justification                                                                                  |
| ---------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------- |
| `PlatformAuditLog`, `LogAnchor`, `AuditLogAcknowledgement` | **≥ 6 ans**      | Prescription quinquennale Code civil art. 2224 + marge 1 an. Prorogation sur contentieux actif |
| `ControlReviewHistory`                                     | 6 ans (idem)     | Preuve de diligence — revues manuelles                                                         |
| Pino stdout                                                | 30 j rotation    | Exploitation uniquement                                                                        |
| Sentry                                                     | 90 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

| Composant                 | Fichier                                                                                                                                                                                                                             |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Schéma Prisma             | [platform/prisma/schema.prisma](../platform/prisma/schema.prisma) — `PlatformAuditLog`, `LogAnchor`, `AuditLogAcknowledgement`, `PlatformState`                                                                                     |
| Writer scellé             | [platform/src/lib/audit-log.ts](../platform/src/lib/audit-log.ts)                                                                                                                                                                   |
| Primitives hash/signature | [platform/src/lib/log-chain.ts](../platform/src/lib/log-chain.ts)                                                                                                                                                                   |
| Scrubbing                 | [platform/src/lib/log-redact.ts](../platform/src/lib/log-redact.ts)                                                                                                                                                                 |
| Vérification              | [platform/src/lib/audit-chain-verify.ts](../platform/src/lib/audit-chain-verify.ts)                                                                                                                                                 |
| Worker crons              | [platform/src/jobs/audit-chain-worker.ts](../platform/src/jobs/audit-chain-worker.ts)                                                                                                                                               |
| Endpoints admin           | [platform/src/app/api/v1/admin/audit-log/verify/route.ts](../platform/src/app/api/v1/admin/audit-log/verify/route.ts) · [acknowledge-tampering/route.ts](../platform/src/app/api/v1/admin/audit-log/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)
