vulnfeed
← Back to feed Grafana Agents API

⚡ n8n Security Automation with vulnfeed

Four import-ready n8n workflows that connect vulnfeed's CVE feed to Slack, email, Jira, and PagerDuty. Copy the JSON, import into n8n, configure your credentials — done.

Setup Slack KEV alert Weekly email digest Jira tickets PagerDuty Tips
🔗 Data source All workflows fetch https://vulnfeed.it/vulns.json — a public JSON array of {10k–15k} CVEs updated every 4 hours. No API key, no rate limits, CORS open. Fields: id, title, severity, score, epss_pct, badge, poc, published, affected, references.

Prerequisites

  1. A running n8n instance — self-hosted or n8n Cloud.
  2. Credentials configured in n8n for whichever destination you use (Slack, SMTP, Jira, or PagerDuty routing key).
  3. To import: open n8n → Workflows → New → ⋮ → Import from JSON, paste the workflow JSON below, save, and activate.
  4. Replace placeholder values ($vars.SLACK_WEBHOOK, security@yourcompany.com, etc.) with real values via n8n → Settings → Variables or directly in each node.
▶ How to import a workflow JSON in n8n Workflows menu → + (new workflow) → top-right menu → Import from JSON → paste → Save → toggle Active.

Workflow 1 — Slack alert for actively exploited CVEs

Slack KEV alert every 4 hours

Runs every 4 hours, fetches vulnfeed, filters CVEs on the CISA Known Exploited Vulnerabilities list, and posts a formatted Slack message with CVE ID, severity, CVSS score, EPSS percentile, and a link to the CVE detail page. Skips silently if nothing new.

⏱ Every 4h # Slack webhook ✓ Only if KEV CVEs exist
Configuration
  • Set SLACK_WEBHOOK variable in n8n Settings → Variables to your Slack incoming webhook URL (Slack → Apps → Incoming Webhooks).
  • The Code node filters only badge === "ACTIVELY EXPLOITED" entries and formats up to 8 CVEs per message.
  • To add a severity filter (e.g. only CRITICAL), add && v.severity === 'CRITICAL' to the filter.
JavaScript — Code node
const vulns = $input.first().json;

const kev = vulns
  .filter(v => v.badge === 'ACTIVELY EXPLOITED')
  .sort((a, b) => (b.score || 0) - (a.score || 0))
  .slice(0, 8);

if (kev.length === 0) return [];

const lines = kev.map(v => {
  const score = v.score != null ? ` CVSS ${v.score.toFixed(1)}` : '';
  const epss  = v.epss_pct != null ? ` · EPSS ${v.epss_pct.toFixed(0)}%ile` : '';
  const poc   = v.poc ? ' · :warning: PoC public' : '';
  return `• ** [${v.severity}${score}${epss}${poc}]\n  ${(v.title || '').slice(0, 110)}`;
});

