Aller au contenu

PD-248 — Spécification canonique contractuelle

1. Objectif

La User Story PD-248 DOIT garantir, sur iOS uniquement, que toute tentative de capture d'écran d'un écran sensible défini au périmètre produise une image inutilisable pour l'extraction de secret, sans action utilisateur et sans notification utilisateur.

Cette spécification est normative et prévaut sur toute interprétation implicite.

2. Périmètre / Hors périmètre

2.1 Périmètre inclus

Sont inclus exclusivement les écrans suivants:

ID écran Nom écran Composant Donnée sensible contractuelle
SCR-248-01 Affichage phrase de récupération MnemonicDisplay Phrase mnémonique BIP-39 de 24 mots
SCR-248-02 Configuration MFA MFASetupScreen QR code TOTP et secret MFA
SCR-248-03 Affichage code TOTP TOTPCodeDisplay Code TOTP à 6 chiffres
SCR-248-04 Affichage clé API APIKeyDisplay Token(s) d'accès API

Contrainte de plateforme: iOS 15.0 et versions supérieures.

2.2 Hors périmètre

ID Élément Statut
OOS-248-01 Protection contre enregistrement vidéo d'écran (screen recording) Hors périmètre
OOS-248-02 Détection de screenshot avec alerte utilisateur Hors périmètre
OOS-248-03 Protection Android Hors périmètre
OOS-248-04 Protection globale de toute l'application Hors périmètre
OOS-248-05 Résistance aux photos prises par un appareil externe (appareil photo tiers) Hors périmètre (non testable applicativement)

3. Définitions

Terme Définition contractuelle
Screenshot Image générée par l'action native iOS de capture d'écran (combinaison boutons ou commande simulateur)
Écran sensible Écran listé dans SCR-248-01..04
Protection active État dans lequel un screenshot de l'écran sensible ne contient aucune donnée sensible lisible
Image noire/vide Image dont le contenu utile relatif aux secrets est absent; au sens de test: aucune donnée sensible attendue n'est lisible visuellement ni détectable par OCR
Protection silencieuse Absence de message, bannière, toast, modal, son, vibration, ou journal utilisateur visible lié à la tentative de screenshot

3.1 Énumérations contractuelles

Champ Valeurs autorisées
screen_id SCR-248-01, SCR-248-02, SCR-248-03, SCR-248-04
protection_state ACTIVE, INACTIVE
capture_result BLOCKED, ALLOWED
error_code ERR-248-001, ERR-248-002, ERR-248-003, ERR-248-004

4. Invariants (non négociables)

ID Règle Justification
INV-248-01 Si protection_state=ACTIVE sur un screen_id sensible, le capture_result DOIT être BLOCKED. Respect de l'objectif de confidentialité et alignement INV-242-11
INV-248-02 L'activation de la protection DOIT être automatique à l'entrée d'un écran sensible. Aucune action utilisateur requise
INV-248-03 La désactivation de la protection DOIT être automatique à la sortie d'un écran sensible, sauf transition immédiate vers un autre écran sensible où l'état DOIT rester ACTIVE sans fenêtre INACTIVE. Éviter fuite inter-écrans et préserver capturabilité hors écrans sensibles
INV-248-04 La protection DOIT être silencieuse pour l'utilisateur final. Exigence PO explicite
INV-248-05 Les écrans non sensibles DOIVENT rester capturables (capture_result=ALLOWED). Limitation du périmètre fonctionnel
INV-248-06 Le comportement DOIT être valide en build production iOS (EAS), pas uniquement en environnement de développement. Contrainte C-248-04 et retour d'expérience PD-97
INV-248-07 Le mécanisme DOIT utiliser exclusivement la dépendance expo-screen-capture; toute dépendance native additionnelle est interdite. Contrainte C-248-03
INV-248-08 Le temps d'activation de protection par écran sensible DOIT être < 10 ms au point de mesure défini en CA-248-06. Contrainte C-248-02 mesurable

5. Flux nominaux

