Voici la review technique indépendante de la spécification PD-103 :
PD-103 — Specification Review¶
Axe 1 — Ambiguites¶
Type : Ambiguïté Référence : Spec §5.1 — timestamp_device regex Description : La regex ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})Z$ utilise un groupe non-capturant (?:\.\d{3}) sans quantificateur ?, rendant les millisecondes obligatoires. Or la longueur annoncée est "24..30", ce qui suppose une précision variable (0 à 6 décimales). Le regex n'accepte que les timestamps de 24 caractères exactement (avec 3 décimales). Contradiction longueur/regex. Impact : Rejet de timestamps valides RFC3339 sans millisecondes, ou avec microsecondes (6 chiffres). Impossible de déterminer le contrat réel. Gravité : Majeur
Type : Ambiguïté Référence : Spec §5.1, §5.4, §5.6 (séquence) — K_doc (DEK) Description : Le diagramme de séquence mentionne AES-256-GCM(image_original_bytes, K_doc, nonce) mais K_doc n'est défini nulle part dans la spec. INV-103-09 mentionne "envelope encryption" et "DEK, nonce, fragment, ReKey" mais ne spécifie ni la génération du DEK, ni le mécanisme d'échange de clé (KEK), ni comment le backend accède au DEK pour déchiffrer le fichier. Impact : Impossible pour une équipe tierce d'implémenter le chiffrement/déchiffrement. Le contrat crypto est incomplet. Gravité : Bloquant
Type : Ambiguïté Référence : Spec §5.2, §5.4, §5.5 — Upload multipart Description : §5.2 définit un seuil multipart (10 MB) et une taille de chunk (5-50 MB), mais les flux nominaux §5.⅘.5 ne mentionnent jamais le multipart. Aucun comportement spécifié pour : passage single → multipart, échec d'un chunk individuel, reprise partielle multipart, complétion de session multipart. Impact : Comportement indéterminé pour les images > 10 MB. Impossible de tester le multipart. Gravité : Majeur
Type : Ambiguïté Référence : Spec §3, §5.4, §5.6 — fichier probatoire Description : §3 définit fichier probatoire comme "Fichier référencé par hash et inclus dans la chaîne probatoire". Mais la spécification ne décrit jamais le moment ni le mécanisme par lequel image_submitted_bytes devient image_probatory_bytes. La transition soumis → probatoire est implicite. Impact : Impossible de vérifier contractuellement quand un fichier acquiert le statut probatoire. Gravité : Mineur
Type : Ambiguïté Référence : Spec §5.1 — signature_status Description : Le champ signature_status est listé dans le modèle de données §5.1 avec les valeurs PENDING_SIGNATURE | SIGNED, mais il n'apparaît pas dans le payload POST /documents/capture. Le diagramme de séquence montre qu'il est positionné par le HSM worker. La spec ne clarifie pas où ce champ vit (colonne DB backend ? champ événement ?) ni qui le positionne initialement. Impact : Ambiguïté sur la responsabilité de l'initialisation du champ et sa localisation. Gravité : Mineur
Type : Ambiguïté Référence : Spec §5.7 — UPLOADING -> CANCELLED Description : La table §5.7 liste CANCELLED dans les transitions sortantes de UPLOADING. Le diagramme Mermaid l'étiquette "annulation utilisateur". Mais il n'existe aucun invariant INV-103-XX couvrant cette transition (les invariants vont de 20 à 28 et sautent ce cas). Ni les cas d'erreur (§6) ni les critères d'acceptation (§7) ne mentionnent l'annulation utilisateur depuis UPLOADING. Impact : Transition autorisée dans la machine d'états mais non contractualisée. Préconditions et comportement indéterminés. Gravité : Majeur
Axe 2 — Contradictions¶
Type : Contradiction Référence : Spec §5bis (séquence) vs §5.5-5.6 (flux) — Transition UPLOADED -> PENDING_SEAL Description : Le diagramme de séquence montre que POST /documents/capture retourne 202 Accepted (status=PENDING_SEAL), impliquant un passage direct à PENDING_SEAL. Or le flux §5.5 step 3 impose UPLOADING -> UPLOADED (sur S3 OK + ACK backend), puis §5.6 step 1 impose une transition distincte UPLOADED -> PENDING_SEAL après persistance de l'événement. Le diagramme de séquence saute l'état UPLOADED. Impact : Incohérence sur l'existence effective de l'état UPLOADED comme état observable. Si le backend retourne directement PENDING_SEAL, l'état UPLOADED est transitoire et non observable, invalidant TC-NOM-01 et TC-NOM-07. Gravité : Majeur
Type : Contradiction Référence : Spec §5.7 vs §5bis (diagramme) vs Invariants — UPLOADING -> CANCELLED Description : Trois sources décrivent cette transition différemment : (1) §5.7 la liste comme autorisée sans condition ; (2) le diagramme Mermaid l'étiquette "annulation utilisateur" ; (3) les invariants ne la mentionnent pas du tout. De plus, ER-103-02 (annulation) ne couvre que l'état CAPTURED, pas UPLOADING. Impact : Impossible de déterminer si l'annulation utilisateur est autorisée pendant l'upload. Si oui, les préconditions (purge du ciphertext en cours d'upload ?) ne sont pas spécifiées. Gravité : Majeur
Axe 3 — Regles non testables¶
Type : Non testable Référence : Spec INV-103-09 — Envelope encryption Description : L'invariant exige que "tout artefact crypto temporaire (DEK, nonce, fragment, ReKey) est chiffré au repos" mais ne spécifie pas le mécanisme de chiffrement au repos (KEK, key wrapping, keychain iOS, etc.). Sans mécanisme défini, un auditeur ne peut pas vérifier comment le chiffrement au repos est réalisé, seulement constater l'absence de clair — ce qui est nécessaire mais insuffisant pour valider la conformité crypto. Impact : Invariant vérifiable uniquement par observation négative (absence de clair), pas par validation positive du mécanisme. Gravité : Majeur
Axe 4 — Incoherences Spec ↔ Tests¶
Type : Incohérence Spec↔Tests Référence : Spec §5.7 (UPLOADING -> CANCELLED) vs Tests §2-§7 Description : La machine d'états autorise UPLOADING -> CANCELLED mais aucun scénario de test ne couvre cette transition. TC-ERR-02 teste uniquement CAPTURED -> CANCELLED. TC-NOM-12 et TC-INV-10 testent les états terminaux mais pas l'entrée en CANCELLED depuis UPLOADING. Impact : Transition autorisée non couverte par les tests. Gravité : Majeur
Type : Incohérence Spec↔Tests Référence : Tests §5 — TC-INV-01 à TC-INV-10 (format) Description : Les tests d'invariants TC-INV-01 à TC-INV-10 sont définis comme entrées de table (observable + commentaire) sans structure GIVEN/WHEN/THEN, contrairement aux tests nominaux et d'erreur. La matrice de couverture §2 les référence comme tests à part entière. TC-INV-02 par exemple ("Refus transition si hash/chiffrement absents") n'a pas de scénario négatif explicite — seul le happy path est testé (TC-NOM-01). Impact : 10 tests d'invariants sans scénario exécutable défini. Ambiguïté sur leur statut : observables intégrés dans d'autres tests, ou tests autonomes non rédigés ? Gravité : Majeur
Type : Incohérence Spec↔Tests Référence : Spec §5.2 (multipart) vs Tests §2-§7 Description : §5.2 contractualise un seuil multipart (10 MB), une taille de chunk (5-50 MB) et des retries (0-5). Aucun test ne couvre le comportement multipart (seuil de basculement, échec d'un chunk, reprise partielle). Impact : Comportement multipart non vérifié. Risque de régression silencieuse sur images volumineuses. Gravité : Mineur (conditionné à la clarification de l'ambiguïté multipart en Axe 1)
Axe 5 — Hypotheses dangereuses¶
Type : Hypothèse dangereuse Référence : Spec §5.5 step 6, INV-103-24 — Session upload différé Description : INV-103-24 conditionne la reprise UPLOAD_DEFERRED -> UPLOADING à "session valide", mais "session" n'est pas défini. S'agit-il de la session d'authentification utilisateur ? De la session de l'URL pré-signée S3 ? Les deux ont des TTL différents. En mode différé (TTL 24h), l'URL pré-signée aura très probablement expiré (TTL pré-signée S3 typiquement 1-15 min). Le flux de reprise doit-il redemander une nouvelle URL pré-signée ? Impact : Reprise différée potentiellement impossible si l'URL pré-signée a expiré, sans mécanisme de renouvellement spécifié. Gravité : Bloquant
Type : Hypothèse dangereuse Référence : Spec §5.1 — timestamp_device, §9 H-103-02 Description : Le timestamp_device est accepté tel quel sans validation de dérive horloge côté serveur. Un device avec une horloge décalée (volontairement ou non) produirait un timestamp incorrect intégré dans la chaîne probatoire. La spec ne mentionne aucune tolérance de skew ni rejet serveur en cas de dérive excessive. Impact : Risque d'invalidation de la preuve probatoire si le timestamp device est significativement décalé par rapport au temps réel. Gravité : Majeur
Axe 5bis — Coherence des diagrammes¶
Type : Incohérence diagramme↔invariants Référence : §5bis (stateDiagram) — transition UPLOADING -> CANCELLED Description : Le diagramme d'état montre UPLOADING --> CANCELLED : annulation utilisateur mais aucun invariant INV-103-XX ne couvre cette transition. Les invariants 20-28 documentent toutes les autres transitions. Transition présente dans le diagramme et la table §5.7, absente des invariants. Impact : Désynchronisation diagramme/invariants. Un auditeur ne peut pas valider cette transition par référence contractuelle. Gravité : Majeur
Type : Incohérence diagramme↔flux Référence : §5bis (sequenceDiagram) — réponse POST /documents/capture Description : Le diagramme de séquence montre B-->>A: 202 Accepted (status=PENDING_SEAL), sautant l'état UPLOADED. Le flux textuel §5.5-5.6 impose un passage explicite par UPLOADED puis PENDING_SEAL. Le diagramme ne représente pas la transition UPLOADED -> PENDING_SEAL comme un événement distinct. Impact : Le diagramme de séquence ne reflète pas fidèlement la machine d'états. Risque d'implémentation incorrecte si l'équipe se fie au diagramme. Gravité : Majeur
Type : Incohérence diagramme↔spec Référence : §5bis (sequenceDiagram) — K_doc Description : Le diagramme de séquence montre AES-256-GCM(image_original_bytes, K_doc, nonce) introduisant un terme K_doc absent du modèle de données §5.1, des invariants §4 et des flux §5.4-5.6. Le mécanisme de génération et de transmission de cette clé n'est documenté nulle part. Impact : Terme non défini dans le diagramme, absent du contrat. Incohérence documentation. Gravité : Majeur (déjà couvert en Axe 1, confirmé ici par le diagramme)
Axe 6 — Risques securite / conformite¶
Type : Risque sécu/conformité Référence : Spec §5.4-5.6, INV-103-06, INV-103-09 — Transmission DEK au backend Description : La spec impose le chiffrement AES-256-GCM file-level avant upload (INV-103-06) et l'envelope encryption (INV-103-09). Le payload chiffré est uploadé sur S3. Mais le mécanisme par lequel le backend obtient le DEK pour déchiffrer le fichier n'est pas spécifié. Le POST (§5bis séquence) envoie nonce_b64 et tag_b64 mais pas le DEK. Sans key exchange ou key wrapping documenté, le backend ne peut pas déchiffrer le fichier. Impact : Soit le chiffrement client-side est inutile (DEK transmis en clair quelque part, non documenté), soit le backend ne peut pas accéder au contenu (pipeline scellement bloqué). Risque de sécurité par obscurité. Gravité : Bloquant
Type : Risque sécu/conformité Référence : Spec §5.5 — Upload S3 via URL pré-signée Description : L'upload utilise une URL pré-signée S3, mais la spec ne mentionne pas l'usage d'un header x-amz-content-sha256 ou Content-MD5 pour garantir l'intégrité du ciphertext uploadé. Sans vérification d'intégrité côté S3, un MITM ou une corruption réseau pourrait remplacer le ciphertext silencieusement. Impact : Le hash SHA3-256 calculé localement porte sur l'image originale en clair, pas sur le ciphertext. Aucun contrôle d'intégrité du ciphertext entre le client et S3. Gravité : Majeur
Type : Risque sécu/conformité Référence : Spec §5.9 — Rate-limit user_id + IP Description : La granularité de rate-limiting est user_id + IP. Un utilisateur mobile légitime changeant de réseau (cellulaire → Wi-Fi → autre cellulaire) aurait un quota distinct par couple (user, IP). Cela peut à la fois : (1) permettre de contourner le rate-limit en changeant d'IP, et (2) bloquer un utilisateur légitime si un NAT partage la même IP avec un autre utilisateur ProbatioVault. Impact : Double risque : contournement par rotation IP et faux positifs en NAT partagé. Gravité : Mineur
Synthese¶
| Gravité | Nombre |
|---|---|
| Bloquant | 3 |
| Majeur | 11 |
| Mineur | 4 |
| Total | 18 |
Points bloquants : 1. Mécanisme d'échange de clé DEK (K_doc) non spécifié — chiffrement/déchiffrement impossible à implémenter. 2. Transmission DEK au backend non documentée — pipeline scellement potentiellement bloqué. 3. Session upload différé non définie — reprise différée potentiellement impossible (URL pré-signée expirée).