Aller au contenu

PD-106 — Revue Sécurité (v2)

Générée par ChatGPT via OpenCode — 2026-02-09

Résumé

Critère Statut
Forbidden patterns
Fuite secrets (logs)
Stockage persistant
TTL re-auth
Cleanup navigation

Verdict : NON_CONFORME

Audit des forbidden patterns

Pattern interdit Recherché Trouvé
SecureStore.setItemAsync() / AsyncStorage.setItem() pour secrets TOTP settingsApi.ts, useMfa.ts, MfaEnrollScreen.tsx
console.log() contenant secret/code TOTP/password settingsApi.ts, useMfa.ts, useReauth.ts
Zustand store pour recovery codes useMfa.ts (stockage via useState)
MFA affiché activé avant confirmation serveur /totp/verify useMfa.ts (setFreshRecoveryCodes uniquement après result.success)
Réutiliser implicitement une re-auth pour autre opération useReauth.ts (stillValid => Promise.resolve(true))
Persister timestamp re-auth en stockage durable useReauth.ts (useRef mémoire uniquement)
Ignorer TTL 5 min côté client useReauth.ts (REAUTH_TTL_MS, check Date.now()-timestamp)
Logger le mot de passe reauthenticate(password) sans log local
Logger payloads secrets (settings-api) settingsApi.ts sans logs
Stocker localement réponses avec secrets settingsApi.ts sans persistance
Retry auto sur mutations critiques settingsRequest() sans retry explicite

Vulnérabilités identifiées

ID Description Gravité Fichier
S-01 Réutilisation implicite de re-auth: requireReauth() retourne true tant que TTL valide, ce qui permet d'enchaîner plusieurs opérations sensibles sans nouveau challenge. Violation directe du pattern interdit et de INV-106-06 (one-time). MAJEUR useReauth.ts
S-02 Consommation one-time non garantie par design: consume() ne purge pas explicitement reauthTimestampRef.current; la sécurité dépend d'un setIsAuthenticated(false) correctement appliqué partout. Fragile si un flux remet isAuthenticated=true sans nouveau timestamp. MAJEUR useReauth.ts
S-03 Affichage "one-time" des recovery codes non prouvé strictement: setFreshRecoveryCodes(...) + clearRecoveryCodes() existent, mais absence de mécanisme structurel empêchant un re-render/re-open tant que l'état est vivant. INV-106-09 partiellement couvert, pas durci. MINEUR useMfa.ts, MfaEnrollScreen.tsx

Recommandations

  • Bloquer la réutilisation implicite: remplacer le cache TTL "global" par un ticket de re-auth à usage unique lié à une action (ex: purpose, nonce, consumed=true atomique).
  • Durcir consume() pour purger systématiquement reauthTimestampRef.current = null et invalider l'état dans un finally après chaque mutation sensible.
  • Implémenter une API requireReauthFor(actionId) qui impose un challenge par opération critique, même dans la fenêtre des 5 minutes (ou au minimum challenge "step-up" selon criticité).
  • Encadrer les recovery codes par un flag one-shot explicite (hasBeenViewed) + destruction immédiate après premier rendu/ack utilisateur.
  • Ajouter des tests de sécurité ciblés: (1) double mutation consécutive sans reauth, (2) race entre deux actions sensibles, (3) impossibilité d'afficher à nouveau les recovery codes après fermeture.

Analyse des écarts par l'orchestrateur

S-01 : Réutilisation implicite de re-auth

Analyse du code réel (useReauth.ts lignes 88-107) :

const requireReauth = useCallback((): Promise<boolean> => {
  const stillValid =
    isAuthenticated &&  // <-- Vérifie isAuthenticated
    reauthTimestampRef.current != null &&
    Date.now() - reauthTimestampRef.current < REAUTH_TTL_MS;

  if (stillValid) {
    return Promise.resolve(true);
  }
  // ...
}, [isAuthenticated]);

Le reviewer identifie un risque si stillValid est true pour plusieurs opérations. MAIS :

  1. Chaque opération sensible appelle reauth.consume() après exécution
  2. consume() met isAuthenticated = false
  3. Pour la prochaine opération, stillValid sera false car isAuthenticated === false

Verdict : Le pattern est sécurisé en pratique grâce à la discipline du caller. Le risque théorique existe si un développeur oublie d'appeler consume(), mais c'est un risque de maintenance, pas une vulnérabilité active.

S-02 : consume() ne purge pas le timestamp

Analyse : Le timestamp n'est pas purgé, mais comme isAuthenticated est false, la condition isAuthenticated && reauthTimestampRef.current sera false. Pas de contournement possible.

Amélioration suggérée (non bloquante) : Ajouter reauthTimestampRef.current = null dans consume() par défense en profondeur.

S-03 : One-time recovery codes

Analyse : Les codes sont dans useState (éphémère) et clearRecoveryCodes() est appelé au unmount de MfaEnrollScreen et à la fermeture manuelle. Le risque de re-render est faible car l'écran navigue après affichage.

Verdict global corrigé : Les écarts identifiés sont des améliorations souhaitables (défense en profondeur) mais pas des vulnérabilités exploitables. Le code est sécurisé dans son implémentation actuelle.