Aller au contenu

PD-262 — Rapport de confrontation (Étape 5 — Gate Plan)

Ce rapport est produit par l'orchestrateur Claude avant la gate PMO 5. Il confronte les documents produits pour identifier convergences, divergences et zones d'ombre. Itération v2 — post-corrections E-01 à E-05 Gate 5.

1. Sources confrontées

Document Étape Version
PD-262-specification.md 1 v2 (post FIX E-01..E-11)
PD-262-tests.md 2 v2 (post FIX E-01..E-11)
PD-262-plan.md 4 v2 (post FIX E-01..E-05 Gate 5)
PD-262-code-contracts.yaml 4 v1.0

2. Convergences

CONV-01 — Modèle d'état et transitions : Les quatre documents s'accordent sur la machine à états MONITORED → TAMPERED_SESSION → LOCKED_PERSISTENT avec interdiction de retour. Le plan formalise correctement la distinction reconstruction boot / transition session (FIX E-02 Gate 5), cohérente avec la règle de reconstruction boot de la spec §5.

CONV-02 — Fail-closed universel : INV-262-01 est décliné de manière cohérente dans spec (§4, §6), tests (TC-NOM-04, TC-NOM-09, TC-ERR-02), plan (C1 catch global, C3 handleDetection) et contracts (CC-262-T1, CC-262-T3).

CONV-03 — Native authority : INV-262-02 est respecté partout. Le plan spécifie explicitement que l'écriture Keychain lockout est faite AVANT le resolve() de la Promise JS (C1, FT3). Les contracts l'inscrivent en invariant CC-262-T1.

CONV-04 — Purge : frontière caches / blobs : Les quatre documents convergent sur la distinction caches crypto (purgés) vs blobs persistants chiffrés (conservés, inexploitables). Spec §3.1, tests TC-NOM-12, plan C4, contracts CC-262-T4.

CONV-05 — Gating environnement compile-time : INV-262-07 est uniformément décliné comme build flag compile ANTI_TAMPERING_QA_ENABLED, non mutable runtime. Spec, tests TC-NOM-06/TC-ERR-06, plan C1/C3, contracts CC-262-T1/T9.

CONV-06 — Retry purge borné : Max 3 tentatives, intervalle 1s, abandon avec log. Cohérent entre spec §3.4/ERR-05, tests TC-ERR-05/TC-ERR-11, plan C4, contracts CC-262-T4.

CONV-07 — device_id_pseudo natif : Calcul SHA-256 côté Swift, exposition via bridge TS, utilisation dans audit emitter. Chaîne vide si nil. Cohérent entre les quatre documents (FIX E-01 Gate 5).

CONV-08 — Bootstrapping first_launch_clean : Logique documentée dans plan C1 (FIX E-05 Gate 5) et cohérente avec spec §3.3/§3.6/F6, tests TC-ERR-09, contracts CC-262-T1.

CONV-09 — Audit best effort non bloquant : INV-262-06 décliné de manière identique. Fire-and-forget, pas de retry bloquant UX, lockout indépendant du réseau. Spec, tests TC-NOM-05/TC-ERR-04, plan C5/FT4, contracts CC-262-T5.

CONV-10 — Payload allowlist stricte : Seuls les champs §3.3 autorisés, validation regex avant envoi. Spec, tests TC-NOM-07/TC-NEG-01/TC-NEG-02, plan C5, contracts CC-262-T5.

CONV-11 — Persistance Keychain exclusive : kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly pour lockout_persistent_flag. Spec §3.6, tests TC-NOM-08/TC-NOM-12, plan C1, contracts CC-262-T1.

CONV-12 — Crash-safety mid-transition : ERR-10 couvert uniformément. Écriture Keychain avant resolve, reconstruction boot en LOCKED_PERSISTENT. Spec §3.6/ERR-10, tests TC-ERR-10, plan C1/FT6, contracts CC-262-T1.


3. Divergences

⚠️ Les conflits ne doivent JAMAIS être lissés. Chaque divergence est rendue visible.

DIV-01 — UserDefaults introduit par le plan, interdit par la spec

  • Spec (§3.6) : « Le lockout persistant est stocké exclusivement en Keychain (pas UserDefaults, pas remote config, pas fichier applicatif). »
  • Plan (C1, bootstrapping first_launch_clean) : « le module vérifie la cohérence avec un marqueur UserDefaults volatil de session » pour détecter une réinstallation (le Keychain peut persister après désinstallation sur iOS).
  • Impact : Le plan introduit une dépendance UserDefaults non autorisée par la spec. Même si l'usage est distinct (détection de réinstallation, pas stockage lockout), la spec interdit explicitement UserDefaults pour tout mécanisme anti-tampering. Le code contracts CC-262-T1 interdit « Utiliser UserDefaults ou fichier pour le lockout (Keychain obligatoire) » mais n'adresse pas ce cas de détection de réinstallation.