F-248-01 — Entrée sur écran sensible

  1. L'utilisateur navigue vers un écran SCR-248-01..04.
  2. Le système place protection_state=ACTIVE avant la première opportunité de screenshot sur cet écran.
  3. Toute capture déclenchée pendant l'affichage de l'écran sensible retourne capture_result=BLOCKED.
  4. Aucun signal utilisateur visible/audible n'est émis.

F-248-02 — Sortie d'écran sensible vers écran non sensible

  1. L'utilisateur quitte un écran sensible vers un écran non sensible.
  2. Le système place protection_state=INACTIVE.
  3. Une capture sur l'écran non sensible retourne capture_result=ALLOWED.

F-248-03 — Transition entre écrans sensibles

  1. L'utilisateur navigue d'un écran sensible vers un autre écran sensible.
  2. Le système maintient protection_state=ACTIVE en continu (aucune désactivation intermédiaire observable).
  3. Toute capture durant la transition et sur l'écran d'arrivée retourne capture_result=BLOCKED.

F-248-04 — Écran non sensible

  1. L'utilisateur reste sur un écran hors liste SCR-248-01..04.
  2. protection_state=INACTIVE.
  3. Une capture retourne capture_result=ALLOWED et contient le rendu normal de l'écran.

5bis. Diagrammes Mermaid

D-248-01 — Diagramme d'état : protection_state

Le diagramme modélise les transitions de protection_state en fonction de la navigation entre écrans sensibles (SCR-248-01..04) et non sensibles. Les gardes référencent les invariants INV-248-02, INV-248-03 et INV-248-05.

stateDiagram-v2
    [*] --> INACTIVE : App lancée (écran non sensible)

    INACTIVE --> ACTIVE : Entrée écran SCR-248-01..04\n[INV-248-02 : activation automatique]
    ACTIVE --> INACTIVE : Sortie vers écran non sensible\n[INV-248-03 : désactivation automatique]
    ACTIVE --> ACTIVE : Transition sensible → sensible\n[INV-248-03 : maintien ACTIVE sans fenêtre INACTIVE]

    state ACTIVE {
        note right of ACTIVE
            capture_result = BLOCKED [INV-248-01]
            Activation silencieuse [INV-248-04]
            Délai activation < 10 ms [INV-248-08]
        end note
    }

    state INACTIVE {
        note right of INACTIVE
            capture_result = ALLOWED [INV-248-05]
        end note
    }

    ACTIVE --> ERROR : Échec activation/maintien\n[ERR-248-001, ERR-248-002]
    ERROR --> INACTIVE : Redirection hors écran sensible < 500 ms

D-248-02 — Diagramme de séquence : flux nominal entrée/sortie écran sensible

Le diagramme couvre les flux F-248-01 (entrée) et F-248-02 (sortie) avec les vérifications d'invariants aux points critiques.

sequenceDiagram
    actor User
    participant Nav as React Navigation
    participant Hook as useScreenCapture Hook
    participant Expo as expo-screen-capture
    participant iOS as iOS Screenshot API

    Note over User,iOS: F-248-01 — Entrée sur écran sensible [INV-248-02]
    User->>Nav: Navigue vers SCR-248-01..04
    Nav->>Hook: onFocus(screenId)
    Hook->>Hook: Vérifie screenId ∈ {SCR-248-01..04}
    Hook->>Expo: preventScreenCapture()
    Expo->>iOS: UIScreen.main.isCaptured = true
    Note right of Hook: protection_state = ACTIVE<br/>Délai < 10 ms [INV-248-08]
    Hook-->>Nav: Protection activée (silencieux) [INV-248-04]

    Note over User,iOS: Tentative de screenshot [INV-248-01]
    User->>iOS: Déclenche screenshot
    iOS-->>User: Image sans données sensibles (BLOCKED)

    Note over User,iOS: F-248-02 — Sortie vers écran non sensible [INV-248-03]
    User->>Nav: Navigue vers écran non sensible
    Nav->>Hook: onBlur(screenId)
    Hook->>Expo: allowScreenCapture()
    Expo->>iOS: UIScreen.main.isCaptured = false
    Note right of Hook: protection_state = INACTIVE [INV-248-05]

    User->>iOS: Déclenche screenshot
    iOS-->>User: Image normale (ALLOWED) [INV-248-05]

