Aller au contenu

Construire un workflow (schéma JSON)

Un workflow TechWatchAlert est un graphe d’automatisation : un déclencheur réagit à un événement (nouvelle CVE, fin de vie qui approche, certificat qui expire…), des conditions filtrent, et des actions notifient ou agissent. Ce graphe est décrit par un objet JSON que vous manipulez via l’éditeur visuel, l’assistant Vigie, ou l’import/export de workflow.

Le graphe (workflow.graph) a exactement deux clés :

{
"nodes": [ /* les tuiles */ ],
"edges": [ /* les connexions entre tuiles */ ]
}
{
"id": "c1",
"type": "condition.priorite",
"params": { "critere": "au_dessus_seuil" },
"position": { "x": 320, "y": 140 }
}
Champ Type Description
id string Identifiant unique dans le graphe (libre : trigger, c1, a1…).
type string Type de tuile (voir le catalogue).
params object Paramètres de la tuile (selon son type). {} si aucun.
position object (optionnel) Coordonnées { x, y } dans le canevas. Omettez-le : l’éditeur dispose automatiquement les tuiles.
{ "id": "e1", "source": "trigger", "target": "c1", "sourceHandle": "out" }
Champ Type Description
source string id de la tuile de départ.
target string id de la tuile d’arrivée.
sourceHandle string Sortie utilisée sur la tuile de départ : out (déclencheurs et actions) ou true / false (conditions). Défaut : out.
id string (optionnel) Identifiant de la connexion.

Un graphe est refusé (avec un message explicite) s’il enfreint l’une de ces règles :

  • Un seul déclencheur par workflow, et exactement un (trigger.*).
  • Un déclencheur ne peut pas avoir d’entrée (aucune connexion ne pointe vers lui).
  • Les conditions ont deux sorties : true et false. Le sourceHandle d’une connexion partant d’une condition doit être true ou false.
  • Les déclencheurs et les actions ont une seule sortie : out.
  • Le graphe est acyclique (DAG) — aucune boucle.
  • 30 tuiles maximum (MAX_NODES), profondeur 20 maximum depuis le déclencheur.
  • id de tuile unique ; toute connexion référence des tuiles existantes.
  • Les tuiles non reliées au déclencheur sont ignorées à l’exécution (tolérées à l’enregistrement pour ne pas bloquer un brouillon).
  • 10 workflows maximum par projet.

Le registre complet est aussi servi par l’API interne (GET /projects/{id}/workflows/node-types) pour générer la palette de l’éditeur.

Un déclencheur réagit à un événement. Le contexte (CVE, EOL ou certificat) détermine quelles conditions ont un sens en aval. Aucun n’a de paramètre, sauf ceux de conformité (ci-dessous).

Type Événement Contexte Déclenché quand…
trigger.cve_new_match cve.new_match CVE une nouvelle CVE correspond à votre stack surveillée.
trigger.cve_updated cve.updated CVE une CVE suivie est mise à jour (score, etc.).
trigger.cve_poc_published cve.poc_published CVE un PoC public apparaît pour une CVE suivie.
trigger.cve_kev_added cve.kev_added CVE une CVE suivie entre au catalogue KEV de la CISA.
trigger.cve_exploitee cve.exploitee CVE une CVE suivie passe en exploitation avérée (KEV de la CISA, VulnCheck, CERT-FR, EUVD).
trigger.cve_correctif_disponible cve.correctif_disponible CVE une version corrigée paraît pour une CVE suivie qui n’en avait pas (une fois par CVE).
trigger.cve_rancongiciel cve.rancongiciel CVE une CVE suivie est associée à un rançongiciel par la CISA ou VulnCheck (une fois par CVE).
trigger.eol_approaching eol.approaching EOL la fin de vie d’un produit suivi approche.
trigger.eol_support_ending eol.support_ending EOL la fin de support actif approche.
trigger.eol_new_release eol.new_release EOL une nouvelle version d’un produit suivi sort.
trigger.url_cert_expiring url.cert_expiring Certificat le certificat HTTPS d’une URL surveillée expire bientôt.

