PD-180 — Specification Review¶
Résultat de l'audit¶
11 écarts identifiés : 1 bloquant, 5 majeurs, 5 mineurs.
E-01 — Contradiction : retour du secret brut à la création¶
Type : Contradiction Référence : §5.3 vs §5.5 Flux A étape 4 Description : §5.3 stipule « Secret brut affiché une seule fois à la création/rotation (jamais relu ensuite) ». Or §5.5 Flux A étape 4 stipule « retourne uniquement masque secret ». Ces deux énoncés sont mutuellement exclusifs. Le schéma de vérification de signature (§5.2) exige que le partenaire reçoive le secret brut S pour calculer K = SHA-256(S). Si seul le masque (4 derniers chars) est retourné, le partenaire ne peut jamais vérifier les signatures. Impact : Le mécanisme de signature est inutilisable. Toute la chaîne de vérification partenaire est rompue. Gravité : Bloquant
E-02 — Ambiguïté : unicité webhook à la création¶
Type : Ambiguïté Référence : §5.5 Flux A étape 3 (« unicité si applicable ») Description : La condition « si applicable » n'est pas définie. Aucune règle ne spécifie ce qui rend deux webhooks duplicats (même URL ? même URL + mêmes event_types ? même URL + même org ?). Le terme « si applicable » est non contractuel. Impact : Comportement indéterminé à l'implémentation — risque de doublons ou de rejets non justifiés. Gravité : Majeur
E-03 — Ambiguïté : périmètre du rate limit pour ping et replay¶
Type : Ambiguïté Référence : CA-13, Flux E, Flux F Description : CA-13 définit un rate limit de 100 intentions/min/org. La spec ne précise pas si les événements webhook.ping et replay comptent comme des intentions de livraison soumises au rate limit. Les Flux E et F décrivent un traitement « identique au flux métier » pour signature/retry/SSRF mais ne mentionnent pas le rate limit. Impact : Un attaquant avec JWT valide pourrait spammer le ping ou le replay sans limite si non comptabilisés. Gravité : Majeur
E-04 — Ambiguïté : cible du replay¶
Type : Ambiguïté Référence : §5.5 Flux F Description : Le replay crée un nouvel événement avec nouvel event_id. La spec ne précise pas vers quel(s) webhook(s) l'événement est réémis : (a) tous les webhooks ACTIVE abonnés au moment du replay, (b) uniquement le(s) webhook(s) qui avaient reçu l'événement original, © un webhook spécifique passé en paramètre. Si l'abonnement a changé entre l'événement original et le replay, le comportement est indéterminé. Impact : Comportement non contractualisable pour une équipe tierce. Gravité : Majeur
E-05 — Incohérence Spec↔Tests : ERR-12 responsabilité du 400¶
Type : Incohérence Spec↔Tests Référence : ERR-12, TC-ERR-12 Description : ERR-12 spécifie « Payload non conforme whitelist/format → 400 avant émission ». Or le payload webhook est construit par le système à partir de données internes (doc_id, hash, user_id). Ce n'est pas un endpoint API où un client soumet un payload. Un code 400 n'a de sens que pour une requête client. TC-ERR-12 teste « data.hash non hex lowercase 64 » — mais cette situation implique une corruption de données internes, pas une requête client invalide. La spec ne précise pas qui reçoit le 400 ni quel appel API est concerné. Impact : Le test est non implémentable sans clarification de l'API/flux concerné. Gravité : Majeur
E-06 — Hypothèse dangereuse : TOCTOU DNS resolution¶
Type : Hypothèse dangereuse Référence : INV-15, §5.5 Flux D étape 4 Description : INV-15 exige une résolution DNS A/AAAA avant envoi avec blocage en cas de DNS rebinding. La spec ne spécifie pas le mécanisme de mitigation du TOCTOU entre la résolution DNS et la connexion TCP. Si l'IP est validée puis la connexion utilise une nouvelle résolution (comportement par défaut des clients HTTP), un attaquant peut répondre avec une IP publique au premier appel DNS et une IP privée au second (classic rebinding). La spec dit « DNS rebinding détecté, envoi bloqué » sans définir l'observable de détection (double résolution ? pinning IP ?). Impact : Contournement possible de la protection SSRF par rebinding. Gravité : Majeur
E-07 — Ambiguïté : contenu de data.metadata¶
Type : Ambiguïté Référence : §5.1 (modèle de données, schéma whitelist) Description : data.metadata est défini comme « Objet JSON <= 4096 bytes UTF-8 ». Le schéma whitelist impose additionalProperties: false au niveau data, mais ne spécifie pas la politique pour les propriétés internes à metadata. Si metadata accepte des propriétés arbitraires, la garantie INV-01 (aucune donnée sensible) repose uniquement sur le bon vouloir du code producteur — pas sur une contrainte de schéma. Impact : Risque de fuite de données sensibles dans metadata sans détection schématique. Gravité : Mineur
E-08 — Ambiguïté : souscription webhook.ping¶
Type : Ambiguïté Référence : §2 (périmètre), §5.5 Flux E, §5.1 (event_type enum) Description : webhook.ping est listé dans les event_types v1 et le modèle de données inclut webhook.ping dans la validation enum. La spec ne précise pas si un webhook doit être explicitement abonné à webhook.ping pour recevoir les pings, ou si tout webhook peut être ciblé par un ping indépendamment de ses event_types. TC-NOM-06 teste le ping sans vérifier la souscription. Impact : Comportement ambigu à l'implémentation. Gravité : Mineur
E-09 — Incohérence Spec↔Tests : TC-NOM-10 ne teste pas N webhooks × 1 événement¶
Type : Incohérence Spec↔Tests Référence : CA-13, TC-NOM-10 Description : CA-13 spécifie « Un événement déclenché vers N webhooks abonnés = N intentions = N unités de quota ». TC-NOM-10 teste avec 1 seul webhook (120 événements × 1 webhook = 120 intentions). Aucun test ne couvre le cas N>1 (ex : 1 événement × 5 webhooks = 5 intentions), qui est la partie distinctive de la règle de comptage. Impact : La règle de multiplication N×intentions n'est pas vérifiée par les tests. Gravité : Mineur
E-10 — Hypothèse dangereuse : TOCTOU secret rotation¶
Type : Hypothèse dangereuse Référence : §5.5 Flux G, §5.3 (obligation d'exécution) Description : La spec exige que chaque tentative relise secret_sha256 en DB. Cependant, entre la lecture DB et l'envoi HTTP effectif, une rotation peut intervenir. Le worker signerait alors avec l'ancien hash lu mais pas encore envoyé. La spec ne spécifie pas si cette fenêtre de concurrence est acceptable ou si un mécanisme de verrouillage/vérification est requis. Impact : Fenêtre de concurrence théorique où une signature utilise un secret invalide post-rotation. Impact limité (retry corrigera avec le nouveau secret). Gravité : Mineur
E-11 — Ambiguïté : event_types vide à la mise à jour¶
Type : Ambiguïté Référence : §5.5 Flux B Description : event_types[] est un attribut modifiable. La spec ne spécifie pas si un tableau vide est autorisé. Un webhook sans souscription à aucun event_type serait ACTIVE mais ne recevrait jamais rien — état fonctionnellement incohérent non traité. Impact : Comportement indéterminé. Gravité : Mineur
Synthèse¶
| Gravité | Nombre | IDs |
|---|---|---|
| Bloquant | 1 | E-01 |
| Majeur | 5 | E-02, E-03, E-04, E-05, E-06 |
| Mineur | 5 | E-07, E-08, E-09, E-10, E-11 |
Verdict : La contradiction bloquante E-01 (retour secret brut vs masque) rend le mécanisme de signature inopérable en l'état. Les 5 écarts majeurs concernent des ambiguïtés contractuelles qui empêchent une implémentation déterministe par une équipe tierce.