Aller au contenu

PD-180 — Spécification canonique des webhooks sortants d’événements utilisateur (corrigée v3)

1. Objectif

La User Story PD-180 définit un mécanisme contractuel de notification push B2B par webhooks sortants pour les événements du cycle de vie documentaire ProbatioVault, afin de :

  • supprimer la dépendance au polling côté partenaires ;
  • garantir des notifications sécurisées, traçables, multi-tenant et robustes ;
  • fournir des capacités d’exploitation API (CRUD, ping, replay, rotation secret) sans interface UI en v1.

Cette spécification est normative et exhaustive. Toute règle ci-dessous est testable.


2. Périmètre / Hors périmètre

Inclus

  • Webhooks sortants au niveau organisation (tenant B2B).
  • Gestion API des abonnements webhook : création, lecture, mise à jour, suppression, activation/désactivation.
  • Couverture des événements v1 :
  • document.created
  • document.sealed
  • document.anchored
  • proof.generated
  • document.shared
  • document.revoked
  • account.device.revoked
  • Endpoint de test webhook.ping.
  • Replay manuel d’un événement livré avec nouvel event_id.
  • Rotation du secret HMAC avec invalidation immédiate de l’ancien.
  • Signature systématique HMAC-SHA256.
  • Journal append-only des tentatives de livraison, consultable via API.
  • Isolation multi-tenant par organisation.

Exclu

  • Webhooks entrants (inbound).
  • UI d’administration.
  • Filtrage avancé par metadata.
  • IP allowlist administrable côté UI.
  • Visualisation DLQ en UI.
  • Webhooks B2C individuels.
  • Transformation de payload.
  • Événements batch/bulk.

3. Définitions

  • Webhook sortant : requête HTTP émise par ProbatioVault vers une URL partenaire.
  • Tenant / organisation : entité B2B isolée logiquement.
  • Event type : type d’événement métier notifié.
  • Replay : réémission manuelle d’un événement historique.
  • Ping : événement de test non métier (webhook.ping).
  • Tentative de livraison : essai unique d’envoi HTTP.
  • Journal append-only métier : registre métier en insertion seule (aucun UPDATE/DELETE applicatif).
  • Purge technique : suppression d’entrées >30 jours via mécanisme d’infrastructure (cron/TTL), distincte du métier.
  • RLS : Row-Level Security PostgreSQL, isolation par organisation.
  • Anti-rejeu : validation d’une fenêtre temporelle de signature.
  • État terminal : état sans transition sortante autorisée.

4. Invariants (non négociables)

