Aller au contenu

Voici mon analyse de review indépendante de la spécification PD-180.


PD-180 — Specification Review

Constats identifiés


CONSTAT R-01

Type : Contradiction
Référence : Spec §6 ERR-05/ERR-06 vs Spec §2 Périmètre (webhooks sortants)
Description : ERR-05 et ERR-06 décrivent un comportement côté récepteur (le partenaire retourne 401). Or PD-180 spécifie le système émetteur. La spec ne définit pas comment ProbatioVault interprète un 401 reçu du partenaire : est-ce un échec avec retry ? Un échec terminal ? La même ambiguïté se propage dans TC-ERR-05 et TC-ERR-06 qui testent le comportement du récepteur, pas de l'émetteur.
Impact : L'équipe d'implémentation ne sait pas quel comportement coder côté émetteur face à un 401 du partenaire. Les tests vérifient un composant hors périmètre.
Gravité : Majeur


CONSTAT R-02

Type : Ambiguïté
Référence : Spec §5.5 Flux D, CA-07
Description : La spec ne définit pas quels codes HTTP de réponse du partenaire constituent un succès de livraison. Seuls les cas d'échec sont couverts (3xx, timeout). Un 200 est-il le seul succès ? Un 201, 202, 204 ? Un 4xx autre que 401 ? Un 5xx ?
Impact : Impossible de déterminer contractuellement quand une tentative passe en DELIVERED vs RETRY_SCHEDULED.
Gravité : Bloquant


CONSTAT R-03

Type : Contradiction
Référence : Spec INV-07 ("append-only : aucun UPDATE/DELETE métier autorisé") vs Spec §7 CA-08 ("consultable 30 jours") vs Spec §10.2 Q-12
Description : Le journal est contractuellement append-only (INV-07) mais doit être limité à 30 jours (CA-08). La purge des entrées >30 jours est un DELETE qui viole INV-07. La spec identifie elle-même cette tension (Q-12) mais ne la résout pas, alors qu'elle est présente dans un invariant non négociable.
Impact : INV-07 et CA-08 sont mutuellement contradictoires sans règle de réconciliation.
Gravité : Bloquant


CONSTAT R-04

Type : Incohérence Spec<>Tests
Référence : Tests §2 Matrice (TC-INV-01, TC-INV-06, TC-INV-07, TC-INV-10, TC-INV-12, TC-INV-13) vs Tests §3-§4
Description : Six tests d'invariants (TC-INV-01, TC-INV-06, TC-INV-07, TC-INV-10, TC-INV-12, TC-INV-13) sont référencés dans la matrice de couverture (§2) et listés dans le tableau §5, mais aucun d'entre eux n'a de scénario GIVEN/WHEN/THEN défini dans les sections §3 ou §4. Seuls les TC-NOM-* et TC-ERR-* sont développés.
Impact : Ces 6 tests d'invariants non négociables n'ont pas de spécification de test exploitable par une équipe tierce.
Gravité : Majeur


CONSTAT R-05

Type : Risque sécu/conformité
Référence : Spec INV-04, INV-05, §10
Description : La protection SSRF est incomplète. INV-04 impose HTTPS et INV-05 interdit les redirections, mais aucune règle n'interdit les URLs ciblant des adresses internes (RFC 1918, loopback 127.0.0.0/8, link-local 169.254.0.0/16, IPv6 ::1). Un attaquant pourrait enregistrer https://10.0.0.1/internal-service ou https://127.0.0.1:8443/admin. La protection DNS rebinding est également absente.
Impact : Vecteur SSRF exploitable via webhook enregistré vers un service interne.
Gravité : Bloquant


CONSTAT R-06

Type : Non testable
Référence : Spec §5.1 Modèle de données — Signature <timestamp>.<body_json>, Q-10
Description : La signature HMAC est calculée sur <timestamp>.<body_json> mais aucune règle de canonicalisation JSON n'est définie. Deux sérialisations valides du même objet JSON (ordre des clés, espaces, échappement unicode) produisent des signatures différentes. Sans règle déterministe, la vérification côté partenaire est non reproductible.
Impact : Un partenaire ne peut pas implémenter de façon fiable la vérification de signature.
Gravité : Bloquant


CONSTAT R-07

Type : Ambiguïté
Référence : Spec §6 ERR-02 ("409 ou 422")
Description : Le code HTTP de rejet pour quota dépassé est spécifié comme "409 ou 422". Un contrat doit fixer un seul code. Le test TC-ERR-02 reproduit la même ambiguïté ("Rejet 409 ou 422 (conforme contrat)").
Impact : Un partenaire ne peut pas distinguer programmatiquement un conflit de quota d'une erreur de validation.
Gravité : Mineur


CONSTAT R-08

