PD-250 — Revue Sécurité
Résumé
| Critère | Statut |
| Forbidden patterns | ❌ |
| Injection SQL | ✅ |
| Auth/Authz | ⚠️ |
| Fuite données | ✅ |
| Validation | ⚠️ |
Verdict : ⚠️ RÉSERVES
Barrières primaires identifiées et effet d'atténuation :
- Auth primaire :
OidcJwtAuthGuard + AuthorizationGuard sur l'endpoint admin (401/403), ce qui bloque les bypass simples sans JWT/role. - Barrières probatoires : signature HSM + horodatage TSA + machine d'état terminale (
DESTROYED/RECONCILIATION_FAILED) ; plusieurs écarts observés restent sur des couches secondaires (observabilité/contrat), pas sur une compromission directe de clé. - WORM/conservation : la conservation du bordereau repose sur des invariants de statut/stockage ; l'absence d'un filtre défensif explicite (
entity_type != 'BORDEREAU') est un écart de defense-in-depth.
Audit des forbidden patterns
| Pattern interdit | Recherché | Trouvé |
allowUnknown:false requis dans Joi config | src/modules/destruction/config/destruction.config.ts | ❌ (allowUnknown: true) |
.default(trackedDefault(...)) pour paramètres bornés | src/modules/destruction/config/destruction.config.ts | ❌ (.default(...) direct) |
Exclusion explicite des bordereaux de la sélection (entity_type != 'BORDEREAU') | src/modules/destruction/services/eligibility.service.ts | ❌ |
API BullMQ dépréciées (getRepeatableJobs/removeRepeatableByKey) | src/modules/destruction/** | ✅ (absentes hors tests contractuels) |
Queue name contenant : | src/modules/destruction/config/destruction.config.ts | ✅ |
Tentatives de bypass
| Attaque | Résultat | Commentaire |
Injection champ protégé { "email": "hack@evil.com" } | N/A | Aucun endpoint PD-250 ne permet une mutation directe d'entité via body utilisateur. |
Injection SQL batchId="'; DROP TABLE--" | Échappé | batchId validé @IsUUID('4') + requête paramétrée (:batchId) dans BordereauController. |
| Fuite champ sensible sur GET admin bordereaux | Non observée | Réponse limitée à bordereauId/batchId/createdAt/documentCount/pdfUrl; pas de PII directe. |
| Cross-access JWT user A sur données B | Refusé | Endpoint sous OidcJwtAuthGuard + AuthorizationGuard + @Roles('admin'). |
| Bypass auth sans JWT | Refusé (401) | Bloqué par OidcJwtAuthGuard. |
Vulnérabilités identifiées
| ID | Description | Gravité | Fichier |
| S-01 | Absence d'audit d'accès refusé (403) pour l'endpoint admin bordereaux. Vecteur : un acteur non-admin répète des appels sur /admin/bordereaux; l'accès est bloqué mais l'événement destruction.access_denied n'est pas tracé côté domaine destruction, réduisant la détection/forensic (INV-250-15 partiellement couvert). | MINEUR | src/modules/destruction/controllers/bordereau.controller.ts |
| S-02 | Filtre défensif manquant entity_type != 'BORDEREAU' dans la sélection d'éligibilité. Vecteur : si un incident amont (bug/migration/écriture admin) fait dériver un bordereau vers un état temporellement éligible, le job peut le sélectionner faute d'exclusion explicite. Impact principal : risque de suppression de preuve en cas de rupture d'un invariant amont; atténué par les barrières WORM/statut. | MINEUR | src/modules/destruction/services/eligibility.service.ts |
| S-03 | Violation de contrat de validation config (allowUnknown: true, absence de trackedDefault). Vecteur : dérive de configuration non tracée (variables inattendues), réduisant la capacité de détection d'un mauvais durcissement en exploitation. | MINEUR | src/modules/destruction/config/destruction.config.ts |
Écarts documentés (mock/stub, non classés MAJEUR)
| ID | Description | Gravité | Fichier |
| D-01 | Stub TSA explicite (TODO(PD-250)): le factory TSA_TIMESTAMP_SERVICE rejette systématiquement. Effet : fail-closed (pas de destruction), impact disponibilité/conformité mais pas bypass sécurité direct. | MINEUR (écart documenté) | src/modules/destruction/destruction.module.ts |
| D-02 | S3Service est un stub TODO (upload/delete en succès simulé). Effet : en environnement non mocké, comportement non réaliste ; à traiter avant production. | MINEUR (écart documenté) | src/modules/storage/s3.service.ts |
Recommandations
- Implémenter un audit explicite des refus d'accès admin (
destruction.access_denied) via guard dédié ou exception filter domaine destruction (conforme INV-250-15). - Ajouter l'exclusion explicite
entity_type != 'BORDEREAU' dans selectEligible et reVerifyEligibility pour verrouiller INV-250-06 en defense-in-depth. - Aligner
destruction.config sur le contrat : allowUnknown: false, defaults via trackedDefault(...), et test contractuel bloquant CI. - Remplacer les stubs TSA/S3 par des adapters réels avant mise en prod, puis rejouer TC-250-04/05/06/08/08b/20 en intégration.