ID Règle Justification
INV-01 Le payload webhook ne contient aucune donnée sensible (contenu en clair, clé, blob chiffré, secret). Zero-knowledge contractuel.
INV-02 Toute requête webhook sortante est signée via X-ProbatioVault-Signature en HMAC-SHA256. Authenticité et intégrité.
INV-03 La signature inclut un timestamp Unix (t) et permet le rejet hors fenêtre anti-rejeu ±5 minutes. Protection contre replay réseau.
INV-04 L’URL cible DOIT utiliser le schéma https. Tout autre schéma (http, ftp, file, data, javascript, etc.) est rejeté à l’enregistrement et à l’envoi. Confidentialité transport et réduction de surface d’attaque.
INV-05 Les réponses HTTP 301/302/307/308 sont traitées en échec ; aucune redirection suivie. Évite redirection abusive/SSRF indirect.
INV-06 L’intention d’envoi est journalisée avant tentative de livraison. Traçabilité complète et auditabilité.
INV-07 Journal append-only métier : aucun UPDATE/DELETE applicatif autorisé. La purge technique par rétention (>30 jours) via cron/TTL infrastructure est autorisée et n’est pas une mutation métier. Périmètre de purge : la purge >30j s’applique aux entrées du journal de tentatives de livraison. Les intentions de livraison (registre d’événements webhook) sont conservées sans limite de durée. Immutabilité métier + conformité rétention.
INV-08 L’isolation tenant est strictement organisationnelle : aucune lecture/modification cross-tenant. Conformité multi-tenant.
INV-09 Le secret HMAC n’est jamais retourné en clair après création/rotation ; seuls les 4 derniers caractères sont affichables. Réduction d’exposition secret.
INV-10 L’organisation est dérivée du JWT (orgId) et jamais d’un paramètre client. Anti-usurpation inter-tenant.
INV-11 Le système webhooks n’altère aucun état probatoire (document/preuve/ancrage/scellement). Séparation stricte notification vs preuve.
INV-12 Un webhook est émis uniquement pour un événement déjà journalisé côté système source. Cohérence causale des notifications.
INV-13 Modèle d’états contractuel : toute transition non listée est interdite explicitement. Évite ambiguïtés de machine à états.
INV-14 Crash pré-commit : rollback complet, aucun artefact persistant ; crash post-commit : rattrapage asynchrone obligatoire. Atomicité DB + asynchrone.
INV-15 Protection SSRF obligatoire à l’enregistrement ET à l’envoi : validation schéma/host, résolution DNS A/AAAA avant envoi, blocage des IP privées (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), loopback (127.0.0.0/8, ::1), link-local (169.254.0.0/16, fe80::/10), metadata cloud (169.254.169.254). Mitigation DNS rebinding : le worker résout le DNS, valide l’IP, puis établit la connexion TCP vers l’IP résolue (IP pinning — pas de seconde résolution DNS par le client HTTP). Si le client HTTP ne supporte pas le pinning IP, effectuer une double résolution avec comparaison. Prévention d’accès réseau interne et rebinding.

5. Contrat fonctionnel et technique

5.1 Modèle de données contractuel (formats et contraintes)

Donnée Format / encodage Taille / longueur Validation Comportement si invalide
webhook_id UUID v4 texte 36 regex UUIDv4 Rejet 400
org_id UUID v4 texte 36 dérivé JWT uniquement Rejet 401/403
event_id UUID v4 texte 36 regex UUIDv4 Rejet 400
event_type Enum string 1..64 [a-z0-9._]+ + enum v1 + webhook.ping Rejet 400
timestamp (payload) RFC3339 UTC ISO-8601 avec ms, suffixe Z 24 date UTC parseable Rejet 400
t (signature) Unix timestamp secondes (décimal) 1..13 chiffres entier valide Rejet partenaire attendu 401
v1 (signature) HMAC-SHA256 hex lowercase 64 ^[a-f0-9]{64}$ Rejet partenaire attendu 401
X-ProbatioVault-Signature t=<unix>,v1=<hex64> <=128 ^t=\d{1,13},v1=[a-f0-9]{64}$ Tentative en échec
target_url URL absolue HTTPS RFC3986 <= 2048 chars schéma https + SSRF checks Rejet 400
data.doc_id UUID v4 texte 36 regex UUIDv4 Rejet 400
data.hash SHA3-256 encodé hex lowercase 64 chars exacts ^[a-f0-9]{64}$ Rejet 400
data.user_id UUID v4 texte 36 regex UUIDv4 Rejet 400
data.device_id UUID v4 texte 36 regex UUIDv4 (variante B uniquement) Rejet 400
data.metadata Objet JSON <= 4096 bytes UTF-8 JSON valide Rejet 400

Schéma JSON whitelist (strict)

Le schéma payload dépend de la catégorie de l'événement. Deux variantes sont définies :

Variante A — Événements documentaires (document.created, document.sealed, document.anchored, proof.generated, document.shared, document.revoked) :

{
  "event_id": "uuid-v4",
  "event_type": "string",
  "timestamp": "rfc3339-utc",
  "data": {
    "doc_id": "uuid-v4",
    "hash": "sha3-256-hex-lowercase-64",
    "user_id": "uuid-v4",
    "metadata": {}
  }
}

