PD-275 — Revue Sécurité¶
Résumé¶
| Critère | Statut | Commentaire |
|---|---|---|
| Forbidden patterns | ✅ Conforme | Le plan d'implémentation privilégie l'usage de l'ORM, évitant a priori le SQL brut. |
| Injection SQL | ✅ Conforme | L'usage de TypeORM et des requêtes paramétrées prévient efficacement les injections SQL. |
| Auth/Authz | ✅ Conforme | Mécanismes robustes (RolesGuard, double-check service, dérivation revokedBy du JWT). |
| Fuite données sensibles | ⚠️ Réserves | [S-01] La réponse de l'API de révocation peut exposer l'ID de l'administrateur. |
| Validation | ⚠️ Réserves | [S-02] Validation de la configuration FINALITY_DEPTH manquante. [S-03] Normalisation d'adresse non garantie. |
Verdict : ⚠️ RÉSERVES
Le plan d'implémentation est globalement très robuste et démontre une excellente maturité en sécurité, notamment sur la gestion de la concurrence et la prévention des TOCTOU.
Les réserves émises sont mineures ou concernent des points de renforcement qui, une fois adressés, permettront d'atteindre un niveau de confiance maximal.
Audit des forbidden patterns¶
Le plan d'implémentation est conforme aux bonnes pratiques en s'appuyant systématiquement sur l'ORM TypeORM pour les interactions avec la base de données.
| Pattern interdit | Recherché dans le plan | Trouvé |
|---|---|---|
SQL brut (raw) | Utilisation de queryRunner.query() avec concaténation | ❌ Non |
| Désactivation des gardes | Contournement de OidcJwtAuthGuard ou RolesGuard | ❌ Non |
| Secrets en clair | Stockage de clés ou secrets dans le code ou la configuration | ❌ Non |
Tentatives de bypass¶
| Attaque | Résultat attendu (selon le plan) | Commentaire |
|---|---|---|
Injection de revokedBy dans le payload | Rejeté (400 ERR-REVOKEDBY-SPOOFING) | Le controller valide l'absence du champ et dérive revokedBy du JWT. Le DTO ne contient pas le champ. |
| Révocation par un utilisateur non autorisé | Rejeté (403 ERR-REVOKE-UNAUTHORIZED) | Le RolesGuard sur l'endpoint bloque l'appel. |
TOCTOU : submit pendant une revoke concurrente | Rejeté ou sérialisé correctement | Le verrou pessimiste SELECT ... FOR UPDATE sur signer_registry garantit la sérialisation des opérations. Le double-check dans le service submitBatch couvre la race condition. |
| Usurpation de statut (bypass de révocation) | Rejeté (ERR-SIGNER-REVOKED) | La source de vérité (signer_registry) est systématiquement vérifiée sous verrou transactionnel avant toute soumission. |
| Finalisation d'un batch non confirmé | Rejeté (ERR-FINALITY-INSUFFICIENT) | Le FinalityGuardService bloque la transition si confirmation_count < FINALITY_DEPTH. |
Vulnérabilités identifiées¶
| ID | Description | Gravité | Fichier / Composant |
|---|---|---|---|
| S-01 | Fuite potentielle de l'ID de l'administrateur : L'endpoint de révocation POST /anchor/signers/:address/revoke retourne un objet contenant revokedBy, qui est le sub du JWT de l'administrateur. Cet ID pourrait être une information personnelle (email, UUID interne) qu'il n'est pas souhaitable d'exposer publiquement. | MINEUR | C7 — SignerController |
| S-02 | Absence de validation de la configuration FINALITY_DEPTH : Le plan spécifie que FINALITY_DEPTH est lu depuis la configuration, mais ne contractualise pas une validation stricte de cette valeur au démarrage. Une valeur de 0 ou négative, issue d'une erreur de configuration, pourrait permettre une finalisation instantanée des batches, annulant la garde de finalité. | MAJEUR | C5 — FinalityGuardService |
| S-03 | Risque de désynchronisation par absence de normalisation d'adresse : Le plan identifie le besoin de normalisation EIP-55 (checksum) comme une "recommandation" (HT-07) mais ne l'intègre pas comme une exigence contractuelle dans le service. Sans normalisation systématique à l'écriture et à la lecture, une même adresse avec des casses différentes (ex: 0xabc... vs 0xAbC...) pourrait être traitée comme deux signers distincts, permettant un bypass de la révocation. | MAJEUR | C4 — SignerRegistryService |
Recommandations¶
-
[S-01] Masquer l'ID de l'administrateur dans la réponse API :
- Créer un DTO de réponse pour l'endpoint de révocation qui omet le champ
revokedBy. - Alternativement, utiliser l'decorator
@Exclude()declass-transformersur le champrevokedByde l'entitéSignerRegistrypour ne jamais l'exposer via les controllers.
- Créer un DTO de réponse pour l'endpoint de révocation qui omet le champ
-
[S-02] Rendre la validation de
FINALITY_DEPTHnon négociable :- Dans le
FinalityGuardServiceou via un validateur de configuration global (ex:class-validatorsur l'objet de config), ajouter une assertion au démarrage de l'application. - Cette assertion doit garantir que
FINALITY_DEPTHest un nombre entier strictement positif (>= 1). Si la validation échoue, l'application doit refuser de démarrer.
- Dans le
-
[S-03] Intégrer la normalisation EIP-55 comme une contrainte d'intégrité :
- Dans
SignerRegistryService, toutes les méthodes recevant une adresse en paramètre (findByAddress,revokeSigner, etc.) doivent systématiquement la normaliser en EIP-55 (ex: viaethers.utils.getAddress(address)) avant de l'utiliser dans une requête. - La colonne
addressde la tablesigner_registrydoit être contractuellement définie pour ne stocker que des adresses normalisées EIP-55. La migration devrait inclure une contrainte CHECK si possible, ou a minima la logique applicative doit être blindée.
- Dans