return [{
  json: {
    count: kev.length,
    text: `:rotating_light: *${kev.length} CVE${kev.length > 1 ? 's' : ''} actively exploited right now* — \n\n` + lines.join('\n\n')
  }
}];
Import JSON — paste into n8n
{
  "name": "vulnfeed — KEV Slack Alert (every 4h)",
  "nodes": [
    {
      "parameters": {
        "rule": { "interval": [{ "field": "hours", "hoursInterval": 4 }] }
      },
      "id": "sch-001", "name": "Every 4 hours",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2, "position": [240, 300]
    },
    {
      "parameters": {
        "url": "https://vulnfeed.it/vulns.json",
        "options": { "response": { "response": { "responseFormat": "json" } } }
      },
      "id": "http-001", "name": "Fetch vulnfeed",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2, "position": [460, 300]
    },
    {
      "parameters": { "jsCode": "const vulns = $input.first().json;\n\nconst kev = vulns\n  .filter(v => v.badge === 'ACTIVELY EXPLOITED')\n  .sort((a, b) => (b.score || 0) - (a.score || 0))\n  .slice(0, 8);\n\nif (kev.length === 0) return [];\n\nconst lines = kev.map(v => {\n  const score = v.score != null ? ` CVSS ${v.score.toFixed(1)}` : '';\n  const epss  = v.epss_pct != null ? ` \u00b7 EPSS ${v.epss_pct.toFixed(0)}%ile` : '';\n  const poc   = v.poc ? ' \u00b7 :warning: PoC public' : '';\n  return `\u2022 ** [${v.severity}${score}${epss}${poc}]\\n  ${(v.title || '').slice(0, 110)}`;\n});\n\nreturn [{\n  json: {\n    count: kev.length,\n    text: `:rotating_light: *${kev.length} CVE${kev.length > 1 ? 's' : ''} actively exploited right now* \u2014 \\n\\n` + lines.join('\\n\\n')\n  }\n}];" },
      "id": "code-001", "name": "Filter KEV CVEs",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2, "position": [680, 300]
    },
    {
      "parameters": {
        "conditions": {
          "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
          "conditions": [{ "leftValue": "={{ $json.count }}", "rightValue": 0, "operator": { "type": "number", "operation": "gt" } }]
        }
      },
      "id": "if-001", "name": "Has results?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2, "position": [900, 300]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $vars.SLACK_WEBHOOK }}",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [{ "name": "text", "value": "={{ $json.text }}" }]
        },
        "options": {}
      },
      "id": "http-002", "name": "Post to Slack",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2, "position": [1120, 220]
    }
  ],
  "connections": {
    "Every 4 hours":  { "main": [[{ "node": "Fetch vulnfeed",  "type": "main", "index": 0 }]] },
    "Fetch vulnfeed": { "main": [[{ "node": "Filter KEV CVEs", "type": "main", "index": 0 }]] },
    "Filter KEV CVEs":{ "main": [[{ "node": "Has results?",    "type": "main", "index": 0 }]] },
    "Has results?":   { "main": [[{ "node": "Post to Slack",   "type": "main", "index": 0 }], []] }
  },
  "active": false,
  "settings": { "executionOrder": "v1" },
  "tags": [{ "name": "vulnfeed" }, { "name": "security" }]
}

Workflow 2 — Weekly email digest

Email Monday 9am HTML digest

Fires every Monday at 9am. Pulls all CVEs published in the last 7 days, sorts by severity and score, and sends a styled HTML email with a table of the top 20. Includes stat summary (total, critical, KEV, PoC) and links to the vulnfeed new-this-week page.

📅 Monday 9am 📧 SMTP / email 🔹 Top 20 by severity
Configuration
  • Add an SMTP credential in n8n (Settings → Credentials → SMTP). Gmail, Sendgrid, SES, Postfix all work.
  • Update fromEmail and toEmail in the Send Email node. Use a comma-separated list for multiple recipients.
  • To filter by product, add && (v.title||'').toLowerCase().includes('nginx') to the filter.
JavaScript — Code node
const vulns = $input.first().json;
const now   = new Date();
const cutoff = new Date(now - 7 * 86400000).toISOString().slice(0, 10);

const fresh = vulns
  .filter(v => (v.published || '').slice(0, 10) >= cutoff)
  .sort((a, b) => {
    const sev = {CRITICAL:0, HIGH:1, MEDIUM:2, LOW:3};
    return (sev[a.severity] ?? 4) - (sev[b.severity] ?? 4) || (b.score || 0) - (a.score || 0);
  });

const nCrit = fresh.filter(v => v.severity === 'CRITICAL').length;
const nKev  = fresh.filter(v => v.badge === 'ACTIVELY EXPLOITED').length;
const nPoc  = fresh.filter(v => v.poc).length;

const rows = fresh.slice(0, 20).map(v => {
  const score = v.score != null ? v.score.toFixed(1) : '—';
  const epss  = v.epss_pct != null ? v.epss_pct.toFixed(0) + '%' : '—';
  const flags = [v.badge === 'ACTIVELY EXPLOITED' ? 'KEV' : '', v.poc ? 'PoC' : ''].filter(Boolean).join(', ');
  return `
    ${v.id}
    ${(v.title || '').slice(0, 90)}
    ${v.severity}
    ${score}
    ${epss}
    ${flags}
  `;
}).join('');