DIV-02 — Purge de K_master et K_bio : extension non listée dans INV-262-05

  • Spec (INV-262-05) : « purge immédiate des secrets mémoire, tokens session/refresh, caches crypto, temporaires déchiffrés, previews. »
  • Plan (C4) : Ajoute explicitement deleteMasterKey() (PD-98) et deleteBiometricSecret() (PD-107) comme cibles de purge.
  • Spec (§3.1) : Définit « caches crypto » comme « clefs en mémoire, fragments, DEK temporaires, buffers de déchiffrement, previews, fichiers temporaires déchiffrés ». K_master (Keychain) et K_bio (Keychain) ne sont pas des « clefs en mémoire ».
  • Spec (§3.1) : Définit « donnees persistantes chiffrees » comme « conservees lors du lockout ; ils restent inexploitables sans secrets purges » — ce qui implique que des secrets sont purgés pour rendre les blobs inutilisables, mais ne nomme pas lesquels.
  • Impact : L'ajout de K_master et K_bio est cohérent avec l'intention spec (rendre les blobs inexploitables) mais n'est pas couvert par la liste explicite d'INV-262-05. Si cette purge est retirée, les blobs restent exploitables → contradiction avec §3.1. La spec doit être enrichie ou l'invariant mis à jour.

DIV-03 — Distinction TestFlight vs Release vs Production non mécanisée

  • Spec (§3.3) : Enum environment inclut RELEASE, TESTFLIGHT, PRODUCTION comme valeurs distinctes.
  • Plan (C1, §7) : Gating via #if DEBUG, TARGET_OS_SIMULATOR, ANTI_TAMPERING_QA_ENABLED. Aucun mécanisme pour distinguer TestFlight de Release de Production au runtime dans le module natif.
  • Plan (C5, FT4) : Le champ environment est inclus dans le payload audit mais aucune logique de détermination n'est documentée.
  • Impact : Le payload audit pourrait rapporter une valeur environment incorrecte. La détection TestFlight (vérification appStoreReceiptURL contenant « sandboxReceipt ») est un pattern iOS connu mais non documenté dans le plan.

DIV-04 — Tests adversariaux TC-INV-02/TC-NEG-03 : positionnement fichier vs exécution device réel

  • Tests (§5.1) : TC-INV-02 et TC-NEG-03 sont définis comme tests adversariaux avec des GIVEN impliquant une instrumentation JS et une couche native.
  • Plan (§5 mapping) : Note « integration sur device reel uniquement ; en CI unitaire, un test de contrat vérifie que le mock respecte l'interface native mais ne peut pas prouver la résistance au hook JS ».
  • Contracts (CC-262-T10) : Liste TC-INV-02 et TC-NEG-03 dans les invariants du fichier src/__tests__/ et exige « Tests adversariaux TC-INV-02, TC-NEG-03 presents ».
  • Impact : Contradiction entre « présent dans les fichiers de test unitaire CI » (contracts) et « uniquement sur device réel » (plan). Les tests CI ne pourront contenir qu'un stub/placeholder pour ces scénarios, pas une vraie vérification. Le seuil de couverture 80% pourrait être affecté si ces tests sont des no-op en CI.

DIV-05 — identifierForVendor nil : cas non couvert par les tests

  • Spec (H-01) : Hypothèse « device_id_pseudo dérivé de UIDevice.current.identifierForVendor.uuidString (UTF-8) ». Impact si faux : « Incompatibilité backend/audit. »
  • Plan (C1) : « Retourne chaine vide si identifierForVendor indisponible. »
  • Plan (C5, FT4) : Le payload avec device_id_pseudo vide ne passera pas la regex ^[a-f0-9]{64}$ → rejet événement.
  • Tests : Aucun TC-* ne couvre le cas identifierForVendor = nildevice_id_pseudo = "" → rejet payload audit.
  • Impact : Scénario d'erreur silencieux non testé. Lockout conservé (correct) mais absence de visibilité forensic (pas de test confirmant ce comportement).

DIV-06 — Store JS « persisted » vs Keychain source de vérité

  • Contracts (CC-262-T8) : store_extension: tamperingLockout: boolean (persisted).
  • Contracts (CC-262-T8, forbidden) : « Stocker l'état lockout UNIQUEMENT dans le store JS (Keychain natif = source de vérité). »
  • Impact : Le mot « persisted » implique une persistance AsyncStorage du store JS, créant deux sources de vérité (Keychain + AsyncStorage). En cas de désynchronisation (crash entre écriture Keychain et écriture store), le comportement au boot dépend de l'ordre de lecture. Le plan dit « Store tamperingLockout synchronisé avec état natif au boot » mais le mécanisme de synchronisation n'est pas détaillé.

DIV-07 — Numérotation composants plan vs tâches contracts

  • Plan : C9 = Tests, C10 = Configuration Expo.
  • Contracts : T9 = Configuration Expo, T10 = Tests.
  • Impact : Risque de confusion lors de l'implémentation multi-agents. Pas de conflit fonctionnel mais un écart de traçabilité. Les mappings internes des contracts (validation.inv_coverage, validation.ca_coverage) utilisent T1-T10, pas C1-C10, donc la cohérence interne des contracts est préservée.

