Hvad det gør
OpenCVE fortæller dig at en CVE er relevant, men en CVE-id alene er ikke handlingsbar. Dette workflow tager id'et og samler den kontekst du skal bruge for at prioritere: CVSS-score fra NVD og EPSS-sandsynlighed for udnyttelse — og skriver det hele til dit dashboard så du har ét sted at triagere fra.
Flow-overblik
Webhook validerer → parallel berigelse → merge → byg record → upsert.
Node-for-node
| # | Node | Type | Funktion |
|---|---|---|---|
| 1 | Webhook | webhook | POST-endpoint /webhook/opencve-cve som OpenCVE kalder |
| 2 | Validate Secret | if | Tjekker hemmelig header så kun OpenCVE kan trigge |
| 3 | NVD Lookup | httpRequest | Henter CVSS + beskrivelse fra NVD CVE API 2.0 |
| 4 | EPSS Score | httpRequest | Henter EPSS-sandsynlighed fra FIRST.org |
| 5 | Merge | merge | Kombinerer de to berigelseskilder (combine all) |
| 6 | Build Record | code | Samler felterne til ét fladt dashboard-objekt |
| 7 | Upsert | postgres | INSERT … ON CONFLICT opdaterer eksisterende CVE-rækker |
Importérbar JSON
n8n importIndsæt i n8n via Workflows → ⋯ → Import from clipboard. Erstat DIN_HEMMELIGHED, Postgres-credential og felt-stier så de matcher dit setup.
{
"name": "OpenCVE Enrichment",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "opencve-cve",
"responseMode": "lastNode",
"options": {}
},
"name": "Webhook (OpenCVE)",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
300
]
},
{
"parameters": {
"conditions": {
"options": {},
"conditions": [
{
"leftValue": "={{ $json.headers['x-webhook-secret'] }}",
"rightValue": "DIN_HEMMELIGHED",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
},
"name": "Validate Secret",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
460,
300
]
},
{
"parameters": {
"url": "=https://services.nvd.nist.gov/rest/json/cves/2.0?cveId={{ $json.body.cve_id }}",
"options": {}
},
"name": "NVD Lookup",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
680,
240
]
},
{
"parameters": {
"url": "=https://api.first.org/data/v1/epss?cve={{ $json.body.cve_id }}",
"options": {}
},
"name": "EPSS Score",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
680,
380
]
},
{
"parameters": {
"mode": "combine",
"combineBy": "combineAll",
"options": {}
},
"name": "Merge Enrichment",
"type": "n8n-nodes-base.merge",
"typeVersion": 3,
"position": [
900,
300
]
},
{
"parameters": {
"jsCode": "// Saml beriget CVE-objekt til dashboard\nconst cve = $input.first().json;\nreturn [{ json: {\n cve_id: cve.body?.cve_id ?? cve.cve_id,\n cvss: cve.vulnerabilities?.[0]?.cve?.metrics?.cvssMetricV31?.[0]?.cvssData?.baseScore ?? null,\n epss: cve.data?.[0]?.epss ?? null,\n description: cve.vulnerabilities?.[0]?.cve?.descriptions?.find(d=>d.lang==='en')?.value ?? '',\n published: cve.vulnerabilities?.[0]?.cve?.published ?? null,\n enriched_at: new Date().toISOString()\n}}];"
},
"name": "Build Record",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
300
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO cves (cve_id, cvss, epss, description, published, enriched_at) VALUES ($1,$2,$3,$4,$5,$6) ON CONFLICT (cve_id) DO UPDATE SET cvss=EXCLUDED.cvss, epss=EXCLUDED.epss, enriched_at=EXCLUDED.enriched_at;",
"options": {}
},
"name": "Upsert Dashboard DB",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
1340,
300
]
}
],
"connections": {
"Webhook (OpenCVE)": {
"main": [
[
{
"node": "Validate Secret",
"type": "main",
"index": 0
}
]
]
},
"Validate Secret": {
"main": [
[
{
"node": "NVD Lookup",
"type": "main",
"index": 0
},
{
"node": "EPSS Score",
"type": "main",
"index": 0
}
]
]
},
"NVD Lookup": {
"main": [
[
{
"node": "Merge Enrichment",
"type": "main",
"index": 0
}
]
]
},
"EPSS Score": {
"main": [
[
{
"node": "Merge Enrichment",
"type": "main",
"index": 1
}
]
]
},
"Merge Enrichment": {
"main": [
[
{
"node": "Build Record",
"type": "main",
"index": 0
}
]
]
},
"Build Record": {
"main": [
[
{
"node": "Upsert Dashboard DB",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
Dashboard-tabel
Minimal Postgres-tabel som Upsert-noden skriver til. Tilpas felter efter dit dashboard.
CREATE TABLE cves (
cve_id TEXT PRIMARY KEY,
cvss NUMERIC,
epss NUMERIC,
description TEXT,
published TIMESTAMPTZ,
enriched_at TIMESTAMPTZ DEFAULT now()
);
INSERT … ON CONFLICT).Credentials
| Credential | Opsætning |
|---|---|
| Webhook-secret | Vilkårlig stærk streng; sættes i OpenCVE-webhookken som header og valideres i node 2 |
| NVD API-nøgle | Gratis fra nvd.nist.gov; tilføj som header på NVD-noden for højere rate-limit |
| Postgres | n8n Postgres-credential mod din dashboard-database |
Tuning & udvidelser
| Udvidelse | Effekt |
|---|---|
| CISA KEV | Tilføj opslag mod CISA's Known Exploited Vulnerabilities — stærkeste prioriterings-signal |
| Severity-filter | Drop/markér CVE'er under en CVSS- eller EPSS-tærskel før upsert |
| Alerting | Tilføj Slack/e-mail-node ved KEV-match eller CVSS ≥ 9.0 |
| Berørte produkter | Gem CPE/produktliste fra NVD så dashboardet kan filtrere på dit miljø |
| Retry | Sæt retry-on-fail på HTTP-noderne mod NVD/EPSS-timeouts |
| Idempotens | ON CONFLICT DO UPDATE sikrer at gentagne webhooks for samme CVE ikke laver dubletter |