Variante B — Événements non-documentaires (account.device.revoked) :

{
  "event_id": "uuid-v4",
  "event_type": "account.device.revoked",
  "timestamp": "rfc3339-utc",
  "data": {
    "device_id": "uuid-v4",
    "user_id": "uuid-v4",
    "metadata": {}
  }
}

Variante C — Ping (webhook.ping) :

{
  "event_id": "uuid-v4",
  "event_type": "webhook.ping",
  "timestamp": "rfc3339-utc",
  "data": {
    "user_id": "uuid-v4",
    "metadata": {}
  }
}

Règle : additionalProperties: false au niveau racine et au niveau data pour chaque variante. Le champ event_type détermine la variante applicable.

5.2 Canonicalisation JSON et signature

  • Canonicalisation obligatoire : sérialisation déterministe via JSON.stringify() ECMAScript standard (ordre des clés d’insertion, sans espaces, sans indentation).
  • Ordre de construction de l’objet à signer :
  • event_id
  • event_type
  • timestamp
  • data avec ordre interne selon variante :
    • Variante A (documentaire) : doc_id, hash, user_id, metadata
    • Variante B (account.device.revoked) : device_id, user_id, metadata
    • Variante C (webhook.ping) : user_id, metadata
  • Message signé :
    <unix_timestamp>.<json_stringify_output>
  • Calcul : v1 = HMAC_SHA256(signing_key, message) avec sortie hex lowercase.
  • Clé de signature : signing_key = SHA-256(secret_brut), soit exactement la valeur secret_sha256 stockée en DB (cf. §5.3).
  • Obligation partenaire : le partenaire reçoit le secret brut S à la création/rotation. Pour vérifier les signatures, il DOIT calculer K = SHA-256(S) une seule fois, puis utiliser K comme clé HMAC : expected = HMAC_SHA256(K, t + "." + body). Cette obligation est documentée dans la réponse API de création/rotation.
  • Construction payload pour signature : l'objet payload DOIT être construit programmatiquement dans l'ordre spécifié ci-dessus immédiatement avant sérialisation. Aucun middleware, ORM ou désérialisation intermédiaire ne doit reconstruire l'objet entre la construction et la sérialisation.

5.3 Secret HMAC (génération, stockage, usage)

  • Secret brut généré par CSPRNG : crypto.randomBytes(32) minimum (32 bytes, peut être supérieur).
  • Secret brut affiché une seule fois à la création/rotation (jamais relu ensuite). Si le partenaire ne capture pas le secret brut, le seul recours est de déclencher une nouvelle rotation. Aucun mécanisme de récupération n’est prévu (by design — réduction de surface d’exposition).
  • En DB : stockage du hash SHA-256 (secret_sha256, hex lowercase 64).
  • Comparaison de secret : uniquement via hash.
  • Clé de signature opérationnelle : signing_key = secret_sha256 (le hash SHA-256 stocké en DB est utilisé directement comme clé HMAC, sans dérivation supplémentaire). Le partenaire calcule K = SHA-256(secret_brut_reçu) et utilise K pour vérifier HMAC-SHA256(K, message).
  • Obligation d’exécution : le worker relit secret_sha256 en DB à chaque tentative d’envoi ; aucun cache longue durée du secret/hash en mémoire worker n’est autorisé.

5.4 Politique de succès/échec HTTP et retry

  • Succès livraison : tout code HTTP 2xx => état DELIVERED.
  • Timeout HTTP de livraison : 5 secondes. Au-delà, la tentative est classée en échec timeout.
  • Échec livraison : tout code non 2xx (incluant 1xx, 3xx, 4xx, 5xx), timeout (>5s), erreur réseau, erreur TLS, erreur DNS, blocage SSRF => tentative échouée.
  • Les réponses partenaire 401 (ex. validation signature/timestamp côté partenaire) sont traitées comme échec retriable.
  • Politique de retry (10 tentatives max, séquence contractuelle) :
    1min, 5min, 15min, 1h, 1h, 1h, 1h, 1h, 1h, 1h
  • Au-delà de la 10e tentative échouée : état terminal FAILED.