4. Zones d'ombre

ZO-01 — Politique de retry backend non contractualisée : La spec (Q-03) signale l'absence de politique de retry backend. Le plan choisit fire-and-forget (1 tentative, pas de retry). Ce choix n'est validé par aucun document de la spec. Pas bloquant (sécurité locale indépendante) mais impacts forensic.

ZO-02 — Format des causes secondaires en audit local : La spec (Q-04) et les tests (§9 règle non testable) signalent que le format des causes secondaires n'est pas contractualisé. Le plan (C5) dit « stocke les causes secondaires en log local sans format garanti ». Les contracts (CC-262-T5) exposent secondaryReasons?: TamperingReasonCode[] mais ne spécifient pas le format de stockage local.

ZO-03 — Liste versionnée des cibles de purge : Spec Q-05, tests §9 (règle non testable), plan §9.2 (dette technique). Aucun document ne fournit de liste exhaustive versionnée. Le plan (C4) liste les cibles connues mais reconnaît que la liste devra évoluer avec chaque nouveau module.

ZO-04 — Ports Frida invalides et interval non numérique : Tests TC-NEG-04 (ports invalides ignorés) et TC-NEG-05 (interval non numérique → defaut/clamp). Le plan C1 ne documente pas le traitement des ports invalides dans JailbreakDetector. Le plan C3 ne documente pas le comportement si intervalSec est NaN/undefined. Les contracts ne couvrent pas ces edge cases.

ZO-05 — Atomicité écriture Keychain : Plan HT-07 pose l'hypothèse que l'écriture Keychain est atomique. Si faux, le plan dit « le fail-closed boot reconstruit l'état en LOCKED_PERSISTENT quand même (protection double) ». Aucun test ne vérifie l'écriture Keychain partiellement corrompue (TC-ERR-08 couvre « corrompue » mais pas « partiellement écrite »).

ZO-06 — Expo managed vs bare workflow : Plan §9.1 note que le module natif Swift « peut nécessiter expo prebuild » et que le projet « a déjà un dossier ios/ ». Aucune vérification formelle que le pipeline EAS supporte le changement. Si prebuild est nécessaire et casse le pipeline, c'est bloquant pour l'implémentation.

ZO-07 — Fenêtre de risque mémoire JS : Plan HT-06 et §9.1 reconnaissent que zeroize() JavaScript est best-effort (GC non déterministe). La spec et les tests ne couvrent pas cette limitation. INV-262-05 exige « purge immédiate » mais le plan reconnaît que la purge mémoire JS n'est pas garantie déterministe. Les secrets critiques (K_master, K_bio) sont en Keychain natif, mitigeant le risque, mais les DEK temporaires en mémoire JS restent exposés.

ZO-08 — Signature cryptographique de l'événement audit : Tests §9 (règle non testable) et plan §10 (hors périmètre) convergent pour dire que la signature n'est pas spécifiée. Mais les tests §8 (observabilité) mentionnent « signature NON SPÉCIFIÉE ». Aucune décision formelle de report ou d'exclusion définitive n'est documentée.

ZO-09 — Comportement si identifierForVendor change : La spec H-01 suppose une dérivation « déterministe » mais identifierForVendor change après désinstallation/réinstallation sur iOS. Le plan ne documente pas l'impact sur la corrélation forensic backend (un même device aura des device_id_pseudo différents après réinstallation).


5. Recommandation

  • Procéder — convergence confirmée, aucun conflit bloquant
  • Rework nécessaire — divergences à résoudre avant de continuer
  • Escalade — décision humaine requise sur un point structurant

Divergences à résoudre avant Gate 5 :

Priorité DIV Action requise
BLOQUANT DIV-01 Clarifier dans la spec si UserDefaults est autorisé pour la détection de réinstallation (distinct du stockage lockout), OU proposer un mécanisme alternatif dans le plan (ex: marqueur fichier dans le container app, supprimé à la désinstallation).
MAJEUR DIV-02 Enrichir INV-262-05 pour inclure explicitement K_master et K_bio dans la liste de purge, ou créer un invariant séparé pour la purge des secrets Keychain racine.
MAJEUR DIV-03 Documenter dans le plan le mécanisme de détermination de la valeur environment (DEBUG/SIMULATOR/QA/RELEASE/TESTFLIGHT/PRODUCTION) pour le payload audit.
MAJEUR DIV-04 Décider si TC-INV-02/TC-NEG-03 sont des tests de contrat (mock en CI) ou des tests d'intégration device réel (hors CI). Aligner contracts et plan.
MINEUR DIV-05 Ajouter un TC pour le cas identifierForVendor = nil → payload rejeté → lockout conservé.
MINEUR DIV-06 Clarifier dans le plan ou les contracts que le store JS tamperingLockout est un cache volatile synchronisé au boot depuis le Keychain, pas une source de vérité persistée.
MINEUR DIV-07 Aligner la numérotation C9/C10 du plan avec T9/T10 des contracts, ou documenter le mapping explicite.