const html = `

vulnfeed Weekly — ${now.toISOString().slice(0,10)}

${fresh.length} new CVEs this week · ${nCrit} critical · ${nKev} actively exploited · ${nPoc} with public PoC

${rows}
CVE Title Sev CVSS EPSS Flags

View full list · Patch now list · vulnfeed.it

`; return [{ json: { subject: \`vulnfeed Weekly: \${fresh.length} CVEs, \${nCrit} critical — \${now.toISOString().slice(0,10)}\`, html, count: fresh.length } }];
Import JSON — paste into n8n
{
  "name": "vulnfeed — Weekly Email Digest (Monday 9am)",
  "nodes": [
    {
      "parameters": {
        "rule": { "interval": [{ "field": "weeks", "weeksInterval": 1, "triggerAtDay": [1], "triggerAtHour": 9, "triggerAtMinute": 0 }] }
      },
      "id": "sch-002", "name": "Monday 9am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2, "position": [240, 300]
    },
    {
      "parameters": { "url": "https://vulnfeed.it/vulns.json", "options": {} },
      "id": "http-003", "name": "Fetch vulnfeed",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2, "position": [460, 300]
    },
    {
      "parameters": { "jsCode": "const vulns = $input.first().json;\nconst now   = new Date();\nconst cutoff = new Date(now - 7 * 86400000).toISOString().slice(0, 10);\n\nconst fresh = vulns\n  .filter(v => (v.published || '').slice(0, 10) >= cutoff)\n  .sort((a, b) => {\n    const sev = {CRITICAL:0, HIGH:1, MEDIUM:2, LOW:3};\n    return (sev[a.severity] ?? 4) - (sev[b.severity] ?? 4) || (b.score || 0) - (a.score || 0);\n  });\n\nconst nCrit = fresh.filter(v => v.severity === 'CRITICAL').length;\nconst nKev  = fresh.filter(v => v.badge === 'ACTIVELY EXPLOITED').length;\nconst nPoc  = fresh.filter(v => v.poc).length;\n\nconst rows = fresh.slice(0, 20).map(v => {\n  const score = v.score != null ? v.score.toFixed(1) : '\u2014';\n  const epss  = v.epss_pct != null ? v.epss_pct.toFixed(0) + '%' : '\u2014';\n  const flags = [v.badge === 'ACTIVELY EXPLOITED' ? 'KEV' : '', v.poc ? 'PoC' : ''].filter(Boolean).join(', ');\n  return `\n    ${v.id}\n    ${(v.title || '').slice(0, 90)}\n    ${v.severity}\n    ${score}\n    ${epss}\n    ${flags}\n  `;\n}).join('');\n\nconst html = `\n

vulnfeed Weekly \u2014 ${now.toISOString().slice(0,10)}

\n

${fresh.length} new CVEs this week · ${nCrit} critical · ${nKev} actively exploited · ${nPoc} with public PoC

\n\n\n \n \n \n \n \n \n\n${rows}\n
CVETitleSevCVSSEPSSFlags
\n

\n View full list ·\n Patch now list ·\n vulnfeed.it\n

\n`;\n\nreturn [{ json: { subject: \\`vulnfeed Weekly: \\${fresh.length} CVEs, \\${nCrit} critical \u2014 \\${now.toISOString().slice(0,10)}\\`, html, count: fresh.length } }];" }, "id": "code-002", "name": "Build digest email", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [680, 300] }, { "parameters": { "fromEmail": "security@yourcompany.com", "toEmail": "team@yourcompany.com", "subject": "={{ $json.subject }}", "emailType": "html", "html": "={{ $json.html }}" }, "id": "mail-001", "name": "Send digest email", "type": "n8n-nodes-base.emailSend", "typeVersion": 2, "position": [900, 300] } ], "connections": { "Monday 9am": { "main": [[{ "node": "Fetch vulnfeed", "type": "main", "index": 0 }]] }, "Fetch vulnfeed": { "main": [[{ "node": "Build digest email", "type": "main", "index": 0 }]] }, "Build digest email": { "main": [[{ "node": "Send digest email","type": "main", "index": 0 }]] } }, "active": false, "settings": { "executionOrder": "v1" }, "tags": [{ "name": "vulnfeed" }, { "name": "security" }] }

Workflow 3 — Jira ticket per critical KEV CVE

Jira Auto-create security tasks

Checks every 4 hours for CRITICAL severity CVEs that are on the CISA KEV list. Creates one Jira issue per new CVE with severity, CVSS, EPSS, description, and a link to the vulnfeed CVE page. Skips CVEs already seen using n8n Variables as a simple state store.

⏱ Every 4h 📋 Jira issue 🔴 CRITICAL + KEV only
Configuration
  • Add a Jira credential in n8n (API token from Atlassian account → Security → API tokens).
  • Change "project": {"value": "SEC"} to your actual Jira project key.
  • Change issue type from Task to Bug or a custom type if your project uses one for security issues.
  • The SEEN_IDS variable prevents duplicate tickets — create it in Settings → Variables with an initial value of []. Add a Set node after Jira creation to update it if needed.
JavaScript — Code node
const vulns  = $input.first().json;
const stored = JSON.parse($vars.SEEN_IDS || '[]');
const seenSet = new Set(stored);

const urgent = vulns.filter(v =>
  v.severity === 'CRITICAL' &&
  v.badge === 'ACTIVELY EXPLOITED' &&
  !seenSet.has(v.id)
);

// Return one item per CVE so the Jira node loops over them
return urgent.map(v => ({
  json: {
    id:    v.id,
    title: v.title || v.id,
    score: v.score,
    epss:  v.epss_pct,
    url:   `https://vulnfeed.it/cve/${v.id}.html`,
    summary: `[${v.id}] ${(v.title || '').slice(0, 100)}`,
    description: `*Severity:* ${v.severity} | *CVSS:* ${v.score ?? '—'} | *EPSS:* ${v.epss_pct != null ? v.epss_pct.toFixed(0) + '%ile' : '—'}\n\n` +
      `*Status:* CISA KEV — actively exploited in the wild${v.poc ? ' · public PoC available' : ''}\n\n` +
      `*Description:* ${(v.description || '').slice(0, 500)}\n\n` +
      `*vulnfeed page:* ${`https://vulnfeed.it/cve/${v.id}.html`}\n` +
      `*NVD:* ${v.url || ''}`
  }
}));
Import JSON — paste into n8n
{
  "name": "vulnfeed — Jira Ticket for Critical KEV CVEs",
  "nodes": [
    {
      "parameters": {
        "rule": { "interval": [{ "field": "hours", "hoursInterval": 4 }] }
      },
      "id": "sch-003", "name": "Every 4 hours",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2, "position": [240, 300]
    },
    {
      "parameters": { "url": "https://vulnfeed.it/vulns.json", "options": {} },
      "id": "http-004", "name": "Fetch vulnfeed",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2, "position": [460, 300]
    },
    {
      "parameters": { "jsCode": "const vulns  = $input.first().json;\nconst stored = JSON.parse($vars.SEEN_IDS || '[]');\nconst seenSet = new Set(stored);\n\nconst urgent = vulns.filter(v =>\n  v.severity === 'CRITICAL' &&\n  v.badge === 'ACTIVELY EXPLOITED' &&\n  !seenSet.has(v.id)\n);\n\n// Return one item per CVE so the Jira node loops over them\nreturn urgent.map(v => ({\n  json: {\n    id:    v.id,\n    title: v.title || v.id,\n    score: v.score,\n    epss:  v.epss_pct,\n    url:   `https://vulnfeed.it/cve/${v.id}.html`,\n    summary: `[${v.id}] ${(v.title || '').slice(0, 100)}`,\n    description: `*Severity:* ${v.severity} | *CVSS:* ${v.score ?? '\u2014'} | *EPSS:* ${v.epss_pct != null ? v.epss_pct.toFixed(0) + '%ile' : '\u2014'}\\n\\n` +\n      `*Status:* CISA KEV \u2014 actively exploited in the wild${v.poc ? ' \u00b7 public PoC available' : ''}\\n\\n` +\n      `*Description:* ${(v.description || '').slice(0, 500)}\\n\\n` +\n      `*vulnfeed page:* ${`https://vulnfeed.it/cve/${v.id}.html`}\\n` +\n      `*NVD:* ${v.url || ''}`\n  }\n}));" },
      "id": "code-003", "name": "Filter new critical KEV",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2, "position": [680, 300]
    },
    {
      "parameters": {
        "conditions": {
          "conditions": [{ "leftValue": "={{ $input.all().length }}", "rightValue": 0, "operator": { "type": "number", "operation": "gt" } }]
        }
      },
      "id": "if-002", "name": "New CVEs?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2, "position": [900, 300]
    },
    {
      "parameters": {
        "resource": "issue",
        "operation": "create",
        "project": { "value": "SEC" },
        "issuetype": { "value": "Task" },
        "summary": "={{ $json.summary }}",
        "additionalFields": {
          "description": "={{ $json.description }}",
          "priority": { "id": "1" },
          "labels": ["cve", "security", "kev"]
        }
      },
      "id": "jira-001", "name": "Create Jira issue",
      "type": "n8n-nodes-base.jira",
      "typeVersion": 1, "position": [1120, 220]
    }
  ],
  "connections": {
    "Every 4 hours":        { "main": [[{ "node": "Fetch vulnfeed",         "type": "main", "index": 0 }]] },
    "Fetch vulnfeed":       { "main": [[{ "node": "Filter new critical KEV","type": "main", "index": 0 }]] },
    "Filter new critical KEV": { "main": [[{ "node": "New CVEs?",           "type": "main", "index": 0 }]] },
    "New CVEs?":            { "main": [[{ "node": "Create Jira issue",      "type": "main", "index": 0 }], []] }
  },
  "active": false,
  "settings": { "executionOrder": "v1" },
  "tags": [{ "name": "vulnfeed" }, { "name": "security" }]
}