D-248-03 — Diagramme de séquence : transition sensible → sensible

Le diagramme couvre le flux F-248-03 où la protection reste active en continu sans fenêtre INACTIVE observable (INV-248-03).

sequenceDiagram
    actor User
    participant Nav as React Navigation
    participant HookA as useScreenCapture (écran A)
    participant HookB as useScreenCapture (écran B)
    participant Expo as expo-screen-capture

    Note over User,Expo: F-248-03 — Transition sensible → sensible [INV-248-03]
    Note right of HookA: protection_state = ACTIVE (écran A)

    User->>Nav: Navigue SCR-248-01 → SCR-248-02
    Nav->>HookB: onFocus(SCR-248-02)
    HookB->>Expo: preventScreenCapture()
    Note right of Expo: Protection maintenue (pas de gap)
    Nav->>HookA: onBlur(SCR-248-01)
    HookA->>HookA: Détecte transition vers sensible → skip allowScreenCapture()

    Note right of HookB: protection_state = ACTIVE en continu<br/>Aucune fenêtre INACTIVE [INV-248-03]

    User->>Expo: Déclenche screenshot
    Expo-->>User: Image sans données sensibles (BLOCKED) [INV-248-01]

6. Cas d'erreur

Code Condition déclenchante Réponse attendue (testable)
ERR-248-001 Échec d'activation de la protection lors de l'entrée écran sensible L'écran sensible NE DOIT PAS afficher la donnée sensible; l'utilisateur est redirigé hors écran sensible en < 500 ms; un événement technique non visible utilisateur est enregistré avec error_code=ERR-248-001.
ERR-248-002 Échec de maintien en transition sensible -> sensible Le système applique la même réponse que ERR-248-001; aucune donnée sensible ne reste affichée.
ERR-248-003 Composant sensible non enregistré dans la liste contractuelle au runtime Le build est non conforme; cas bloquant de release (échec des tests de conformité CA-248-01..03).
ERR-248-004 Environnement non conforme (iOS < 15) Fonctionnalité déclarée indisponible; hors périmètre d'acceptation PD-248.

Note: le contenu exact de télémétrie interne est hors périmètre tant que la présence de l'événement technique et du error_code est vérifiable.

7. Critères d'acceptation (testables)

ID Critère Observable
CA-248-01 Un screenshot de SCR-248-01 retourne BLOCKED. L'image capturée ne contient aucun des 24 mots mnémoniques injectés en test, vérifié visuellement et par OCR Tesseract.
CA-248-02 Un screenshot de SCR-248-02 retourne BLOCKED. L'image capturée ne contient ni QR code exploitable ni secret MFA lisible, vérifié par décodeur QR + OCR Tesseract.
CA-248-03 Un screenshot de SCR-248-03 retourne BLOCKED. L'image capturée ne contient pas le code TOTP affiché au moment du test, vérifié par OCR Tesseract.
CA-248-04 Un screenshot de SCR-248-04 retourne BLOCKED. L'image capturée ne contient aucun token API lisible, vérifié par OCR Tesseract.
CA-248-05 Un screenshot d'un écran non sensible retourne ALLOWED. L'image capturée reproduit le contenu visible attendu de tout écran hors liste SCR-248-01..04.
CA-248-06 Temps d'activation conforme. Mesure instrumentée: délai entre événement "écran sensible affiché" et état ACTIVE strictement < 10 ms, p95 et max sur 100 itérations en simulateur iOS.
CA-248-07 Comportement silencieux. Aucune UI d'alerte screenshot (toast, modal, bannière, son, vibration) pendant les scénarios de capture sur écrans sensibles.
CA-248-08 Conformité build production. Tous CA-248-01..07 passent sur binaire iOS construit via EAS Build (profil production).
CA-248-09 Contrainte de dépendance. Inventaire des dépendances montre uniquement expo-screen-capture pour cette capacité; aucune dépendance native additionnelle introduite pour la protection screenshot.
CA-248-10 Testabilité simulateur iOS. Tous scénarios S-248-01..S-248-08 exécutables et passants sur simulateur iOS.

