Aller au contenu

PD-275 — Revue Sécurité

Résumé

Critère Statut
Forbidden patterns ⚠️
Injection SQL
Auth/Authz ⚠️
Fuite données
Validation ⚠️

Verdict : ⚠️ RÉSERVES

Contrainte globale : l'implémentation est globalement robuste côté SQLi/concurrence/traçabilité, mais 3 écarts de sécurité (dont 1 écart documenté de stub) empêchent un verdict CONFORME.

Audit des forbidden patterns

Pattern interdit Recherché Trouvé
Subquery WHERE d'index partiel Migrations PD-275 ✅ Absent
Type-change non réversible Migrations PD-275 ✅ Absent
DROP TABLE/COLUMN en up Migrations PD-275 ✅ Absent
Transition REVOKED -> * / état REACTIVATED signer-status.enum.ts ✅ Absent
Lecture sans verrou pessimiste (revoke/assertActive) signer-registry.service.ts ✅ Absent
revokedBy depuis paramètre externe signer.controller.ts, service ✅ Absent (body rejeté, valeur JWT sub)
Finalisation sans garde de seuil anchor-batch.service.ts ✅ Absent
FINALITY_DEPTH en dur (non configurable) finality-guard.service.ts ✅ Configurable via ConfigService
Route sans OidcJwtAuthGuard / RolesGuard signer.controller.ts ⚠️ Pas de garde locale explicite (dépendance APP_GUARD + check service)

Tentatives de bypass

Attaque Résultat Commentaire
{ "revokedBy":"hack@evil.com" } Rejet (400) Anti-spoofing explicite via req.body + code ERR-REVOKEDBY-SPOOFING
address="'; DROP TABLE--" Non exploitable Requêtes TypeORM paramétrées (findOne/update/save)
Requête sans JWT Refus (403) Fail-closed via absence req.user.sub
JWT sans rôle ADMIN/SIGNER_ADMIN Refus Check OR côté service + audit deny
Double revoke concurrent 1 succès + 1 conflit attendu SELECT ... FOR UPDATE + test statut REVOKED
Finalize sans confirmations suffisantes Refus assertFinalityReached() + audit FINALITY_CHECK_FAILED
Submit signer révoqué (si signerAddress présent) Refus assertSignerActive() sous transaction
Submit signer révoqué (signerAddress absent) ⚠️ Bypass possible Le check est conditionnel (if (signerAddress))

Vulnérabilités identifiées

ID Description Gravité Fichier
S-01 Bypass possible du contrôle signer actif si signerAddress est indéfini : process() saute assertSignerActive, puis submitBatch() saute aussi le contrôle (conditionnel). Vecteur : config sans anchor.signerAddress + intégration wallet non finalisée. Impact : ancrage possible sans preuve runtime de statut ACTIVE. MAJEUR src/modules/anchor/processors/blockchain-anchor.processor.ts, src/modules/anchor/services/anchor-batch.service.ts
S-02 finalityDepth non borné/validé au démarrage. Vecteur : config anchor.finalityDepth <= 0 (ou incohérente) → garde de finalité neutralisable logiquement. Impact : finalisation prématurée (violation INV-275-02) en cas de dérive config. MAJEUR src/modules/anchor/services/finality-guard.service.ts
S-03 Validation d'entrée incomplète sur address (format EIP-55 non enforced). Vecteur : entrée arbitraire/oversize dans paramètre path → bruit d'audit/log, erreurs métier évitables, surface DoS applicative accrue. Impact limité mais réel sur robustesse sécurité. MINEUR src/modules/anchor/controllers/signer.controller.ts

Recommandations

  • [S-01] Forcer un mode fail-closed strict sur signer : si resolveSignerAddress() ne résout rien, lever une erreur bloquante (pas de fallback permissif).
  • [S-02] Valider anchor.finalityDepth au bootstrap (entier, >=1, borne max raisonnable) et échouer au démarrage si invalide.
  • [S-03] Ajouter validation forte sur :address (regex Ethereum/EIP-55 + longueur stricte) via pipe dédié.
  • [Defense-in-depth] Ajouter @UseGuards explicite côté contrôleur + mécanisme rôle OR dédié pour supprimer la dépendance implicite aux guards globaux.
  • [Points forts] Conserver les acquis : verrou pessimiste, anti-spoofing revokedBy, audit d'échecs, transitions terminales REVOKED.