5.5 Flux nominaux

Flux A — Création d’un webhook

  1. Client authentifié organisation envoie target_url + event_type[].
  2. Système vérifie quota organisationnel (max 5 webhooks).
  3. Système valide URL (https obligatoire + SSRF checks), liste d’événements. Unicité non imposée : deux webhooks avec la même target_url mais des event_types différents sont autorisés.
  4. Système génère secret CSPRNG (>=32 bytes), stocke secret_sha256. La réponse de création retourne le secret brut en clair une seule et unique fois (avec instruction partenaire : « calculer K = SHA-256(S) pour vérification HMAC »). Les réponses API ultérieures (GET, LIST) n’exposent que le masque (4 derniers caractères).
  5. Webhook devient éligible à livraison selon état (ACTIVE/INACTIVE).

Flux B — Mise à jour webhook (URL, événements, état)

Attributs modifiables : target_url, event_types[], état (transitions autorisées selon §5.7). Attributs immuables : webhook_id, org_id, secret_sha256, created_at.

  1. Client authentifié met à jour un ou plusieurs attributs modifiables.
  2. Revalidation complète (https, SSRF, enum événements, contraintes format).
  3. Changement d’état appliqué immédiatement.
  4. Les livraisons futures respectent l’état courant.

Flux C — Suppression webhook

  1. Client authentifié supprime un webhook de son organisation.
  2. État devient DELETED.
  3. DELETED est terminal : aucune transition sortante autorisée.

Flux D — Émission événement métier

  1. Événement source est déjà journalisé.
  2. Système crée l’intention de livraison (append-only métier).
  3. Livraison asynchrone vers chaque webhook ACTIVE abonné.
  4. Avant chaque envoi : relecture secret_sha256 en DB + résolution DNS A/AAAA + contrôles SSRF.
  5. Tentative journalisée avec statut, durée, code HTTP/résultat, numéro de tentative.
  6. Code 2xx => DELIVERED.
  7. Sinon => retry selon séquence ; au max => FAILED.

Flux E — Ping

  1. Client déclenche test sur webhook.
  2. Système émet webhook.ping avec même structure contractuelle.
  3. Signature, timeout (5s), retry, SSRF checks et journalisation identiques au flux métier.
  4. Le ping est émis indépendamment des event_types souscrits par le webhook — tout webhook peut être pingué.
  5. Le ping génère une intention de livraison soumise au rate limit CA-13 (comptabilisée comme 1 intention).

Flux F — Replay

