AutomationFlowsWeb Scraping › Prioritize and Report Wazuh Vulnerabilities with Epss and Cisa Kev

Prioritize and Report Wazuh Vulnerabilities with Epss and Cisa Kev

ByAlberto Flores @xpertix on n8n.io

This workflow runs daily (or manually) to pull vulnerability findings from Wazuh Indexer, enriches CVEs with FIRST EPSS and the CISA KEV catalog, calculates a priority score, tracks finding lifecycle in n8n Data Tables, and emails an executive security report while logging run…

Event trigger★★★★☆ complexity20 nodesHTTP RequestData TableEmail Send
Web Scraping Trigger: Event Nodes: 20 Complexity: ★★★★☆ Added:
Prioritize and Report Wazuh Vulnerabilities with Epss and Cisa Kev — n8n workflow card showing HTTP Request, Data Table, Email Send integration

This workflow corresponds to n8n.io template #17782 — we link there as the canonical source.

This workflow follows the Datatable → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "kGQM3SqYqjH2nc8Y",
  "meta": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true
  },
  "name": "Track and prioritize Wazuh vulnerabilities with EPSS and CISA KEV",
  "tags": [
    {
      "id": "vBzAMZ5eKvESpFXV",
      "name": "cybersecurity",
      "createdAt": "2026-08-03T19:17:53.250Z",
      "updatedAt": "2026-08-03T19:17:53.250Z"
    },
    {
      "id": "pad3X1JZMW21RUnx",
      "name": "wazuh",
      "createdAt": "2026-08-03T19:17:53.258Z",
      "updatedAt": "2026-08-03T19:17:53.258Z"
    },
    {
      "id": "QeU5aSFbaSbv65vq",
      "name": "vulnerability management",
      "createdAt": "2026-08-03T19:17:53.260Z",
      "updatedAt": "2026-08-03T19:17:53.260Z"
    },
    {
      "id": "Ii2AvPDqzLclC1Tc",
      "name": "daily report",
      "createdAt": "2026-08-03T19:17:53.262Z",
      "updatedAt": "2026-08-03T19:17:53.262Z"
    }
  ],
  "nodes": [
    {
      "id": "a6cf187d-4050-4d12-875b-cf305c36b05b",
      "name": "Run manually",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "e876b6f7-9ee0-4cd5-a4fe-db1ae9272c4c",
      "name": "Query Wazuh Vulnerability Inventory",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 3,
      "position": [
        480,
        144
      ],
      "parameters": {
        "url": "={{ $('Configure workflow').first().json.wazuhIndexerUrl.replace(/\\/$/, '') + '/wazuh-states-vulnerabilities*/_search' }}",
        "method": "POST",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          },
          "pagination": {
            "pagination": {
              "parameters": {
                "parameters": [
                  {
                    "name": "search_after",
                    "type": "body",
                    "value": "={{ $response.body.hits.hits[$response.body.hits.hits.length - 1].sort }}"
                  }
                ]
              },
              "maxRequests": 1000,
              "limitPagesFetched": true,
              "completeExpression": "={{ $response.body.hits.hits.length < 1000 }}",
              "paginationCompleteWhen": "other"
            }
          },
          "allowUnauthorizedCerts": false
        },
        "jsonBody": {
          "size": 1000,
          "sort": [
            {
              "agent.id": "asc"
            },
            {
              "vulnerability.id": "asc"
            },
            {
              "_id": "asc"
            }
          ],
          "query": {
            "match_all": {}
          },
          "track_total_hits": true
        },
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth"
      },
      "credentials": {
        "httpBasicAuth": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.4,
      "waitBetweenTries": 5000
    },
    {
      "id": "6fdc5122-3cdc-48cf-9fd1-fb2e50c5a48a",
      "name": "Normalize Vulnerabilities",
      "type": "n8n-nodes-base.code",
      "position": [
        720,
        144
      ],
      "parameters": {
        "jsCode": "const hits = $input.all().flatMap(item => item.json.hits?.hits ?? []);\nreturn hits.map((hit) => {\n  const s = hit._source ?? {};\n  const v = s.vulnerability ?? {};\n  const p = s.package ?? {};\n  const a = s.agent ?? {};\n  return { json: {\n    finding_key: hit._id ?? [a.id, v.id, p.name, p.version].join('|'),\n    cve: v.id ?? '',\n    agent_id: a.id ?? '',\n    agent_name: a.name ?? '',\n    package_name: p.name ?? '',\n    package_version: p.version ?? '',\n    severity: v.severity ?? 'Unknown',\n    cvss_score: Number(v.score?.base ?? 0),\n    lifecycle_status: 'active',\n    first_seen_at: s['@timestamp'] ?? new Date().toISOString(),\n    last_seen_at: new Date().toISOString(),\n    resolved_at: null,\n    detection_count: 1,\n    cisa_kev: false,\n    epss_score: 0,\n    priority_score: Number(v.score?.base ?? 0),\n    context_object: JSON.stringify({ source_index: hit._index, status: v.status ?? 'Active' })\n  }};\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "ecb11343-fc40-41fd-891d-6aabb3e9bafb",
      "name": "Run daily at 07:00 UTC",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        192
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 7
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "953afd61-a37e-4017-ae51-369f923ef037",
      "name": "Load Lifecycle History",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        1952,
        144
      ],
      "parameters": {
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "wazuh_vulnerability_lifecycle",
          "cachedResultName": "wazuh_vulnerability_lifecycle"
        }
      },
      "executeOnce": true,
      "typeVersion": 1.1,
      "alwaysOutputData": true
    },
    {
      "id": "52d2bf49-90c7-49b5-b352-16d77b343d54",
      "name": "Compute Lifecycle Changes",
      "type": "n8n-nodes-base.code",
      "position": [
        2192,
        144
      ],
      "parameters": {
        "jsCode": "const current = $('Apply Threat Intelligence').all().map(i => i.json);\nconst history = $input.all().map(i => i.json).filter(r => r.finding_key);\nconst now = new Date().toISOString();\nconst previous = new Map(history.map(r => [r.finding_key, r]));\nconst seen = new Set();\nconst changes = current.map(row => {\n  const old = previous.get(row.finding_key);\n  seen.add(row.finding_key);\n  return { json: {\n    ...row,\n    lifecycle_status: !old ? 'new' : old.lifecycle_status === 'resolved' ? 'reopened' : 'ongoing',\n    first_seen_at: old?.first_seen_at ?? row.first_seen_at ?? now,\n    last_seen_at: now,\n    resolved_at: null,\n    detection_count: Number(old?.detection_count ?? 0) + 1\n  }};\n});\nfor (const old of history) {\n  if (!seen.has(old.finding_key) && old.lifecycle_status !== 'resolved') {\n    changes.push({ json: { ...old, lifecycle_status: 'resolved', last_seen_at: old.last_seen_at ?? now, resolved_at: now } });\n  }\n}\nreturn changes;"
      },
      "typeVersion": 2
    },
    {
      "id": "0c5fd426-bd7c-4b79-87e8-c0e77dead309",
      "name": "Upsert Lifecycle Records",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        2432,
        144
      ],
      "parameters": {
        "columns": {
          "value": {
            "cve": "={{ $json.cve }}",
            "agent_id": "={{ $json.agent_id }}",
            "cisa_kev": "={{ $json.cisa_kev }}",
            "severity": "={{ $json.severity }}",
            "agent_name": "={{ $json.agent_name }}",
            "cvss_score": "={{ $json.cvss_score }}",
            "epss_score": "={{ $json.epss_score }}",
            "finding_key": "={{ $json.finding_key }}",
            "resolved_at": "={{ $json.resolved_at }}",
            "last_seen_at": "={{ $json.last_seen_at }}",
            "package_name": "={{ $json.package_name }}",
            "first_seen_at": "={{ $json.first_seen_at }}",
            "context_object": "={{ $json.context_object }}",
            "priority_score": "={{ $json.priority_score }}",
            "detection_count": "={{ $json.detection_count }}",
            "package_version": "={{ $json.package_version }}",
            "lifecycle_status": "={{ $json.lifecycle_status }}"
          },
          "schema": [
            {
              "id": "finding_key",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "finding_key",
              "defaultMatch": false
            },
            {
              "id": "cve",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "cve",
              "defaultMatch": false
            },
            {
              "id": "agent_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "agent_id",
              "defaultMatch": false
            },
            {
              "id": "agent_name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "agent_name",
              "defaultMatch": false
            },
            {
              "id": "package_name",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "package_name",
              "defaultMatch": false
            },
            {
              "id": "package_version",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "package_version",
              "defaultMatch": false
            },
            {
              "id": "severity",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "severity",
              "defaultMatch": false
            },
            {
              "id": "cvss_score",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "cvss_score",
              "defaultMatch": false
            },
            {
              "id": "lifecycle_status",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "lifecycle_status",
              "defaultMatch": false
            },
            {
              "id": "first_seen_at",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "first_seen_at",
              "defaultMatch": false
            },
            {
              "id": "last_seen_at",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "last_seen_at",
              "defaultMatch": false
            },
            {
              "id": "resolved_at",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "resolved_at",
              "defaultMatch": false
            },
            {
              "id": "detection_count",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "detection_count",
              "defaultMatch": false
            },
            {
              "id": "cisa_kev",
              "type": "boolean",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "cisa_kev",
              "defaultMatch": false
            },
            {
              "id": "epss_score",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "epss_score",
              "defaultMatch": false
            },
            {
              "id": "priority_score",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "priority_score",
              "defaultMatch": false
            },
            {
              "id": "context_object",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "context_object",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "finding_key",
              "keyValue": "={{ $json.finding_key }}"
            }
          ]
        },
        "options": {},
        "matchType": "allConditions",
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "wazuh_vulnerability_lifecycle",
          "cachedResultName": "wazuh_vulnerability_lifecycle"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "df2d9b9e-7c54-48f9-80a3-2491c3917fcc",
      "name": "Build Run Summary",
      "type": "n8n-nodes-base.code",
      "position": [
        2752,
        256
      ],
      "parameters": {
        "jsCode": "const rows = $('Compute Lifecycle Changes').all().map(i => i.json);\nconst config = $('Configure workflow').first().json;\nconst active = rows.filter(r => r.lifecycle_status !== 'resolved');\nconst count = status => rows.filter(r => r.lifecycle_status === status).length;\nconst severity = level => active.filter(r => String(r.severity).toLowerCase() === level).length;\nconst now = new Date().toISOString();\nconst epssThreshold = Number(config.epssHighThreshold ?? 0.5);\nconst priorityThreshold = Number(config.criticalPriorityThreshold ?? 80);\nreturn [{ json: {\n  run_id: `wazuh-${Date.now()}`,\n  started_at: now,\n  completed_at: now,\n  run_status: 'completed',\n  total_active: active.length,\n  new_count: count('new'),\n  ongoing_count: count('ongoing') + count('reopened'),\n  resolved_count: count('resolved'),\n  critical_count: severity('critical'),\n  high_count: severity('high'),\n  summary_object: JSON.stringify({\n    reopened: count('reopened'),\n    cisa_kev_count: active.filter(r => r.cisa_kev).length,\n    epss_high_probability_count: active.filter(r => Number(r.epss_score ?? 0) >= epssThreshold).length,\n    priority_threshold_count: active.filter(r => Number(r.priority_score ?? 0) >= priorityThreshold).length,\n    epss_threshold: epssThreshold,\n    priority_threshold: priorityThreshold,\n    generatedAt: now\n  })\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "347d7fa9-0764-4288-9b28-2c8e56c88f33",
      "name": "Log Workflow Run",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        2992,
        352
      ],
      "parameters": {
        "columns": {
          "value": {
            "run_id": "={{ $json.run_id }}",
            "new_count": "={{ $json.new_count }}",
            "high_count": "={{ $json.high_count }}",
            "run_status": "={{ $json.run_status }}",
            "started_at": "={{ $json.started_at }}",
            "completed_at": "={{ $json.completed_at }}",
            "total_active": "={{ $json.total_active }}",
            "ongoing_count": "={{ $json.ongoing_count }}",
            "critical_count": "={{ $json.critical_count }}",
            "resolved_count": "={{ $json.resolved_count }}",
            "summary_object": "={{ $json.summary_object }}"
          },
          "schema": [
            {
              "id": "run_id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "run_id",
              "defaultMatch": false
            },
            {
              "id": "started_at",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "started_at",
              "defaultMatch": false
            },
            {
              "id": "completed_at",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "completed_at",
              "defaultMatch": false
            },
            {
              "id": "run_status",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "run_status",
              "defaultMatch": false
            },
            {
              "id": "total_active",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "total_active",
              "defaultMatch": false
            },
            {
              "id": "new_count",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "new_count",
              "defaultMatch": false
            },
            {
              "id": "ongoing_count",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "ongoing_count",
              "defaultMatch": false
            },
            {
              "id": "resolved_count",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "resolved_count",
              "defaultMatch": false
            },
            {
              "id": "critical_count",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "critical_count",
              "defaultMatch": false
            },
            {
              "id": "high_count",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "high_count",
              "defaultMatch": false
            },
            {
              "id": "summary_object",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "summary_object",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "dataTableId": {
          "__rl": true,
          "mode": "name",
          "value": "wazuh_vulnerability_runs",
          "cachedResultName": "wazuh_vulnerability_runs"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "b6314f68-20c8-4f95-8f5d-263f7a8f6c31",
      "name": "Build EPSS Batches",
      "type": "n8n-nodes-base.code",
      "notes": "Reduce los hallazgos a CVE \u00fanicos y los agrupa en lotes para evitar una llamada por vulnerabilidad.",
      "position": [
        976,
        144
      ],
      "parameters": {
        "jsCode": "const uniqueCves = [...new Set(\n  $input.all()\n    .map(item => String(item.json.cve ?? '').trim().toUpperCase())\n    .filter(cve => /^CVE-\\d{4}-\\d{4,}$/.test(cve))\n)];\nconst batchSize = 100;\nconst batches = [];\nfor (let index = 0; index < uniqueCves.length; index += batchSize) {\n  const cves = uniqueCves.slice(index, index + batchSize);\n  batches.push({\n    json: {\n      batch_number: Math.floor(index / batchSize) + 1,\n      cve_count: cves.length,\n      cves: cves.join(',')\n    }\n  });\n}\nreturn batches;"
      },
      "typeVersion": 2
    },
    {
      "id": "460e4926-9642-4714-8650-fef10361945c",
      "name": "Fetch EPSS in Batches",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Env\u00eda \u00fanicamente identificadores CVE a FIRST EPSS, con autorizaci\u00f3n expl\u00edcita del usuario; no transmite activos, paquetes ni credenciales.",
      "maxTries": 3,
      "position": [
        1200,
        144
      ],
      "parameters": {
        "url": "https://api.first.org/data/v1/epss",
        "options": {
          "timeout": 30000,
          "batching": {
            "batch": {
              "batchSize": 1,
              "batchInterval": 250
            }
          },
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "cve",
              "value": "={{ $json.cves }}"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.4,
      "waitBetweenTries": 5000
    },
    {
      "id": "e553a516-f596-426e-9501-6f64a96c4681",
      "name": "Fetch CISA KEV Catalog",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Descarga una sola vez por ejecuci\u00f3n el cat\u00e1logo oficial CISA Known Exploited Vulnerabilities.",
      "maxTries": 3,
      "position": [
        1424,
        144
      ],
      "parameters": {
        "url": "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "executeOnce": true,
      "retryOnFail": true,
      "typeVersion": 4.4,
      "waitBetweenTries": 5000
    },
    {
      "id": "b6136f58-dd4c-4bd8-b314-01aa15acec8c",
      "name": "Apply Threat Intelligence",
      "type": "n8n-nodes-base.code",
      "notes": "Cruza Wazuh, EPSS y CISA KEV y calcula un puntaje explicable de 0 a 100.",
      "position": [
        1648,
        144
      ],
      "parameters": {
        "jsCode": "const current = $('Normalize Vulnerabilities').all().map(item => item.json);\nconst epssResponses = $('Fetch EPSS in Batches').all().map(item => item.json);\nconst kevResponse = $input.first().json;\nconst config = $('Configure workflow').first().json;\n\nconst epssByCve = new Map();\nfor (const response of epssResponses) {\n  for (const entry of response.data ?? []) {\n    const cve = String(entry.cve ?? '').toUpperCase();\n    if (cve) epssByCve.set(cve, entry);\n  }\n}\n\nconst kevByCve = new Map();\nfor (const entry of kevResponse.vulnerabilities ?? []) {\n  const cve = String(entry.cveID ?? '').toUpperCase();\n  if (cve) kevByCve.set(cve, entry);\n}\n\nconst cvssWeight = Number(config.cvssWeight ?? 5);\nconst epssWeight = Number(config.epssWeight ?? 30);\nconst kevWeight = Number(config.kevWeight ?? 20);\n\nreturn current.map(row => {\n  const cve = String(row.cve ?? '').toUpperCase();\n  const epss = epssByCve.get(cve);\n  const kev = kevByCve.get(cve);\n  const epssScore = Number(epss?.epss ?? 0);\n  const cvssScore = Number(row.cvss_score ?? 0);\n  const priorityScore = Math.min(100, Math.round((cvssScore * cvssWeight + epssScore * epssWeight + (kev ? kevWeight : 0)) * 10) / 10);\n\n  let baseContext = {};\n  try { baseContext = JSON.parse(row.context_object || '{}'); } catch {}\n\n  return { json: {\n    ...row,\n    cisa_kev: Boolean(kev),\n    epss_score: epssScore,\n    priority_score: priorityScore,\n    context_object: JSON.stringify({\n      ...baseContext,\n      epss_percentile: Number(epss?.percentile ?? 0),\n      epss_date: epss?.date ?? null,\n      cisa_kev: kev ? {\n        vendor: kev.vendorProject ?? null,\n        product: kev.product ?? null,\n        vulnerability_name: kev.vulnerabilityName ?? null,\n        date_added: kev.dateAdded ?? null,\n        due_date: kev.dueDate ?? null,\n        known_ransomware_use: kev.knownRansomwareCampaignUse ?? null,\n        required_action: kev.requiredAction ?? null\n      } : null,\n      priority_formula: `CVSS\u00d7${cvssWeight} + EPSS\u00d7${epssWeight} + KEV\u00d7${kevWeight}`\n    })\n  }};\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "fe3e2226-1d87-4ad8-a089-42111db91eb7",
      "name": "Send Executive Security Report",
      "type": "n8n-nodes-base.emailSend",
      "maxTries": 3,
      "position": [
        2992,
        160
      ],
      "parameters": {
        "html": "={{ (() => {\n  const summary = $('Build Run Summary').first().json;\n  const details = JSON.parse(summary.summary_object || '{}');\n  const config = $('Configure workflow').first().json;\n  const topLimit = Number(config.topFindingsLimit || 10);\n  const rows = $('Compute Lifecycle Changes').all()\n    .map(item => item.json)\n    .filter(row => row.lifecycle_status !== 'resolved')\n    .sort((a, b) => Number(b.priority_score || 0) - Number(a.priority_score || 0))\n    .slice(0, topLimit);\n\n  const escapeHtml = value => String(value ?? '')\n    .replaceAll('&', '&amp;')\n    .replaceAll('<', '&lt;')\n    .replaceAll('>', '&gt;')\n    .replaceAll('\"', '&quot;')\n    .replaceAll(\"'\", '&#039;');\n\n  const topRows = rows.map(row => `\n    <tr>\n      <td style=\"padding:8px;border-bottom:1px solid #e5e7eb;\">${escapeHtml(row.cve)}</td>\n      <td style=\"padding:8px;border-bottom:1px solid #e5e7eb;\">${escapeHtml(row.agent_name)}</td>\n      <td style=\"padding:8px;border-bottom:1px solid #e5e7eb;\">${escapeHtml(row.severity)}</td>\n      <td style=\"padding:8px;border-bottom:1px solid #e5e7eb;text-align:right;\">${Number(row.cvss_score || 0).toFixed(1)}</td>\n      <td style=\"padding:8px;border-bottom:1px solid #e5e7eb;text-align:right;\">${(Number(row.epss_score || 0) * 100).toFixed(2)}%</td>\n      <td style=\"padding:8px;border-bottom:1px solid #e5e7eb;text-align:center;\">${row.cisa_kev ? 'Yes' : 'No'}</td>\n      <td style=\"padding:8px;border-bottom:1px solid #e5e7eb;text-align:right;font-weight:700;\">${Number(row.priority_score || 0).toFixed(1)}</td>\n    </tr>\n  `).join('');\n\n  return `\n  <div style=\"font-family:Arial,sans-serif;color:#111827;max-width:900px;margin:auto;\">\n    <div style=\"background:#111827;color:#fff;padding:24px;border-radius:10px 10px 0 0;\">\n      <h1 style=\"margin:0;font-size:24px;\">${escapeHtml(config.organizationName)} Cybersecurity Daily Brief</h1>\n      <p style=\"margin:8px 0 0;color:#d1d5db;\">Wazuh Vulnerability Intelligence & Lifecycle</p>\n    </div>\n    <div style=\"padding:24px;border:1px solid #e5e7eb;\">\n      <p>Generated at: <strong>${escapeHtml(summary.completed_at)}</strong></p>\n      <table style=\"width:100%;border-collapse:collapse;margin:20px 0;\">\n        <tr>\n          <td style=\"padding:12px;background:#f3f4f6;\"><strong>Active</strong><br>${summary.total_active}</td>\n          <td style=\"padding:12px;background:#fee2e2;\"><strong>Critical</strong><br>${summary.critical_count}</td>\n          <td style=\"padding:12px;background:#ffedd5;\"><strong>High</strong><br>${summary.high_count}</td>\n          <td style=\"padding:12px;background:#fef3c7;\"><strong>CISA KEV</strong><br>${details.cisa_kev_count || 0}</td>\n          <td style=\"padding:12px;background:#dbeafe;\"><strong>EPSS \u2265 ${(Number(details.epss_threshold || 0.5) * 100).toFixed(0)}%</strong><br>${details.epss_high_probability_count || 0}</td>\n        </tr>\n      </table>\n      <p><strong>Lifecycle changes:</strong> ${summary.new_count} new, ${summary.ongoing_count} ongoing, ${summary.resolved_count} resolved and ${details.reopened || 0} reopened.</p>\n      <h2 style=\"margin-top:28px;\">Top ${topLimit} findings by priority</h2>\n      <table style=\"width:100%;border-collapse:collapse;font-size:13px;\">\n        <thead>\n          <tr style=\"background:#111827;color:#fff;\">\n            <th style=\"padding:8px;text-align:left;\">CVE</th>\n            <th style=\"padding:8px;text-align:left;\">Agent</th>\n            <th style=\"padding:8px;text-align:left;\">Severity</th>\n            <th style=\"padding:8px;\">CVSS</th>\n            <th style=\"padding:8px;\">EPSS</th>\n            <th style=\"padding:8px;\">KEV</th>\n            <th style=\"padding:8px;\">Priority</th>\n          </tr>\n        </thead>\n        <tbody>${topRows}</tbody>\n      </table>\n      <p style=\"margin-top:24px;color:#6b7280;font-size:12px;\">Priority = CVSS\u00d7${config.cvssWeight} + EPSS\u00d7${config.epssWeight} + CISA KEV\u00d7${config.kevWeight}. Maximum score: 100. Sources: Wazuh, FIRST EPSS and CISA KEV.</p>\n    </div>\n  </div>`;\n})() }}",
        "options": {
          "replyTo": "={{ $('Configure workflow').first().json.senderEmail }}",
          "appendAttribution": false
        },
        "subject": "={{ '[' + $('Configure workflow').first().json.organizationName + '] Cybersecurity Daily Brief - ' + $('Build Run Summary').first().json.completed_at.slice(0, 10) }}",
        "toEmail": "={{ $('Configure workflow').first().json.reportRecipient }}",
        "fromEmail": "={{ $('Configure workflow').first().json.senderName + ' <' + $('Configure workflow').first().json.senderEmail + '>' }}"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 2.1,
      "waitBetweenTries": 5000
    },
    {
      "id": "1d8ccf14-9580-4a07-87ae-5db7723d3b01",
      "name": "Workflow overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -864,
        -912
      ],
      "parameters": {
        "color": "#FFE770",
        "width": 620,
        "height": 1096,
        "content": "## Track and prioritize Wazuh vulnerabilities\n\n**Wazuh** is an open-source cybersecurity platform that provides SIEM and XDR capabilities. It collects and analyzes security telemetry to support use cases such as threat detection, compliance monitoring, incident investigation, and vulnerability detection.\n\nWazuh can identify software vulnerabilities across monitored endpoints, but security teams may still need clearer historical reporting, lifecycle tracking, and context for deciding which findings require action first. This workflow addresses those gaps by preserving findings over time and turning raw vulnerability data into a prioritized security report.\n\n### How it works\n\nThe workflow reads Wazuh's complete vulnerability inventory with a read-only account, validated TLS, and search-after pagination. It tracks whether each finding is **new**, **ongoing**, **resolved**, or **reopened** and enriches CVEs with:\n\n\u2192 **FIRST EPSS**, which estimates the probability that a vulnerability will be exploited in the next 30 days.\n\n\u2192 **CISA KEV**, a catalog of vulnerabilities known to have been exploited in real-world attacks.\n\nIt combines this context with CVSS severity to calculate an explainable priority score, sends an executive email report, and records run metrics for auditing.\n\n### Setup\n\n\u2192 Create the two Data Tables described in the section notes.\n\n\u2192 Add read-only Wazuh/OpenSearch Basic Auth credentials and trust the Wazuh Indexer CA in the n8n host or container.\n\n\u2192 Add SMTP credentials to the email node.\n\n\u2192 Replace every placeholder in **Configure workflow**.\n\n\u2192 Run it manually, review the results, and then activate the schedule.\n\n### Customization\n\nAdjust the schedule, recipients, report size, risk thresholds, and scoring weights in **Configure workflow**."
      },
      "typeVersion": 1
    },
    {
      "id": "2a1c2d40-3843-4b69-9093-63f5599067a2",
      "name": "Collection and least privilege",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 520,
        "content": "### 1 - Configure, collect, and normalize\nSet deployment-specific values in **Configure workflow**. The Wazuh request validates TLS, uses read-only Basic Auth, retrieves the full inventory with search-after pagination, and creates a stable finding key for lifecycle tracking."
      },
      "typeVersion": 1
    },
    {
      "id": "36006bbd-e045-4996-8632-1c2315473a03",
      "name": "Threat intelligence methodology",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 928,
        "height": 520,
        "content": "### 2 - Enrich and prioritize\nCVEs are deduplicated and queried in EPSS batches. The CISA KEV catalog is downloaded once, then configurable CVSS, EPSS, and KEV weights produce an explainable score."
      },
      "typeVersion": 1
    },
    {
      "id": "45c9818b-b526-4732-a4ce-789c142d05c4",
      "name": "Lifecycle state model",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1872,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 760,
        "height": 520,
        "content": "### 3 - Track lifecycle changes\nCreate a Data Table named `wazuh_vulnerability_lifecycle` using the schema described in the submission guide. Current findings are classified as new, ongoing, resolved, or reopened and then upserted."
      },
      "typeVersion": 1
    },
    {
      "id": "5fda1a54-9a12-4d59-8464-ed6d15236955",
      "name": "Outputs and audit trail",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2656,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 744,
        "content": "### 4 - Report and audit\nThe summary fans out to email and a terminal audit branch. Create `wazuh_vulnerability_runs` using the documented schema. The log node is intentionally terminal; it is connected and stores one row per run."
      },
      "typeVersion": 1
    },
    {
      "id": "caa697ae-b48b-4ce1-a1b6-6fde5db0717d",
      "name": "Configure workflow",
      "type": "n8n-nodes-base.set",
      "position": [
        240,
        144
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "organization-name",
              "name": "organizationName",
              "type": "string",
              "value": "Your Organization"
            },
            {
              "id": "wazuh-indexer-url",
              "name": "wazuhIndexerUrl",
              "type": "string",
              "value": "https://YOUR_WAZUH_INDEXER:9200"
            },
            {
              "id": "report-recipient",
              "name": "reportRecipient",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "sender-name",
              "name": "senderName",
              "type": "string",
              "value": "Security Automation"
            },
            {
              "id": "sender-email",
              "name": "senderEmail",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "top-findings-limit",
              "name": "topFindingsLimit",
              "type": "number",
              "value": 10
            },
            {
              "id": "epss-high-threshold",
              "name": "epssHighThreshold",
              "type": "number",
              "value": 0.5
            },
            {
              "id": "critical-priority-threshold",
              "name": "criticalPriorityThreshold",
              "type": "number",
              "value": 80
            },
            {
              "id": "cvss-weight",
              "name": "cvssWeight",
              "type": "number",
              "value": 5
            },
            {
              "id": "epss-weight",
              "name": "epssWeight",
              "type": "number",
              "value": 30
            },
            {
              "id": "kev-weight",
              "name": "kevWeight",
              "type": "number",
              "value": 20
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "active": false,
  "settings": {
    "timezone": "UTC",
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all"
  },
  "versionId": "2f6acb5b-72cf-4774-9705-674e6d61db75",
  "nodeGroups": [
    {
      "id": "configuration-collection",
      "name": "Configure, collect, and normalize Wazuh findings",
      "nodeIds": [
        "caa697ae-b48b-4ce1-a1b6-6fde5db0717d",
        "e876b6f7-9ee0-4cd5-a4fe-db1ae9272c4c",
        "6fdc5122-3cdc-48cf-9fd1-fb2e50c5a48a"
      ]
    },
    {
      "id": "threat-intel-enrichment",
      "name": "Enrich and prioritize vulnerabilities",
      "nodeIds": [
        "b6314f68-20c8-4f95-8f5d-263f7a8f6c31",
        "460e4926-9642-4714-8650-fef10361945c",
        "e553a516-f596-426e-9501-6f64a96c4681",
        "b6136f58-dd4c-4bd8-b314-01aa15acec8c"
      ]
    },
    {
      "id": "track-lifecycle",
      "name": "Track vulnerability lifecycle",
      "nodeIds": [
        "953afd61-a37e-4017-ae51-369f923ef037",
        "52d2bf49-90c7-49b5-b352-16d77b343d54",
        "0c5fd426-bd7c-4b79-87e8-c0e77dead309"
      ]
    },
    {
      "id": "report-audit",
      "name": "Report results and log each run",
      "nodeIds": [
        "df2d9b9e-7c54-48f9-80a3-2491c3917fcc",
        "fe3e2226-1d87-4ad8-a089-42111db91eb7",
        "347d7fa9-0764-4288-9b28-2c8e56c88f33"
      ]
    }
  ],
  "connections": {
    "Run manually": {
      "main": [
        [
          {
            "node": "Configure workflow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Run Summary": {
      "main": [
        [
          {
            "node": "Log Workflow Run",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Executive Security Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build EPSS Batches": {
      "main": [
        [
          {
            "node": "Fetch EPSS in Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure workflow": {
      "main": [
        [
          {
            "node": "Query Wazuh Vulnerability Inventory",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch EPSS in Batches": {
      "main": [
        [
          {
            "node": "Fetch CISA KEV Catalog",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch CISA KEV Catalog": {
      "main": [
        [
          {
            "node": "Apply Threat Intelligence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Lifecycle History": {
      "main": [
        [
          {
            "node": "Compute Lifecycle Changes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run daily at 07:00 UTC": {
      "main": [
        [
          {
            "node": "Configure workflow",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert Lifecycle Records": {
      "main": [
        [
          {
            "node": "Build Run Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apply Threat Intelligence": {
      "main": [
        [
          {
            "node": "Load Lifecycle History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute Lifecycle Changes": {
      "main": [
        [
          {
            "node": "Upsert Lifecycle Records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Vulnerabilities": {
      "main": [
        [
          {
            "node": "Build EPSS Batches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Query Wazuh Vulnerability Inventory": {
      "main": [
        [
          {
            "node": "Normalize Vulnerabilities",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow runs daily (or manually) to pull vulnerability findings from Wazuh Indexer, enriches CVEs with FIRST EPSS and the CISA KEV catalog, calculates a priority score, tracks finding lifecycle in n8n Data Tables, and emails an executive security report while logging run…

Source: https://n8n.io/workflows/17782/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

This workflow runs a manual health assessment of a Wazuh deployment by querying the Wazuh Manager API, Wazuh Indexer API, and host metrics over SSH, then scores the results, generates prioritized reco

HTTP Request, Ssh, Data Table +1
Web Scraping

The Sora 2 API allows seamless generation of CGI ads, turning text prompts into stunning videos. This workflow automates the entire process from video generation to upload, notification, and file shar

Form Trigger, HTTP Request, Email Send +1
Web Scraping

This workflow automates the collection of public procurement data from TenderNed (the official Dutch tender platform). It: Fetches the latest tender publications from the TenderNed API Retrieves detai

HTTP Request, XML, Data Table
Web Scraping

Storyboard I2V Pipeline (17 shots) with Retry/Wait/State/Notify. Uses httpTrigger, httpRequest, emailSend. Event-driven trigger; 28 nodes.

Http Trigger, HTTP Request, Email Send
Web Scraping

Create CGI ads effortlessly by integrating the Google Veo3 API for video generation and uploading to Google Drive with seamless email notifications. On form submission: Triggers the workflow when a fo

Form Trigger, HTTP Request, Email Send +1