PD-277 — Revue Sécurité
Résumé
| Critère | Statut |
| Forbidden patterns | ❌ |
| Injection SQL | ✅ |
| Auth/Authz | ❌ |
| Fuite données | ❌ |
| Validation | ❌ |
Verdict : ❌ NON_CONFORME
Barrières primaires identifiées
- OIDC JWT guard + guard de rôle (
OidcJwtAuthGuard, LegalPreAccessGuard) sur le contrôleur. - Chiffrement at-rest hérité infra (spec INV-277-06, hors logique métier locale).
Lecture defense-in-depth
- Les écarts nonce (S-01, S-02) touchent la barrière primaire anti-rejeu elle-même -> MAJEUR.
- Les écarts authz (S-03) ne sont pas compensés par une barrière ownership/RLS visible dans ce flux -> MAJEUR.
- Les stubs documentés (D-01, D-02) sont classés écarts documentés MINEUR (pas de MAJEUR sans exploit reproductible sur implémentation réelle).
Audit des forbidden patterns
| Pattern interdit | Recherché | Trouvé |
Comparaison nonce côté applicatif (doit être DB JSONB @>) | usedNonces.includes(nonce) | ❌ |
| Acceptation nonce hors format UUID v4 lowercase ASCII | regex avec flag i | ❌ |
| Retry automatique sur erreur de sérialisation | gestion 40001 / retry | ✅ |
| Certificats fournis directement par appelant externe | input API expose cert IDs | ✅ |
| Méthode de modification des certifs après création | update cert fields en service/repository | ✅ |
Tentatives de bypass
| Attaque | Résultat | Commentaire |
Injection champ protégé { "email": "hack@evil.com" } | 400 (attendu) | Validation globale whitelist + forbidNonWhitelisted active dans src/main.ts. |
Injection SQL legalReKeyId="'; DROP TABLE--" | Rejet/échappé (attendu) | Paramètres bindés sur where(... :id); pas d'injection exploitable démontrée. |
Rejeu nonce par casse (nonce puis NONCE) | Bypass possible | Regex accepte uppercase (/i), stockage/compare case-sensitive -> second appel peut passer. |
| Rejeu concurrent (2 requêtes même nonce) | Bypass possible | Check includes hors verrou DB dédié; race possible avant update JSONB. |
| Cross-access JWT user A vers ReKey user B | Bypass possible | Flux consultDocument/getLegalAccessStatus sans ownership check explicite. |
| Bypass auth sans JWT | Refusé (attendu) | Guard OIDC global au contrôleur. |
Vulnérabilités identifiées
| ID | Description | Gravité | Fichier |
| S-01 | Bypass anti-rejeu par variation de casse du nonce. Le format impose lowercase ASCII, mais la regex utilise le flag i. Un attaquant peut réutiliser le même UUID en uppercase/lowercase pour contourner la détection de rejeu basée sur égalité exacte de chaîne. Vecteur: appel 1 avec 550e8400-e29b-41d4-a716-446655440000, appel 2 avec 550E8400-E29B-41D4-A716-446655440000. | MAJEUR | src/modules/legal-pre/services/legal-rekey-manager.service.ts:386 |
| S-02 | Race condition anti-rejeu (TOCTOU). La vérification usedNonces.includes(nonce) est faite côté applicatif sans lock row-level explicite ni contrainte UNIQUE DB sur nonce scope LegalReKey. Deux requêtes concurrentes peuvent valider puis insérer le même nonce. | MAJEUR | src/modules/legal-pre/services/legal-rekey-manager.service.ts:419 |
| S-03 | BOLA/IDOR sur accès ReKey. Les endpoints métier reposent sur un guard de rôle global, mais les opérations consultDocument et getLegalAccessStatus ne vérifient pas que l'appelant est propriétaire/délégué du ReKey ciblé. Un utilisateur légal A connaissant un legalReKeyId B peut accéder aux métadonnées/flux de consultation B. | MAJEUR | src/modules/legal-pre/controllers/legal-pre.controller.ts:101 |
Écarts documentés (mock/stub)
| ID | Description | Gravité | Fichier |
| D-01 | TspVerifierStub et LegalIdentityResolverStub documentés via TODO/stub; ce ne sont pas des preuves d'implémentation crypto/IAM réelle. Classé dette documentée, pas vulnérabilité critique en soi. | MINEUR | src/modules/legal-pre/providers/tsp-verifier.stub.ts:7 |
| D-02 | consultDocument contient un TODO explicite indiquant que l'appel réseau/crypto réel PreService.reEncrypt() n'est pas branché. Écart de complétude, pas exploit réseau direct au stade actuel. | MINEUR | src/modules/legal-pre/services/legal-pre-orchestrator.service.ts:349 |
Recommandations
- Corriger immédiatement la validation nonce en lowercase strict sans flag
i et rejeter toute variante non canonique (ERR-NONCE-FORMAT). - Déplacer la détection d'unicité nonce au niveau DB atomique (ex:
UPDATE ... WHERE NOT used_nonces @> ... RETURNING) ou table dédiée legal_rekey_nonce avec contrainte UNIQUE (legal_rekey_id, nonce) + SERIALIZABLE. - Retirer la logique
includes() applicative pour se conformer au forbidden pattern PD-277 (unicité gérée côté DB uniquement). - Ajouter un contrôle d'autorisation objet (ownership/delegation) sur
consultDocument, closeAccess, getLegalAccessStatus et renvoyer une erreur opaque en cas d'accès cross-tenant. - Ajouter des tests adversariaux e2e reproduisant: (1) rejeu par casse, (2) rejeu concurrent, (3) cross-access user A -> ReKey B.