Voici mon analyse de la spécification et des tests PD-180.
PD-180 — Specification Review (Gate 3)¶
Points identifiés¶
1.¶
Type : Contradiction
Référence : Spec §5.3 (Secret HMAC) vs §5.2 (Signature)
Description : La spec stipule que le secret brut est affiché une seule fois au partenaire (§5.3 al. 2), que seul secret_sha256 = SHA-256(secret) est stocké en DB (§5.3 al. 4), et que la « clé de signature opérationnelle est dérivée du hash stocké » (§5.3 al. 5). Or le partenaire reçoit le secret brut S et vérifiera HMAC-SHA256(S, message). Si le système signe avec une clé dérivée de SHA-256(S), les signatures ne correspondent pas côté partenaire. Deux lectures contradictoires :
- (a) la clé HMAC EST le secret brut → contredit « aucun secret en clair persistant » et « dérivée du hash »
- (b) la clé HMAC est SHA-256(S) → le partenaire doit aussi hasher son secret avant vérification, mais cette obligation n'est documentée nulle part
Impact : Impossibilité d'implémenter une signature vérifiable sans lever l'ambiguïté. Un choix erroné rend toutes les signatures invalides côté partenaire.
Gravité : Bloquant
2.¶
Type : Ambiguïté
Référence : Spec §5.1 (Schéma JSON whitelist) + §2 (Périmètre inclus, event types)
Description : Le schéma payload strict (additionalProperties: false) impose data.doc_id, data.hash, data.user_id, data.metadata pour tous les événements. Or l'événement account.device.revoked n'est pas lié à un document : les champs doc_id et hash n'ont pas de sémantique définie pour ce type. Idem pour webhook.ping. La spec ne précise pas si ces champs sont nullable, omissibles, ou remplis avec des valeurs conventionnelles.
Impact : L'implémentation devra choisir arbitrairement (null ? UUID zéro ? hash zéro ?) sans contrat, ou violer additionalProperties: false en omettant les champs.
Gravité : Bloquant
3.¶
Type : Ambiguïté
Référence : Spec §5.4 (Politique retry), ERR-08, TC-ERR-08
Description : Le timeout HTTP est mentionné comme condition d'échec (§5.4 al. 2, ERR-08) mais aucune durée n'est spécifiée. Le scénario TC-ERR-08 teste un « timeout » sans valeur contractuelle.
Impact : Valeur laissée au choix de l'implémenteur, non testable de manière déterministe.
Gravité : Majeur
4.¶
Type : Incohérence Spec↔Tests
Référence : Tests §3-§6 (références ECT-01, ECT-04, ECT-05, ECT-07, ECT-08, ECT-09, ECT-11)
Description : Les scénarios TC-NOM-09, TC-ERR-05, TC-ERR-12, TC-SEC-01, TC-SEC-02, TC-SEC-03 référencent des identifiants ECT-XX qui n'existent pas dans la spécification. Ces références croisées pointent vers un artefact absent ou une version antérieure non fournie.
Impact : Traçabilité rompue — impossible de vérifier la couverture de ces exigences.
Gravité : Majeur
5.¶
Type : Ambiguïté
Référence : Spec Flux F (Replay), ERR-11
Description : Flux F indique « événement historique autorisé » comme pré-condition au replay. ERR-11 distingue « introuvable » (404) et « non autorisé » (403). Or aucune règle d'autorisation du replay n'est définie : quels événements sont replayables ? Tous ceux de l'organisation ? Uniquement ceux en état DELIVERED ? FAILED ? Depuis quel horizon temporel ?
Impact : Condition non testable — TC-ERR-11 teste le rejet « non autorisé » sans critère d'autorisation défini.
Gravité : Majeur
6.¶
Type : Ambiguïté
Référence : Spec Flux B (Mise à jour webhook)
Description : Flux B mentionne « attributs autorisés » sans les lister explicitement. Les attributs modifiables (target_url ? event_type[] ? état ? nom ?) ne sont pas contractualisés. La distinction entre mise à jour d'attributs et changement d'état (via le même endpoint ou un endpoint dédié) n'est pas précisée.
Impact : L'implémenteur définira arbitrairement le contrat d'update ; un auditeur ne peut pas vérifier la conformité.
Gravité : Majeur
7.¶
Type : Ambiguïté
Référence : Spec §5.3 al. 5 (« dérivée du hash stocké »)
Description : La méthode de dérivation de la clé de signature à partir de secret_sha256 n'est pas spécifiée. Est-ce le hash lui-même utilisé comme clé HMAC ? Une dérivation HKDF ? Autre ? La formulation « dérivée du hash stocké » est vague pour un contrat cryptographique.
Impact : Couplé au point 1, rend le mécanisme de signature non implémentable de manière univoque.
Gravité : Majeur (aggrave le point 1)
8.¶
Type : Ambiguïté
Référence : Spec CA-13 (Rate limit 100 événements/min/org)
Description : Le rate limit de 100 événements/min/org n'apparaît dans aucun invariant et n'est pas détaillé dans le contrat fonctionnel (§5). Précisions manquantes : (a) 100 intentions de livraison ou 100 tentatives HTTP ? Si une org a 5 webhooks abonnés au même event type, un événement génère-t-il 1 ou 5 unités de quota ? (b) Le comportement « mis en attente » n'est pas spécifié : file BullMQ ? Délai de résorption ? Ordre de traitement ?
Impact : Règle testable en apparence mais interprétation ambiguë. TC-NOM-10 teste « 120 événements en 60s » sans clarifier l'unité de comptage.
Gravité : Mineur
9.¶
Type : Hypothèse dangereuse
Référence : Spec §5.2 (Canonicalisation JSON)
Description : La canonicalisation repose sur JSON.stringify() ECMAScript avec « ordre des clés d'insertion ». Cet ordre est garanti par la spécification ES2015+ pour les clés non-entières, mais dépend de l'ordre de construction de l'objet en mémoire. Si un middleware, un ORM, ou une désérialisation intermédiaire reconstruit l'objet dans un ordre différent, la signature change. La spec ne mentionne pas cette fragilité ni ne contractualise une sérialisation alternative (ex : JCS RFC 8785).
Impact : Signatures invalides en cas de refactoring interne sans changement fonctionnel apparent.
Gravité : Mineur
10.¶
Type : Incohérence Spec↔Tests
Référence : Spec INV-07/CA-08 (purge >30 jours) vs TC-NOM-14
Description : TC-NOM-14 vérifie que les entrées >30 jours sont purgées et que les entrées ≤30 jours sont consultables. Or la spec ne définit pas le périmètre exact de la purge : journal de tentatives uniquement ? Intentions de livraison aussi ? Événements ? Le test suppose un scope implicite.
Impact : Le périmètre de purge est testable mais non contractualisé.
Gravité : Mineur
11.¶
Type : Risque sécu/conformité
Référence : Spec §5.3 al. 3 (« Secret brut affiché une seule fois »)
Description : La réponse de création/rotation renvoie le secret brut en clair dans le corps HTTP. Si le client ne le capture pas (erreur réseau partielle, timeout client, crash), le secret est irrécouvrable et le webhook est inutilisable (impossible de vérifier les signatures). La spec ne prévoit aucun mécanisme de récupération autre qu'une nouvelle rotation.
Impact : Risque opérationnel — webhook configuré mais invérifiable, nécessitant une rotation immédiate. Faible impact sécurité, mais impact UX/fiabilité.
Gravité : Mineur
Synthèse¶
| Gravité | Nombre |
|---|---|
| Bloquant | 2 |
| Majeur | 5 |
| Mineur | 4 |
| Total | 11 |
Les deux points bloquants concernent : (1) la contradiction sur le mécanisme de signature HMAC (secret brut vs hash comme clé), et (2) l'incompatibilité du schéma payload strict avec les événements non documentaires. Ces points doivent être résolus avant Gate 5.