PD-85 — Revue Sécurité¶
Résumé¶
| Critère | Statut |
|---|---|
| Forbidden patterns | ✅ |
| Injection SQL | ✅ |
| Auth/Authz | ✅ |
| Fuite données | ⚠️ |
| Validation | ⚠️ |
Verdict : ⚠️ RÉSERVES
Audit des forbidden patterns¶
| Pattern interdit | Recherché | Trouvé |
|---|---|---|
| createVerify(ALG).update(hash) | Oui | ✅ Absent |
| Math.random() | Oui | ✅ Absent |
| S3 GetObject / decrypt | Oui | ✅ Absent |
| fire-and-forget audit | Oui | ❌ Trouvé dans catch path |
| Secret exposure en DTOs | Oui | ✅ Absent |
Tentatives de bypass¶
| Attaque | Résultat | Commentaire |
|---|---|---|
| SQLi via proofId | Bloqué | DTO UUID v4 + requête paramétrée |
| Cross-access JWT A / proofs B | Bloqué | Ownership check présent |
| Bypass auth sans JWT | Bloqué | OidcJwtAuthGuard |
| Bypass premium FREE | Bloqué | PremiumGuard → 403 |
| 501 proofIds | Bloqué | @ArrayMaxSize(500) |
| Duplicate IDs | Bloqué | @ArrayUnique() |
| Secret pattern "password" | Bloqué | SecretExposureValidator |
Vulnérabilités identifiées¶
| ID | Description | Gravité | Fichier |
|---|---|---|---|
| S-01 | Audit non fail-closed dans catch path : .catch(...) absorbe l'échec d'audit puis continue avec ExportException. Opération sans trace WORM possible | CRITIQUE | export.service.ts:181 |
| S-02 | Énumération d'existence de ressources : messages d'erreur distincts ("Proof not found" vs "Proof not accessible") avec proofId — permet profiling des IDs valides | MAJEUR | export.service.ts:199,213 |
| S-03 | TTL URL signée non borné en code : signedUrlTtlMin utilisé sans clamp <= 30. Mauvaise config = URLs trop longues (INV-85-04) | MAJEUR | export.service.ts:302 |
| S-04 | Secret exposure validator contournable : détection par substring simple (homoglyphes, encodage base64, synonymes non listés). Risque faux négatifs | RÉSERVE | secret-exposure.validator.ts |
| S-05 | DoS applicatif : checkOwnership fait 2 accès DB par proofId (jusqu'à 500). Sous rafale, amplification CPU/DB significative | MOYEN | export.service.ts:192-218 |
| S-06 | UUID case sensitivity : @ArrayUnique compare les strings telles quelles. Deux formes (upper/lower) du même UUID passent comme uniques | MOYEN | complaint-file-request.dto.ts |
| S-07 | Exposition d'infos internes : logger.error(message, stack) + metadata audit avec error.message peut divulguer détails internes | MINEUR | export.service.ts:170-185 |
| S-08 | Metadata audit : ...metadata mergé en dernier peut écraser champs réservés (result, exportId) | FAIBLE | export.service.ts:364 |
Barrières primaires identifiées¶
| Barrière | Composant | Protection |
|---|---|---|
| Auth JWT | OidcJwtAuthGuard | Authentification Keycloak |
| Plan check | PremiumGuard | Filtrage FREE |
| RLS ownership | checkOwnership + SQL paramétré | Isolation données |
| S3 presign | getSignedUrl sans GetObject | Zero-Knowledge |
| Encryption at rest | AES-256-GCM / HSM | Protection données au repos |
Recommandations prioritaires¶
- Rendre l'audit strictement fail-closed dans le catch (si audit KO → 503, jamais absorber)
- Uniformiser les messages d'erreur 404 (même message pour found vs accessible)
- Borner TTL en code :
Math.min(config, 1800)+ validation Joi fail-fast - Remplacer ownership N+1 par requête set-based
WHERE proof_id IN (...)avec JOIN - Normaliser UUID lowercase avant @ArrayUnique