Critères d’éligibilité au replay : un événement est éligible au replay si et seulement si : - (1) il appartient à l’organisation du demandeur (dérivée du JWT) ; - (2) son état de livraison est DELIVERED ou FAILED ; - (3) il date de moins de 30 jours (les événements purgés retournent 404). Les événements en état PENDING, IN_PROGRESS ou RETRY_SCHEDULED ne sont pas replayables (retour 409 Conflict).

  1. Client demande replay d’un événement historique éligible.
  2. Système vérifie les critères d’éligibilité ci-dessus.
  3. Système crée une nouvelle notification avec :
  4. event_id nouveau,
  5. timestamp nouveau,
  6. lien original_event_id conservé côté journal interne/API de traçabilité.
  7. L'événement replayed est émis vers tous les webhooks ACTIVE abonnés à l'event_type au moment du replay (pas au moment de l'événement original).
  8. Traitement identique (signature, livraison, retry, journalisation).
  9. Le replay génère des intentions de livraison soumises au rate limit CA-13.

Flux G — Rotation du secret

  1. Client déclenche rotation.
  2. Nouveau secret actif immédiatement.
  3. Ancien secret invalide immédiatement.
  4. Les événements déjà en file non envoyés sont signés avec le nouveau secret.
  5. Note explicite : chaque tentative relit secret_sha256 en DB ; aucune tentative ne doit utiliser un secret mis en cache avant rotation.

5.6 Atomicité multi-composant (DB + asynchrone)

Scope Synchrone/Async Garantie contractuelle
Écriture événement/intention de livraison Synchrone transactionnelle ACID
Tentatives HTTP webhook Async post-commit Idempotent, retry-safe
Journal de tentatives Async/séquentiel par tentative Append-only métier
Crash pré-commit N/A Rollback complet
Crash post-commit N/A Rattrapage asynchrone obligatoire

5.7 Modèle d’états et transitions

A. État webhook - ACTIVEINACTIVE : AUTORISÉE - ACTIVEDELETED : AUTORISÉE - INACTIVEACTIVE : AUTORISÉE - INACTIVEDELETED : AUTORISÉE - DELETED* : INTERDITE (terminal)

B. État livraison - PENDINGIN_PROGRESS : AUTORISÉE - IN_PROGRESSDELIVERED : AUTORISÉE (si HTTP 2xx) - IN_PROGRESSRETRY_SCHEDULED : AUTORISÉE (si non 2xx/timeout/erreur) - RETRY_SCHEDULEDIN_PROGRESS : AUTORISÉE - RETRY_SCHEDULEDFAILED : AUTORISÉE (tentative 10 échouée) - DELIVERED* : INTERDITE - FAILED* : INTERDITE (replay crée un nouvel événement)

5.8 Diagrammes Mermaid

Diagramme d’états — Webhook (INV-13)

stateDiagram-v2
    [*] --> ACTIVE : Création (Flux A)
    ACTIVE --> INACTIVE : Désactivation (Flux B)
    INACTIVE --> ACTIVE : Réactivation (Flux B)
    ACTIVE --> DELETED : Suppression (Flux C)
    INACTIVE --> DELETED : Suppression (Flux C)
    DELETED --> [*]

    note right of DELETED
        État terminal — aucune transition
        sortante autorisée (INV-13)
    end note

Diagramme d’états — Livraison (INV-06, INV-13, INV-14)

stateDiagram-v2
    [*] --> PENDING : Intention journalisée (INV-06)
    PENDING --> IN_PROGRESS : Worker prend en charge
    IN_PROGRESS --> DELIVERED : HTTP 2xx
    IN_PROGRESS --> RETRY_SCHEDULED : Non-2xx / timeout / erreur
    RETRY_SCHEDULED --> IN_PROGRESS : Retry (séquence §5.4)
    RETRY_SCHEDULED --> FAILED : 10e tentative échouée

    DELIVERED --> [*]
    FAILED --> [*]

    note right of PENDING
        Crash pré-commit → rollback complet (INV-14)
    end note
    note right of FAILED
        État terminal — replay crée un
        nouvel événement (INV-13)
    end note
    note left of DELIVERED
        État terminal (INV-13)
    end note

Diagramme de séquence — Émission événement métier (Flux D)

Ce diagramme couvre le flux principal : journalisation de l’intention (INV-06, INV-12), signature HMAC-SHA256 (INV-02, INV-03), validation SSRF (INV-15), et livraison avec retry.

sequenceDiagram
    participant Source as Module source
    participant DB as PostgreSQL
    participant Worker as Worker async
    participant DNS as DNS Resolver
    participant Target as URL partenaire

    Source->>DB: Journalise événement source (INV-12)
    Source->>DB: INSERT intention livraison (INV-06)<br/>état PENDING — ACID (INV-14)

    Worker->>DB: SELECT intention PENDING → IN_PROGRESS
    Worker->>DB: Relit secret_sha256 (§5.3)
    Worker->>Worker: Construit payload (variante A/B/C)<br/>Canonicalise JSON (§5.2)
    Worker->>Worker: Signe HMAC-SHA256(secret_sha256,<br/>t + "." + payload) (INV-02, INV-03)

    Worker->>DNS: Résolution A/AAAA target_url
    DNS-->>Worker: IP résolue

    alt IP privée / loopback / link-local / metadata (INV-15)
        Worker->>DB: INSERT tentative FAILED (SSRF bloqué)
    else IP autorisée
        Worker->>Target: POST payload + X-ProbatioVault-Signature<br/>(IP pinning — INV-15)
        alt HTTP 2xx
            Target-->>Worker: 2xx
            Worker->>DB: INSERT tentative DELIVERED
        else Non-2xx / timeout / erreur
            Target-->>Worker: erreur
            Worker->>DB: INSERT tentative RETRY_SCHEDULED
            Note over Worker: Retry selon séquence §5.4<br/>(max 10 tentatives → FAILED)
        end
    end

Diagramme de séquence — Rotation du secret (Flux G, INV-09)

sequenceDiagram
    participant Client as Client B2B
    participant API as API ProbatioVault
    participant DB as PostgreSQL
    participant Worker as Worker async

    Client->>API: POST /webhooks/{id}/rotate
    API->>API: Génère nouveau secret CSPRNG ≥32 bytes
    API->>API: Calcule nouveau secret_sha256
    API->>DB: UPDATE secret_sha256 (ancien invalidé immédiatement)
    API-->>Client: 200 + secret brut en clair (unique fois, INV-09)<br/>+ instruction SHA-256(S) pour vérification HMAC

    Note over Worker: Tentatives futures relisent<br/>secret_sha256 en DB à chaque envoi
    Worker->>DB: SELECT secret_sha256 (nouveau)
    Worker->>Worker: Signe avec nouveau secret

6. Cas d’erreur

ID Cas Réponse attendue
ERR-01 target_url non https ou schéma interdit 400
ERR-02 Dépassement quota webhooks/organisation (>5) 409 Conflict
ERR-03 event_type non supporté 400
ERR-04 Accès cross-tenant 403
ERR-05 Le partenaire répond 401 (signature absente/malformée perçue côté partenaire) Tentative en échec + retry
ERR-06 Le partenaire répond 401 (timestamp hors fenêtre anti-rejeu côté partenaire) Tentative en échec + retry
ERR-07 Réponse cible 3xx Tentative en échec, redirection non suivie, retry
ERR-08 Timeout livraison Tentative en échec, retry
ERR-09 Tentatives max atteintes État final FAILED
ERR-10 Rotation secret pendant file en attente Tentatives futures signées avec nouveau secret uniquement
ERR-11 Replay introuvable/non autorisé/non éligible 404 (introuvable/purgé), 403 (autre org), 409 (état non éligible : PENDING/IN_PROGRESS/RETRY_SCHEDULED)
ERR-12 Données source incohérentes pour la construction du payload (ex : doc_id manquant pour un événement documentaire, hash non conforme hex64) L'intention de livraison est créée en état FAILED avec motif PAYLOAD_VALIDATION_ERROR. Aucune tentative HTTP n'est émise. L'erreur est loggée pour investigation (corruption données internes).
ERR-13 SSRF détectée (IP/résolution interdite, DNS rebinding) Blocage envoi, tentative en échec + retry si pertinent

7. Critères d’acceptation (testables)

ID Critère Observable
CA-01 CRUD webhook organisationnel opérationnel API create/list/update/delete dans tenant courant
CA-02 Limite 5 webhooks par organisation appliquée 6e création rejetée en 409
CA-03 Signature HMAC-SHA256 systématique et canonique Header valide + payload signé <t>.<JSON.stringify()>
CA-04 Anti-rejeu ±5 min implémenté Hors fenêtre rejetable côté partenaire
CA-05 Schéma URL strictement https Tout autre schéma rejeté
CA-06 Redirections non suivies 3xx classé échec
CA-07 Retry contractuel exact sur 10 tentatives Séquence 1m,5m,15m,1h,1h,1h,1h,1h,1h,1h
CA-08 Journal append-only métier + rétention technique >30 jours Aucune mutation métier, purge infra conforme
CA-09 Replay crée nouvel event_id et trace original_event_id côté journal Corrélation disponible API/audit
CA-10 Rotation invalide immédiatement l’ancien secret Toute tentative post-rotation utilise nouveau secret
CA-11 Isolation RLS inter-tenant stricte Tests cross-tenant refusés
CA-12 Non-mutation probatoire États probatoires inchangés
CA-13 Rate limit 100 intentions de livraison/min/org en sliding window 60s. Un événement déclenché vers N webhooks abonnés = N intentions = N unités de quota. Excédent mis en attente (file BullMQ), non perdu, traitement dans l'ordre d'arrivée
CA-14 SSRF protection à l’enregistrement + à l’envoi IP/ranges interdits bloqués, DNS rebinding détecté
CA-15 Secret HMAC conforme sécurité CSPRNG >=32 bytes, stockage hash SHA-256, pas de cache worker

8. Scénarios de test (Given / When / Then)

  • GWT-01 (Création valide) Given tenant authentifié avec 0 webhook
    When création webhook HTTPS avec event_type valides
    Then création réussie et secret non exposé en clair.

  • GWT-02 (Quota dépassé) Given tenant avec 5 webhooks
    When création d’un 6e
    Then rejet 409 Conflict.

  • GWT-03 (Schéma URL interdit) Given tenant authentifié
    When soumission http:// ou ftp://
    Then rejet 400.

  • GWT-04 (Livraison signée canonique) Given événement éligible + webhook actif
    When tentative émise
    Then signature calculée sur <t>.<JSON.stringify(payload)> et header valide.

  • GWT-05 (2xx succès / non-2xx échec) Given endpoint partenaire
    When réponse 204 puis 401
    Then 204 => DELIVERED, 401 => échec + retry.

  • GWT-06 (Retry terminal) Given endpoint indisponible
    When 10 tentatives échouent selon séquence contractuelle
    Then état final FAILED et journal complet.

  • GWT-07 (Replay) Given événement historique autorisé
    When replay
    Then nouvel event_id + lien original_event_id en journal interne.

  • GWT-08 (Rotation secret) Given webhook existant
    When rotation
    Then toutes les tentatives suivantes relisent DB et signent avec nouveau secret.

  • GWT-09 (RLS) Given organisations A et B
    When A accède aux ressources de B
    Then refus systématique.

  • GWT-10 (État terminal DELETED) Given webhook DELETED
    When tentative de transition
    Then transition refusée.

  • GWT-11 (SSRF + DNS rebinding) Given URL cible résolue vers IP privée ou rebinding vers IP interdite
    When validation en création ou en envoi
    Then blocage + aucune requête sortante vers cible interdite.


9. Hypothèses explicites

ID Hypothèse Impact si faux
H-01 Les modules producteurs journalisent l’événement source avant émission webhook. Violation INV-12.
H-02 Le JWT fournit un orgId fiable. Rupture INV-10/INV-08.
H-03 La purge technique >30 jours (cron/TTL) est disponible et auditable. Non-conformité INV-07/CA-08.
H-04 Le runtime supporte résolution DNS A/AAAA fiable avant envoi. Risque de non-conformité INV-15.
H-05 Horloge système synchronisée NTP pour anti-rejeu. Faux positifs 401 et retries excessifs.

10. Références

  • Epic : PD-186 — Backend Core
  • JIRA : PD-180
  • Repo : ProbatioVault-backend
  • Dépendances : PD-13, PD-14, PD-15, PD-3, PD-28, PD-19, PD-31, PD-60, PD-55, PD-41