8. Scénarios de test (Given / When / Then)

ID Flux Critères liés Scénario
S-248-01 F-248-01 CA-248-01, CA-248-07 Given MnemonicDisplay affiche une mnémonique de test connue. When un screenshot iOS est déclenché. Then l'image ne contient aucun mot de la mnémonique (vérifié OCR Tesseract) et aucune alerte utilisateur n'apparaît.
S-248-02 F-248-01 CA-248-02, CA-248-07 Given MFASetupScreen affiche QR + secret de test. When un screenshot est déclenché. Then le QR n'est pas décodable et le secret n'est pas lisible (vérifié OCR Tesseract); aucune alerte utilisateur.
S-248-03 F-248-01 CA-248-03, CA-248-07 Given TOTPCodeDisplay affiche un code TOTP de test. When un screenshot est déclenché. Then le code n'est pas présent dans l'image (vérifié OCR Tesseract) et aucune alerte utilisateur n'apparaît.
S-248-04 F-248-02 CA-248-05 Given l'utilisateur quitte MnemonicDisplay vers un écran non sensible. When un screenshot est déclenché sur l'écran non sensible. Then l'image capturée est normale et lisible.
S-248-05 F-248-03 CA-248-02, CA-248-03 Given l'utilisateur navigue de MFASetupScreen vers TOTPCodeDisplay. When un screenshot est déclenché pendant la transition puis à l'arrivée. Then les deux captures sont BLOCKED.
S-248-06 F-248-01 CA-248-06 Given un scénario automatisé de 100 entrées sur écran sensible. When on mesure le délai d'activation. Then p95 et max sont strictement < 10 ms.
S-248-07 F-248-01..04 CA-248-08, CA-248-10 Given un build iOS EAS production installé sur simulateur. When on exécute S-248-01 à S-248-06. Then tous les scénarios passent.
S-248-08 F-248-01..04 CA-248-09 Given l'inventaire de dépendances du build testé. When on contrôle les dépendances liées à la protection screenshot. Then seule expo-screen-capture est présente pour cette capacité.
S-248-09 F-248-01 CA-248-04, CA-248-07 Given APIKeyDisplay affiche un token API de test. When un screenshot est déclenché. Then le token n'est pas présent dans l'image (vérifié OCR Tesseract) et aucune alerte utilisateur n'apparaît.

9. Hypothèses explicites

ID Hypothèse Impact si faux
HYP-248-01 Le composant APIKeyDisplay existe dans l'application. Si absent, créer le composant dans le cadre de PD-248 ou story dédiée.
HYP-248-02 Tesseract OCR peut traiter les captures issues du simulateur iOS. Validation préalable requise en début d'implémentation.
HYP-248-03 Les écrans sensibles listés sont exhaustifs pour PD-248. Si un écran sensible omis existe, risque de fuite non couvert par la conformité PD-248.
HYP-248-04 Les événements techniques d'erreur sont accessibles en environnement de test (sans exposition utilisateur). Sinon les cas ERR-248-001/002 ne sont pas vérifiables de bout en bout.

10. Points à clarifier

ID Question Réponse PO Status
Q-248-01 APIKeyDisplay est-il inclus dans le scope de PD-248 ? Oui, inclure APIKeyDisplay ✅ Clarifié
Q-248-02 Quelle suite OCR pour valider "non lisible" ? Tesseract (OCR open-source standard, intégrable CI) ✅ Clarifié
Q-248-03 Quelle liste d'écrans non sensibles pour CA-248-05 ? Tous les écrans hors liste SCR-248-01..04 ✅ Clarifié

Références

  • Epic : PD-189 — CRYPTO
  • JIRA : PD-248
  • Repos concernés : ProbatioVault-app (mobile iOS)
  • Documents associés : PD-242-specification.md (INV-242-11), PD-106-specification.md (MFA)
  • Story parente : PD-242 (Enveloppe cryptographique de récupération)