Chaque condition a deux sorties (true / false). Une tuile d’une autre famille que le déclencheur (ex. condition CVE après un déclencheur EOL) n’a aucune donnée à lire : elle renvoie systématiquement false. L’éditeur ne les propose plus dans sa palette et signale celles qui le seraient devenues après un changement de déclencheur.

Type Contexte Paramètres
condition.priorite CVE critere ∈ exploitee (CVE exploitée selon la politique : KEV en tête, EPSS, PoC) | kev_en_tete (au catalogue KEV, en tête) | au_dessus_seuil (CVSS au seuil d’alerte) | sous_seuil_exception (sous le seuil, alertée parce qu’exploitée). Lit la politique de priorisation du projet, affinée par le seuil de l’élément de stack qui a fait correspondre la CVE.
condition.has_poc CVE (aucun) — vrai si un PoC public existe.
condition.is_kev CVE (aucun) — vrai si la CVE est au catalogue KEV.
condition.product_name CVE op ∈ contains | starts_with | ends_with | equals ; value texte (≤ 100).
condition.vendor_name CVE idem product_name, sur l’éditeur.
condition.match_type CVE value ∈ vendor | product (comment la CVE a matché votre stack).
condition.case_status CVE op ∈ eq | ne ; value ∈ statut de suivi ou NONE.
condition.has_tag CVE tag_id (UUID d’un tag de l’organisation).
condition.eol_days_remaining EOL op (numérique) ; value jours −3650 → 3650.
condition.eol_is_past EOL (aucun) — vrai si la date de fin de vie est dépassée.
condition.eol_product_name EOL op (texte) ; value texte (≤ 100).
condition.cert_days_remaining Certificat op (numérique) ; value jours −3650 → 3650.
condition.cert_is_expired Certificat (aucun) — vrai si le certificat est expiré.
condition.url_contains Certificat op (texte) ; value texte (≤ 100).

Tuiles de seuil obsolètes. condition.severity (op ∈ gte | eq | in ; value ou values, sévérités CRITICAL, HIGH, MEDIUM, LOW, NONE, UNKNOWN), condition.cvss et condition.epss (op numérique ; value 0 → 10 ou 0 → 1) faisaient doublon avec la politique de priorisation du projet (réglages du projet, onglet Priorisation) : le « à partir de quel score » s’y règle désormais. Elles ne figurent plus dans la palette, mais un graphe qui les contient se valide et s’exécute toujours à l’identique ; l’éditeur les marque « Obsolète » et propose de les remplacer par condition.priorite. Statuts de suivi : PENDING, ANALYZING, FIXING, MITIGATED (atténuée), ACCEPTED, RESOLVED, NOT_APPLICABLE. condition.case_status les accepte tous. action.create_case n’accepte pas MITIGATED, et écarte ACCEPTED à l’exécution (le journal le dit) : ces statuts exigent une mesure, une justification et une échéance, posées par une personne.

Les actions s’exécutent quand le flux les atteint. Elles ont une sortie out et peuvent s’enchaîner.

Type Paramètres Effet
action.notify_inapp (aucun) Notification dans l’application.
action.send_email recipients (optionnel) : restreint aux IDs membres listés ; vide = tous les membres concernés. ai_enrich (booléen, optionnel) et ai_instruction (≤ 500) : cf. ci-dessous. E-mail.
action.fire_webhooks webhook_id (optionnel) : un webhook précis, vide = tous les webhooks actifs ; format ∈ json | slack | discord | teams ; ai_enrich / ai_instruction comme ci-dessus. Webhook sortant signé (voir Signature des webhooks), envoyé après enregistrement, une fois par événement du projet.
action.create_case status ∈ statut de suivi, sauf MITIGATED (requis). Crée une fiche d’alerte (suivi), seulement si le flux atteint la tuile. Nécessaire pour qu’une alerte apparaisse dans « Alertes » et que l’e-mail contienne le bouton « Voir l’alerte ».
action.add_tag tag_id (UUID, requis). Ajoute un tag à la CVE dans le projet (auto-classement).

ai_enrich: true sur une tuile action.send_email ou action.fire_webhooks joint au message une analyse courte : niveau de risque, pourquoi, premières étapes de remédiation. ai_instruction (≤ 500 caractères) permet d’orienter la rédaction — « focus remédiation Debian », « réponds en anglais ».