Workflow 4 — PagerDuty alert for CVSS 9+ actively exploited

PagerDuty Page on-call for critical exploits

Pages your on-call rotation when a CVE with CVSS ≥9.0 is either actively exploited (CISA KEV) or has a public PoC. Uses PagerDuty's Events API v2 with dedup_key = CVE ID so the same CVE won't fire duplicate alerts. Sends structured payload with severity, EPSS, affected component, and deep link.

⏱ Every 4h 📲 PagerDuty 🔴 CVSS ≥9 + KEV or PoC
Configuration
  • Get your Integration Key from PagerDuty: Services → your service → Integrations → Add integration → Events API v2.
  • Set the PAGERDUTY_KEY variable in n8n Settings → Variables.
  • The dedup_key is set to the CVE ID — PagerDuty will suppress duplicate events for the same CVE until it's acknowledged.
  • Adjust the CVSS threshold (>= 9.0) or severity filter in the Code node to tune alert volume.
JavaScript — Code node
const vulns = $input.first().json;

const critical = vulns
  .filter(v => (v.score || 0) >= 9.0 && (v.badge === 'ACTIVELY EXPLOITED' || v.poc))
  .sort((a, b) => (b.score || 0) - (a.score || 0))
  .slice(0, 5);

return critical.map(v => ({
  json: {
    routing_key: $vars.PAGERDUTY_KEY,
    event_action: 'trigger',
    dedup_key: v.id,
    payload: {
      summary:   `[vulnfeed] ${v.id} — ${v.severity} CVSS ${v.score} ${v.badge === 'ACTIVELY EXPLOITED' ? '(ACTIVELY EXPLOITED)' : '(PoC public)'}`,
      source:    'vulnfeed.it',
      severity:  v.severity === 'CRITICAL' ? 'critical' : 'error',
      component: (v.affected || [])[0] || 'unknown',
      custom_details: {
        cvss:      v.score,
        epss_pct:  v.epss_pct,
        kev:       v.badge === 'ACTIVELY EXPLOITED',
        poc:       !!v.poc,
        title:     (v.title || '').slice(0, 200),
        details:   `https://vulnfeed.it/cve/${v.id}.html`
      }
    },
    links: [{ href: `https://vulnfeed.it/cve/${v.id}.html`, text: 'vulnfeed CVE page' }]
  }
}));
Import JSON — paste into n8n
{
  "name": "vulnfeed — PagerDuty alert for CVSS 9+ exploited",
  "nodes": [
    {
      "parameters": {
        "rule": { "interval": [{ "field": "hours", "hoursInterval": 4 }] }
      },
      "id": "sch-004", "name": "Every 4 hours",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2, "position": [240, 300]
    },
    {
      "parameters": { "url": "https://vulnfeed.it/vulns.json", "options": {} },
      "id": "http-005", "name": "Fetch vulnfeed",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2, "position": [460, 300]
    },
    {
      "parameters": { "jsCode": "const vulns = $input.first().json;\n\nconst critical = vulns\n  .filter(v => (v.score || 0) >= 9.0 && (v.badge === 'ACTIVELY EXPLOITED' || v.poc))\n  .sort((a, b) => (b.score || 0) - (a.score || 0))\n  .slice(0, 5);\n\nreturn critical.map(v => ({\n  json: {\n    routing_key: $vars.PAGERDUTY_KEY,\n    event_action: 'trigger',\n    dedup_key: v.id,\n    payload: {\n      summary:   `[vulnfeed] ${v.id} \u2014 ${v.severity} CVSS ${v.score} ${v.badge === 'ACTIVELY EXPLOITED' ? '(ACTIVELY EXPLOITED)' : '(PoC public)'}`,\n      source:    'vulnfeed.it',\n      severity:  v.severity === 'CRITICAL' ? 'critical' : 'error',\n      component: (v.affected || [])[0] || 'unknown',\n      custom_details: {\n        cvss:      v.score,\n        epss_pct:  v.epss_pct,\n        kev:       v.badge === 'ACTIVELY EXPLOITED',\n        poc:       !!v.poc,\n        title:     (v.title || '').slice(0, 200),\n        details:   `https://vulnfeed.it/cve/${v.id}.html`\n      }\n    },\n    links: [{ href: `https://vulnfeed.it/cve/${v.id}.html`, text: 'vulnfeed CVE page' }]\n  }\n}));" },
      "id": "code-004", "name": "Build PagerDuty payloads",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2, "position": [680, 300]
    },
    {
      "parameters": {
        "conditions": {
          "conditions": [{ "leftValue": "={{ $input.all().length }}", "rightValue": 0, "operator": { "type": "number", "operation": "gt" } }]
        }
      },
      "id": "if-003", "name": "Has alerts?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2, "position": [900, 300]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://events.pagerduty.com/v2/enqueue",
        "sendHeaders": true,
        "headerParameters": { "parameters": [{ "name": "Content-Type", "value": "application/json" }] },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "id": "http-006", "name": "Send to PagerDuty",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2, "position": [1120, 220]
    }
  ],
  "connections": {
    "Every 4 hours":           { "main": [[{ "node": "Fetch vulnfeed",            "type": "main", "index": 0 }]] },
    "Fetch vulnfeed":          { "main": [[{ "node": "Build PagerDuty payloads",  "type": "main", "index": 0 }]] },
    "Build PagerDuty payloads":{ "main": [[{ "node": "Has alerts?",               "type": "main", "index": 0 }]] },
    "Has alerts?":             { "main": [[{ "node": "Send to PagerDuty",         "type": "main", "index": 0 }], []] }
  },
  "active": false,
  "settings": { "executionOrder": "v1" },
  "tags": [{ "name": "vulnfeed" }, { "name": "security" }]
}

💡 Tips & patterns

Filter by product / team ownership
// Add to any Code node filter to match specific products
const MY_PRODUCTS = ['nginx', 'kubernetes', 'openssh', 'postgres', 'redis'];
const relevant = vulns.filter(v =>
  MY_PRODUCTS.some(p =>
    (v.title || '').toLowerCase().includes(p) ||
    (v.affected || []).some(a => a.toLowerCase().includes(p))
  )
);
Deduplicate with n8n Variables (avoid repeat alerts)
EPSS-based filtering (exploitation probability)
Post to Microsoft Teams instead of Slack

vulnfeed JSON API: https://vulnfeed.it/vulns.json — open, no auth, updated every 4h.
More integrations: Grafana & Prometheus · AI agents · API docs