PD-278 — Revue Sécurité (7c)¶
Producteur : ChatGPT Relecteur : Claude (synthèse) Date : 2026-03-01 Commit évalué :
410ed7d(feature/PD-278-nfz42013-dip-state)
Contexte¶
PD-278 ajoute des transitions d'état DIP (Dissemination Information Package) au coffre-fort numérique probatoire NF Z42-013. L'implémentation touche à l'authentification, l'autorisation, le contrôle de concurrence, le rate-limiting, l'audit sécurité, et la protection WORM.
Documents de référence à injecter¶
- Spécification : PD-278-specification.md (§4 Invariants, §6 Cas d'erreur, §7 Critères)
- Plan d'implémentation : PD-278-plan.md (§7 Impacts sécurité)
- Code contracts : PD-278-code-contracts.yaml
Code source à auditer¶
Surface d'attaque (par priorité)¶
- Contrôle d'accès :
dissemination.controller.ts: @UseGuards(JwtAuthGuard, AuthorizationGuard, DisseminationRateLimitGuard)-
Rôles F1: PA, SA, auditor ; Rôles F2: PA, SA, auditor, retention_service
-
Protection anti-exfiltration :
dissemination.service.ts: garde retention_due=false sur SEALED→DIP-
Mécanisme anti-contournement rétention (INV-278-14)
-
Rate-limiting :
-
dissemination-rate-limit.guard.ts: Redis INCR + EXPIRE, fail-closed 503 -
Audit des refus sécurité :
-
dissemination-audit-exception.filter.ts: persistance synchrone DOCUMENT_DISSEMINATION_DENIED -
Intégrité WORM :
- Migration DDL : trigger motif_communication, trigger attestations append-only
-
REVOKE TRUNCATE sur attestations
-
Injection SQL :
-
dissemination.service.ts: requêtes paramétrées $1, $2... -
Cryptographie :
- hash_evidence : SHA-256 de JSON canonique (RFC 8785)
- randomUUID() pour attestation_id, request_id, package_id
Grille d'audit sécurité¶
1. OWASP Top 10 applicabilité¶
| Catégorie OWASP | Risque PD-278 | Mitigation implémentée | Vérification |
|---|---|---|---|
| A01 Broken Access Control | Exfiltration via DIP prolongé | retention_due guard + retention_service BYPASSRLS | Vérifier que toutes les routes sont protégées |
| A02 Cryptographic Failures | Secrets en clair en base | hash_evidence chiffré repos, signature_ref HSM | Scanner les requêtes pour secrets clairs |
| A03 Injection | SQL injection via motif_communication | Paramètres positionnels $1..$N | Vérifier TOUS les queryRunner.query() |
| A04 Insecure Design | Bypass machine à états | ALLOWED_TRANSITIONS exhaustive + SELECT FOR UPDATE | Fuzz transitions interdites |
| A07 Auth Failures | Enumération utilisateurs | Messages d'erreur identiques (DisseminationErrorMessages) | Vérifier que 401 et 403 ne leakent pas d'info |
| A09 Logging Failures | Refus non audités | Exception filter synchrone 401/403/429/409-RETENTION | Vérifier exhaustivité des codes capturés |
2. Contrôle d'accès détaillé¶
- Les @Roles() sont-ils correctement positionnés sur chaque endpoint ?
- Le rate-limit guard est-il UNIQUEMENT sur F1 (SEALED→DIP), pas sur F2 ?
- Le ParseUUIDPipe est-il appliqué sur le param :id de F2 ?
- L'acteur est-il extrait de user.sub (JWT claim), jamais du body ?
3. Protection anti-contournement rétention (INV-278-14)¶
- La garde
retention_due=falseest-elle vérifiée DANS la transaction (pas en cache) ? - Le rôle
retention_servicepeut-il forcer DIP→SEALED même avec RLS ? - L'action retention_service est-elle auditée (DOCUMENT_RETURNED) ?
4. Contrôle de concurrence¶
- SELECT FOR UPDATE avec ORDER BY id ASC partout ?
- Le même ordre de verrouillage est-il documenté cross-module (§5.9) ?
- Timeout de lock configurable pour éviter deadlock infini ?
5. Rate-limiting¶
- Fail-closed si Redis down (503, pas bypass) ?
- Clés Redis avec actorId (pas document_id) ?
- TTL correctement positionné (60s pour rate, 86400s pour quota) ?
- Quota journalier reset à 00:00:00Z UTC ?
6. Audit sécurité¶
- L'exception filter capture-t-il TOUS les codes de refus sécurité (401, 403, 429, 409-RETENTION-DUE) ?
- Les codes métier (409-STATE, 409-CONFLICT, 422-GUARD-COPIES) sont-ils EXCLUS (pas de double audit) ?
- La persistance audit est-elle synchrone (QueryRunner dédié, commit avant response HTTP) ?
7. Intégrité cryptographique¶
- hash_evidence calculé côté serveur (jamais client) ?
- canonicalize() = RFC 8785 (pas JSON.stringify) ?
- randomUUID() = crypto.randomUUID() (pas Math.random) ?
- SHA3-256 pour journal, SHA-256 pour attestation : distinction correcte ?
8. Protection WORM¶
- Trigger DB bloque UPDATE motif_communication ?
- Trigger DB bloque UPDATE/DELETE attestations ?
- REVOKE TRUNCATE sur attestations ?
- COALESCE(motif_communication, $4) : ne permet-il pas de passer de NULL à une valeur puis de modifier ?
Format de verdict attendu¶
### Verdict : ✅ / ⚠️ / ❌
### Vulnérabilités identifiées
| ID | Sévérité (CRITIQUE/HAUTE/MOYENNE/BASSE) | Description | Fichier:ligne | Recommandation |
|----|------------------------------------------|-------------|---------------|----------------|
### Points de robustesse
- ...
### Réserves sécurité
- ...