L’analyse est produite à partir des seules données techniques publiques de l’événement. Ni l’identité des utilisateurs, ni le nom du projet ne font partie de la requête, et toute adresse e-mail est expurgée, y compris depuis votre consigne.

format: "teams" produit une Adaptive Card dans l’enveloppe attachments attendue par un webhook Teams (Power Automate). Depuis le retrait des connecteurs Office 365, la MessageCard historique n’est plus rendue — c’est ce format qu’il faut utiliser.

Chaque appel est signé avec le secret du webhook, montré une seule fois à sa création (et à chaque régénération) :

  • X-TWA-Timestamp : l’heure d’envoi, en secondes Unix ;
  • X-TWA-Signature : sha256= + HMAC-SHA256 hexadécimal de X-TWA-Timestamp + "." + corps brut.

cve.exploitee part une seule fois par CVE et par projet. cvss_score et epss_score valent null quand le score n’est pas publié : ce n’est pas un zéro.

{
"event": "cve.exploitee",
"project_id": "…",
"triggered_at": "2026-10-13T18:00:00+00:00",
"cve_id": "CVE-2026-0001",
"severity": "CRITICAL",
"cvss_score": null,
"epss_score": null,
"has_poc": false,
"is_kev": true,
"rancongiciel": false,
"description": "…",
"matched_type": "product",
"matched_value": "openssl"
}
attendu = "sha256=" + hmac.new(secret.encode(), f"{ts}.".encode() + corps_brut, hashlib.sha256).hexdigest()
assert hmac.compare_digest(attendu, signature) and abs(time.time() - int(ts)) < 300

La comparaison refuse un horodatage écarté de cinq minutes. Une copie renvoyée dans ce délai se vérifie encore : l’envoi ne garde pas les signatures déjà parties.

L’appel part après l’enregistrement de l’événement, une seule fois par projet même si le workflow tourne pour chaque membre ; une erreur passagère (délai, 429, 5xx) est rejouée avec un délai croissant. Le journal d’exécution affiche queued (en file d’attente), puis sent ou error.

Valide (un workflow doit avoir exactement un déclencheur), mais n’agit pas.

{
"nodes": [
{ "id": "trigger", "type": "trigger.cve_new_match", "params": {} }
],
"edges": []
}

« Quand une nouvelle CVE de ma stack dépasse le seuil d’alerte du projet, notifie Slack. » Le seuil lui-même (CVSS 7, 9…) se règle dans la politique de priorisation du projet.

{
"nodes": [
{ "id": "trigger", "type": "trigger.cve_new_match", "params": {} },
{ "id": "c1", "type": "condition.priorite", "params": { "critere": "au_dessus_seuil" } },
{ "id": "a1", "type": "action.fire_webhooks", "params": { "format": "slack" } }
],
"edges": [
{ "id": "e1", "source": "trigger", "target": "c1", "sourceHandle": "out" },
{ "id": "e2", "source": "c1", "target": "a1", "sourceHandle": "true" }
]
}

La connexion c1 → a1 utilise sourceHandle: "true" : l’action ne se déclenche que si la condition est vraie.

« KEV → fiche d’alerte + e-mail ; sinon → notification in-app simple. »

{
"nodes": [
{ "id": "trigger", "type": "trigger.cve_new_match", "params": {} },
{ "id": "kev", "type": "condition.is_kev", "params": {} },
{ "id": "case", "type": "action.create_case", "params": { "status": "PENDING" } },
{ "id": "mail", "type": "action.send_email", "params": {} },
{ "id": "inapp", "type": "action.notify_inapp", "params": {} }
],
"edges": [
{ "id": "e1", "source": "trigger", "target": "kev", "sourceHandle": "out" },
{ "id": "e2", "source": "kev", "target": "case", "sourceHandle": "true" },
{ "id": "e3", "source": "case", "target": "mail", "sourceHandle": "out" },
{ "id": "e4", "source": "kev", "target": "inapp", "sourceHandle": "false" }
]
}

Les actions s’enchaînent (case → mail via out). La branche false mène à une notification légère.

