PD-103 — Specification Review v2¶
Auditeur : Claude (Gate 3, itération v2) Documents audités : PD-103-specification.md (v2), PD-103-tests.md (v2) Date : 2026-04-02
Écarts identifiés¶
ECT-01 — « Payload canonique identique » non défini¶
Type : Ambiguïté
Référence : §5.5 step 12, §5.12, §6 ER-103-13, §5.9 (idempotence)
Description : Le terme « payload canonique identique » est utilisé comme critère de
distinction entre réponse idempotente (200) et conflit (409), mais n'est jamais défini.
Quels champs constituent la forme canonique ? Comparaison byte-level du body JSON ou
comparaison champ par champ ? Les champs optionnels (OCR) sont-ils inclus ? Le tri des
clés JSON est-il contractuel ?
Impact : Deux implémentations indépendantes (mobile et backend) pourraient diverger sur
la sérialisation canonique, provoquant des 409 sur des replays légitimes ou des 200 sur
des payloads réellement divergents.
Gravité : Bloquant
ECT-02 — Terme « ReKey » indéfini dans INV-103-09¶
Type : Ambiguïté
Référence : INV-103-09 (envelope encryption)
Description : L'invariant liste « DEK, nonce, fragment, ReKey » comme artefacts crypto
temporaires devant être chiffrés au repos. Le terme « ReKey » n'apparaît nulle part
ailleurs dans la spécification ni dans les définitions §3. S'il s'agit d'une clé de
re-chiffrement (proxy re-encryption), son rôle, son cycle de vie et ses protections
doivent être contractualisés. S'il ne s'applique pas à PD-103, il doit être retiré.
Impact : Artefact sécurité non défini = non testable et non auditable.
Gravité : Majeur
ECT-03 — Capture_id UUID case-insensitive vs. comparaison canonique idempotence¶
Type : Contradiction
Référence : §5.1 (capture_id : « insensitive hex »), §5.5 step 12 (payload canonique),
§5.12 (sémantique idempotente)
Description : capture_id est déclaré case-insensitive (accepte a-f et A-F). Or le
mécanisme d'idempotence compare le « payload canonique identique ». Si le mobile envoie
capture_id en minuscules au premier appel puis en majuscules au replay, le backend doit-il
normaliser avant comparaison ? Sans normalisation contractualisée, un replay légitime
pourrait être rejeté en 409.
Impact : Faux positif 409 Conflict sur replay légitime.
Gravité : Majeur
ECT-04 — Tolérance skew timestamp : configurable ou fixe ?¶
Type : Ambiguïté
Référence : §5.2 (default 300s, min 0, max 900), CA-103-17 (« > ±5 min »), ER-103-12
(« > ±5 min »), TC-ERR-12 (« skew > ±300s »)
Description : Le tableau §5.2 déclare un paramètre configurable (min 0, max 900s) avec
défaut 300s, mais les critères d'acceptation et cas d'erreur codifient en dur « ±5 min ».
Si la tolérance est configurable, les tests et CA doivent référencer la valeur configurée,
pas une constante. Si elle est fixe à 300s, les bornes min/max sont trompeuses.
Impact : Tests non représentatifs si tolérance configurée hors défaut ; ou paramètre
configurable mort si tests imposent 300s.
Gravité : Mineur
ECT-05 — Effacement DEK mémoire non déterministe en React Native¶
Type : Hypothèse dangereuse
Référence : §5.4 step 11, §10.1 (stack : React Native + Expo)
Description : La spec exige « efface DEK en clair de la mémoire applicative après succès ».
En JavaScript (React Native), la mémoire est gérée par garbage collector — la mise à null
d'une variable ne garantit pas l'effacement physique. Le DEK peut persister en heap jusqu'au
prochain cycle GC. La spec ne mentionne aucun mécanisme natif (SecureEnclave, mlock, zeroize)
pour garantir l'effacement.
Impact : DEK potentiellement exposé en mémoire mobile post-utilisation ; compromis si dump
mémoire ou jailbreak.
Gravité : Majeur
ECT-06 — Orphelins S3 non couverts par la réconciliation¶
Type : Hypothèse dangereuse
Référence : §5.5 steps 2-4 (PUT S3 puis POST backend), §5.9 (réconciliation)
Description : Le flux nominal uploade le ciphertext sur S3 (step 2-3) puis appelle POST
/documents/capture (step 4). Si l'application crashe entre le PUT S3 réussi et le POST
backend, un ciphertext orphelin existe sur S3 sans enregistrement backend. La réconciliation
§5.9 ne couvre que les « captures non terminales » (backend-side) ; elle ne détecte pas les
objets S3 sans correspondance backend.
Impact : Accumulation de ciphertext orphelins sur S3 ; coût stockage et risque sécurité
(données chiffrées non traçables).
Gravité : Majeur
ECT-07 — Disponibilité SHA3-256 et RSA-OAEP-SHA256 en React Native¶
Type : Hypothèse dangereuse
Référence : §5.4 steps 4, 7-10, §10.1 (React Native + Expo SDK 54)
Description : La spec contractualise SHA3-256 (hash), AES-256-GCM (chiffrement) et
RSA-OAEP-SHA256 (wrapping) côté mobile React Native. L'API Web Crypto standard ne
supporte pas SHA3-256 (uniquement SHA-1/256/384/512). React Native n'expose pas non plus
SHA3 nativement. Expo SDK 54 n'inclut pas de module crypto supportant SHA3. Une dépendance
tierce ou un module natif sera nécessaire mais n'est pas documenté dans les contraintes
techniques §10.1. Idem pour RSA-OAEP-SHA256 dont la disponibilité dépend de la bibliothèque
crypto choisie.
Impact : Blocage d'implémentation si la contrainte crypto n'est pas satisfaisable avec la
stack déclarée.
Gravité : Majeur
ECT-08 — « Cycle conforme » non défini pour clearing conditionnel¶
Type : Non testable
Référence : §5.9 (clearing conditionnel), TC-INV-08
Description : Le clearing conditionnel exige « 3 cycles conformes consécutifs » pour lever
le flag SEAL_DELAYED. Le terme « cycle conforme » n'est pas défini : cycle de quoi ?
Réconciliation ? Scellement ? Monitoring ? Sans définition, TC-INV-08 est non vérifiable
(son THEN référence « nombre contractuel de cycles conformes » — circulaire).
Impact : Mécanisme de clearing non implémentable et non testable.
Gravité : Majeur
ECT-09 — Déclenchement de SEAL_DELAYED non spécifié¶
Type : Non testable
Référence : §5.9 (clearing conditionnel)
Description : La spec définit comment LEVER le flag SEAL_DELAYED (3 cycles conformes) mais
ne définit pas ce qui le DÉCLENCHE. Quand le flag est-il positionné ? Sur SLA scellement
dépassé ? Sur échec HSM ? Sur réconciliation ? Aucune condition d'entrée n'est
contractualisée.
Impact : Flag non activable = clearing conditionnel mort.
Gravité : Majeur
ECT-10 — Scope du lock distribué ambigu¶
Type : Ambiguïté
Référence : §5.9 (lock distribué) : « Scope capture_id (ou batch d'ancrage) »
Description : Le « ou » n'est pas contractualisé. Quand le scope est-il capture_id ?
Quand est-il « batch d'ancrage » ? Le batch est-il un ensemble de capture_id ? Qui décide
du scope ? Le worker ? La configuration ? Sans règle de sélection, deux implémentations
pourraient utiliser des scopes incompatibles.
Impact : Deadlocks ou race conditions si scope mal choisi.
Gravité : Mineur
ECT-11 — Échec unwrap DEK backend non spécifié¶
Type : Risque sécu/conformité
Référence : §5.5 step 5 (unwrap DEK backend), §5.12 (contrat API), §6 (cas d'erreur)
Description : Le backend unwrappe le DEK via sa clé privée. Aucun cas d'erreur ne couvre
l'échec d'unwrapping (dek_wrapped_b64 corrompu, wrappé avec une ancienne KEK après
rotation, ou clé privée indisponible). Le §6 ne liste pas ce scénario. Le §5.12 ne
définit pas de code HTTP pour cette situation.
Impact : Comportement indéterminé sur échec crypto backend ; capture bloquée en état
incohérent sans code d'erreur contractuel.
Gravité : Bloquant
ECT-12 — Transition UPLOAD_DEFERRED → CANCELLED sans invariant dédié¶
Type : Incohérence Spec↔Tests
Référence : Diagramme d'état §5bis (transition annotée « TTL expiré »), §5.7 table
(UPLOAD_DEFERRED → CANCELLED), §5.3 (deferredUploadTtl), TC-ERR-11
Description : La transition UPLOAD_DEFERRED → CANCELLED sur expiration TTL est documentée
dans le diagramme, la table §5.7, le SLA §5.3, et testée par TC-ERR-11, mais n'a pas
d'invariant INV-103-XX dédié. Toutes les autres transitions ont un invariant explicite
(INV-103-20 à INV-103-29). Cette asymétrie fragilise la traçabilité.
Impact : Gap de traçabilité dans la matrice INV → transitions.
Gravité : Mineur
ECT-13 — Effacement DEK mémoire mobile non testé¶
Type : Incohérence Spec↔Tests
Référence : §5.4 step 11 (« efface DEK en clair de la mémoire applicative »), tests §2-5
Description : La spec exige l'effacement du DEK en mémoire mobile après wrapping. Aucun
test (TC-NOM, TC-INV, TC-NEG) ne vérifie cet effacement. TC-INV-03 vérifie que le backend
ne persiste pas le DEK en clair, mais pas que le mobile l'efface de sa mémoire.
Impact : Exigence sécurité non testée ; risque de régression silencieuse.
Gravité : Majeur
ECT-14 — Diagramme de séquence : hash function non spécifié pour TSA et blockchain¶
Type : Ambiguïté (cohérence diagramme)
Référence : §5bis diagramme de séquence, lignes TSA et blockchain
Description : Le diagramme indique « timestamp(hash(merkle_root)) » pour TSA et
« anchor(hash(merkle_root || tsa_token_ref)) » pour blockchain. La fonction de hash
n'est pas spécifiée (SHA-256 ? SHA3-256 ? identique au hash document ?). L'opérateur
« || » (concaténation) n'est pas défini : byte concatenation ? Avec ou sans séparateur ?
Impact : Ambiguïté sur les opérations crypto du pipeline de scellement.
Gravité : Mineur
ECT-15 — Diagramme de séquence : communication statut backend → mobile non spécifiée¶
Type : Ambiguïté (cohérence diagramme)
Référence : §5bis diagramme de séquence, ligne « B-->>A: status=PENDING_SEAL puis
status=SEALED puis status=ANCHOR_CONFIRMED »
Description : Le diagramme montre une flèche retour backend→mobile pour les changements de
statut, mais le mécanisme de communication n'est pas défini. Push notification ? Polling ?
WebSocket ? Server-Sent Events ? La notification push §5.6 step 6 couvre SEALED, mais
PENDING_SEAL et ANCHOR_CONFIRMED ne sont pas contractualisés.
Impact : Implémentation du feedback statut non spécifiée.
Gravité : Mineur
ECT-16 — Diagramme de séquence : flux UPLOAD_DEFERRED absent¶
Type : Ambiguïté (cohérence diagramme)
Référence : §5bis diagramme de séquence, §5.5 flux nominal B
Description : Le diagramme de séquence ne montre que le happy path. Le flux de reprise
différée (UPLOAD_DEFERRED → reprise → UPLOADING → UPLOADED), qui est un flux nominal
documenté en §5.5, n'est pas représenté. Pour une spec avec 8 états et des flux de reprise
contractuels, l'absence du chemin alternatif réduit la valeur du diagramme.
Impact : Diagramme incomplet pour un flux nominal contractuel.
Gravité : Mineur
ECT-17 — Politique de rotation/compromission KEK backend non couverte¶
Type : Risque sécu/conformité
Référence : H-103-09 (distribution KEK), Q-103-08 (rotation KEK)
Description : Q-103-08 identifie la rotation KEK comme point à clarifier. Mais le risque
va au-delà : si la KEK est rotée, les captures en UPLOAD_DEFERRED wrappées avec
l'ancienne KEK deviennent indéchiffrables au backend. Aucun mécanisme de re-wrapping
ou de grace period n'est contractualisé. En cas de compromission KEK, aucune procédure
de révocation n'est définie.
Impact : Perte de captures différées après rotation KEK ; pas de réponse incident en cas
de compromission.
Gravité : Majeur
ECT-18 — URL pré-signée S3 : pas de protection contre interception¶
Type : Risque sécu/conformité
Référence : §5.5 step 1 (URL pré-signée), §5.2 (TTL URL 60-900s)
Description : Les URLs pré-signées S3 sont des bearer tokens : quiconque intercepte l'URL
peut uploader du contenu (dans le TTL). La spec ne mentionne pas de restriction d'IP source,
de signature de requête additionnelle, ou de TLS pinning pour la transmission de l'URL.
Sur un réseau Wi-Fi non sécurisé, l'URL pourrait être interceptée.
Impact : Injection de ciphertext malveillant sur le bucket S3 via URL interceptée.
Gravité : Mineur
ECT-19 — Tests §9 : règle bloquante Q-103-07 non résolue¶
Type : Incohérence Spec↔Tests
Référence : Tests §9 (Q-103-07 classé « Bloquant »), Spec §10.2 (Q-103-07 : « Politique
légale de conservation locale au-delà du TTL »)
Description : Le document de tests classe Q-103-07 comme règle non testable de gravité
« Bloquant ». Or la spec n'a pas résolu ce point (il reste en Q-103-07 §10.2 comme
question ouverte). Un écart bloquant identifié par les tests devrait empêcher la
progression — la spec ne devrait pas passer Gate 3 avec une règle bloquante non résolue.
Impact : Risque conformité juridique ; la conservation locale chiffrée au-delà du TTL
pourrait violer des obligations de suppression.
Gravité : Bloquant
ECT-20 — « Résolution manuelle uniquement » non défini pour états terminaux¶
Type : Ambiguïté
Référence : INV-103-28, §5.7 (CANCELLED et ANCHOR_CONFIRMED)
Description : Les états terminaux portent la mention « résolution manuelle uniquement »
mais aucune procédure, interface ou rôle habilité n'est défini. Qui peut résoudre ?
Via quelle API/console ? Avec quelle traçabilité ? L'absence de contractualisation
rend cette mention inopérante.
Impact : En production, une capture bloquée en CANCELLED par erreur n'a pas de chemin
de récupération auditable.
Gravité : Mineur
ECT-21 — ocr_text : pas de sanitisation contractualisée¶
Type : Risque sécu/conformité
Référence : §5.1 (ocr_text : UTF-8, 0..20000 caractères), §5.4 step 6
Description : ocr_text est extrait localement puis transmis au backend. Aucune règle de
sanitisation n'est contractualisée (échappement HTML, suppression caractères de contrôle,
normalisation Unicode). Si ocr_text est affiché dans une interface web, c'est un vecteur
XSS. Si stocké sans normalisation, des caractères homoglyphes pourraient falsifier
l'indexation.
Impact : XSS potentiel ou corruption d'indexation.
Gravité : Mineur
ECT-22 — Nonce AES-GCM : mécanisme de génération non spécifié¶
Type : Ambiguïté
Référence : §5.4 step 8 (« nonce 96 bits unique pour la capture »)
Description : Le DEK est généré via CSPRNG (step 7) mais le nonce 96 bits est décrit
uniquement comme « unique pour la capture » sans spécifier le mécanisme (CSPRNG ?
compteur ? dérivé ?). Pour AES-GCM, le nonce DOIT être unique par couple (key, nonce).
Comme le DEK est unique par capture, le risque de nonce-reuse est faible, mais la spec
devrait contractualiser CSPRNG pour le nonce comme elle le fait pour le DEK.
Impact : Risque faible (DEK unique par capture) mais asymétrie contractuelle.
Gravité : Mineur
Synthèse¶
| Gravité | Nombre | IDs |
|---|---|---|
| Bloquant | 3 | ECT-01, ECT-11, ECT-19 |
| Majeur | 8 | ECT-02, ECT-03, ECT-05, ECT-06, ECT-07, ECT-08, ECT-09, ECT-13, ECT-17 |
| Mineur | 10 | ECT-04, ECT-10, ECT-12, ECT-14, ECT-15, ECT-16, ECT-18, ECT-20, ECT-21, ECT-22 |
Note : 9 écarts Majeurs (erreur de comptage corrigée : ECT-17 inclus = 9 Majeurs).
Avis auditeur¶
Trois écarts bloquants empêchent l'acceptation en l'état :
- ECT-01 : « payload canonique » est le pivot du contrat d'idempotence (200 vs 409) — sans définition, l'API est non implémentable de manière déterministe.
- ECT-11 : l'échec d'unwrapping DEK backend est un scénario réaliste (rotation KEK, corruption transport) sans code d'erreur ni cas d'erreur.
- ECT-19 : Q-103-07 (conservation locale post-TTL) est classé Bloquant par les tests eux-mêmes ; la spec ne peut progresser avec un point juridique bloquant non résolu.
Les 9 écarts Majeurs concernent principalement la faisabilité crypto sur React Native (ECT-05, ECT-07), des mécanismes non testables (ECT-08, ECT-09), et des gaps de couverture test/sécurité (ECT-13, ECT-17).