Type : Ambiguïté
Référence : Spec §5.10 A — État DELETED ("résolution manuelle uniquement")
Description : La spec indique que DELETED est un état terminal sans transition sortante, puis mentionne "résolution manuelle uniquement" entre parenthèses. Ce terme n'est pas défini : s'agit-il d'une opération DBA ? D'une API admin hors périmètre ? Si DELETED est véritablement terminal (INV-13), la mention "résolution manuelle" est une contradiction.
Impact : Ambiguïté sur le caractère réellement terminal de l'état DELETED.
Gravité : Mineur


CONSTAT R-09

Type : Hypothèse dangereuse
Référence : Spec §5.8 Flux G (Rotation secret), INV-14
Description : La rotation du secret invalide l'ancien "immédiatement" (ERR-10). Mais si un worker BullMQ a déjà lu l'ancien secret en mémoire avant la rotation et exécute la livraison après, il signera avec l'ancien secret. La spec ne précise pas si le secret doit être relu depuis la DB à chaque tentative ou s'il peut être mis en cache par le worker.
Impact : Race condition entre rotation et livraison en cours. Le partenaire pourrait recevoir une signature avec un secret déjà invalidé.
Gravité : Majeur


CONSTAT R-10

Type : Non testable
Référence : Spec INV-01
Description : INV-01 interdit "donnée sensible (contenu en clair, clé, blob chiffré)" dans le payload. La spec ne fournit pas de liste exhaustive des champs considérés comme sensibles ni de schéma JSON contractuel du payload. TC-INV-01 mentionne "contrôle de schéma + inspection champs" mais aucun schéma de référence n'est fourni. La vérification est subjective sans whitelist de champs autorisés.
Impact : L'invariant le plus critique (zero-knowledge) est non vérifiable sans schéma contractuel du payload.
Gravité : Majeur


CONSTAT R-11

Type : Incohérence Spec<>Tests
Référence : Spec §7 CA-13, Tests TC-NOM-10
Description : CA-13 spécifie "100 événements/minute/organisation" mais ne définit pas si la fenêtre est glissante (sliding window) ou fixe (fixed window). TC-NOM-10 teste ">100 événements en 1 minute" sans préciser le mode de calcul. Deux implémentations conformes au test pourraient avoir des comportements radicalement différents en edge case (burst en fin de fenêtre fixe).
Impact : Le test ne discrimine pas entre deux implémentations incompatibles.
Gravité : Mineur


CONSTAT R-12

Type : Hypothèse dangereuse
Référence : Spec §5.1 — data.hash ("format d'encodage non spécifié")
Description : Le champ data.hash est décrit comme "SHA3-256 document" mais sans encodage contractuel (hex, base64, base64url). La spec signale elle-même cette lacune mais la classe en "point à clarifier" plutôt qu'en donnée manquante bloquante. Ce champ est inclus dans le payload signé : un encodage différent entre émetteur et vérificateur rompt la signature.
Impact : Rupture de vérification de signature si l'encodage diverge entre versions ou environnements.
Gravité : Majeur


CONSTAT R-13

Type : Ambiguïté
Référence : Spec §5.5 Flux D point 6, §10.2 Q-03
Description : La politique de retry indique "séquence 1m, 5m, 15m, 1h" pour les 4 premières tentatives, puis "max 10" sans détailler les intervalles des tentatives 5 à 10. Six tentatives n'ont pas de délai défini.
Impact : 60% de la politique de retry n'est pas spécifiée.
Gravité : Majeur


CONSTAT R-14

Type : Non testable
Référence : Spec INV-09, §9 H-06
Description : INV-09 impose que le secret HMAC ne soit "jamais retourné en clair" mais H-06 admet que "son format exact n'est pas imposé". Sans longueur minimale ni exigence de génération (CSPRNG), le secret pourrait être trivialement devinable (ex: 4 caractères). L'invariant de sécurité est satisfait formellement (masqué en API) mais contournable en pratique (brute-force).
Impact : La sécurité de la signature repose sur un secret dont la robustesse n'est pas contractualisée.
Gravité : Majeur


CONSTAT R-15

Type : Incohérence Spec<>Tests
Référence : Tests §7 TC-NEG-01 vs Spec INV-04
Description : TC-NEG-01 teste les schémas ftp:// et javascript: mais INV-04 ne mentionne que le rejet de http://. Les schémas exotiques (ftp://, file://, data:, javascript:) ne sont pas explicitement couverts par l'invariant. Le test suppose un comportement non spécifié (rejet de tout schéma non-HTTPS).
Impact : Écart entre ce que l'invariant garantit et ce que le test vérifie.
Gravité : Mineur


Synthèse

Gravité Nombre
Bloquant 4 (R-02, R-03, R-05, R-06)
Majeur 7 (R-01, R-04, R-09, R-10, R-12, R-13, R-14)
Mineur 4 (R-07, R-08, R-11, R-15)

15 constats identifiés dont 4 bloquants portant sur : l'absence de définition des codes de succès HTTP (R-02), la contradiction append-only vs rétention (R-03), le risque SSRF par IP interne (R-05), et l'absence de canonicalisation JSON pour la signature (R-06).