{
"nodes": [
{ "id": "trigger", "type": "trigger.cve_kev_added", "params": {} },
{
"id": "mail",
"type": "action.send_email",
"params": { "ai_enrich": true, "ai_instruction": "Priorise la remédiation." }
}
],
"edges": [
{ "id": "e1", "source": "trigger", "target": "mail", "sourceHandle": "out" }
]
}

L’analyse est une propriété du message, pas une étape du graphe : plus rien à ordonner.

{
"nodes": [
{ "id": "trigger", "type": "trigger.eol_approaching", "params": {} },
{ "id": "c1", "type": "condition.eol_days_remaining", "params": { "op": "lte", "value": 90 } },
{ "id": "a1", "type": "action.send_email", "params": {} }
],
"edges": [
{ "id": "e1", "source": "trigger", "target": "c1", "sourceHandle": "out" },
{ "id": "e2", "source": "c1", "target": "a1", "sourceHandle": "true" }
]
}

Quand NIS2 est activée sur un projet, ses avis de conformité (échéances de correction, fin de vie, revues d’inventaire, risques acceptés, et côté organisation : incidents, fournisseurs, gouvernance) partent par des workflows créés à l’activation. Ils se modifient comme les autres ; leurs tuiles propres :

Type Portée Paramètres
trigger.conformite_projet workflow de projet evenements (requis, liste) parmi conformite.echeance_proche, conformite.echeance_depassee, conformite.echeance_escaladee, conformite.fin_de_vie_depassee, conformite.fin_de_vie_escaladee, conformite.revue_due, conformite.risque_a_approuver, conformite.risque_decide, conformite.risque_a_revoir.
trigger.conformite_org workflow d’organisation evenements (requis, liste) parmi conformite.incident_echeance, conformite.fournisseur_a_evaluer, conformite.rapport_pret, conformite.fonction_attribuee.
condition.evenement_conformite les deux evenements (requis, liste) — vrai si l’avis est l’un d’eux.

Après un déclencheur de conformité, action.notify_inapp et action.send_email prennent destinataires (liste) : prevus (la personne que l’avis concerne — porteur, demandeur, approbateur… — à défaut les administrateurs), rssi, direction, incident, admins_projet, admins_org. Vide = prevus. recipients ajoute des membres de l’organisation à l’e-mail. action.fire_webhooks n’existe que dans un workflow de projet. Une personne ne reçoit jamais deux fois le même avis par le même canal, même si deux workflows l’y destinent.

{
"nodes": [
{ "id": "trigger", "type": "trigger.conformite_projet", "params": { "evenements": ["conformite.echeance_escaladee"] } },
{ "id": "mail", "type": "action.send_email", "params": { "destinataires": ["rssi", "direction"] } }
],
"edges": [{ "id": "e1", "source": "trigger", "target": "mail", "sourceHandle": "out" }]
}
Message Cause
Le workflow doit contenir exactement un déclencheur Zéro ou plusieurs tuiles trigger.*.
Sortie '…' invalide pour … sourceHandle incohérent (ex. out sur une condition, ou true sur une action).
Le workflow contient une boucle Le graphe n’est pas acyclique.
Un déclencheur ne peut pas avoir d'entrée Une connexion pointe vers le trigger.*.
Paramètre requis '…' manquant pour … Paramètre obligatoire absent (ex. critere sur condition.priorite).
Valeur '…' invalide pour … Valeur hors de la liste autorisée (sévérité, statut, opérateur…).
Valeur hors bornes pour … Nombre hors plage (CVSS 0–10, EPSS 0–1, jours −3650–3650).
La tuile … n'est pas disponible dans un workflow d'organisation Tuile réservée aux projets (ou l’inverse), ex. action.fire_webhooks dans un workflow d’organisation.
Trop de tuiles (max 30) / Workflow trop profond (max 20) Graphe trop grand.
  • Le plus simple : décrivez votre besoin à Vigie (assistant IA) sur la page Workflows — elle génère le graphe, que vous approuvez.
  • L’éditeur visuel produit et relit ce même JSON ; l’export d’un workflow donne un fichier { name, description, graph } réimportable.
  • Voir aussi : Workflows (API).