CVE-Monitor by ca7ai
中级
这是一个AI Summarization, Multimodal AI领域的自动化工作流,包含 8 个节点。主要使用 Code, Gmail, HttpRequest, ScheduleTrigger, OpenAi 等节点。 将NVD整理的安全CVE摘要与AI优化总结发送到Gmail
前置要求
- •Google 账号和 Gmail API 凭证
- •可能需要目标 API 的认证凭证
- •OpenAI API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "neJ7FcRJ9hJGJd5g",
"meta": {
"instanceId": "146ab8f1ce0cef4bd9c6b91db262ad7cd77638e200455d5838ba053a27e415bc",
"templateCredsSetupCompleted": true
},
"name": "CVE-Monitor by ca7ai",
"tags": [],
"nodes": [
{
"id": "223a6937-0478-4835-83cd-64b59e8068e1",
"name": "发送消息",
"type": "n8n-nodes-base.gmail",
"position": [
1120,
-352
],
"webhookId": "fc455ed7-d565-4df5-ad55-a7b01d2ad953",
"parameters": {
"sendTo": "test@gmail.com",
"message": "={{ $json.message.content.html }}",
"options": {},
"subject": "={{ $json.message.content.subject }}"
},
"credentials": {
"gmailOAuth2": {
"id": "uxofckoDCCCdikjn",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "4a2ed259-8552-46a0-bd97-e439b72ca073",
"name": "HTTP 请求",
"type": "n8n-nodes-base.httpRequest",
"position": [
96,
-352
],
"parameters": {
"url": "=https://services.nvd.nist.gov/rest/json/cves/2.0",
"options": {},
"jsonQuery": "={{ JSON.stringify({\n // pull enough to pick the newest 20; 7 days is safe\n pubStartDate: new Date(Date.now() - 7*24*60*60*1000).toISOString(),\n pubEndDate: new Date().toISOString(),\n resultsPerPage: 200,\n startIndex: 0\n}) }}\n",
"sendQuery": true,
"sendHeaders": true,
"specifyQuery": "json",
"headerParameters": {
"parameters": [
{
"name": "apiKey",
"value": "YOUR API KEY HERE"
},
{
"name": "pubStartDate",
"value": "={{ new Date(Date.now() - 24*60*60*1000).toISOString() }}"
},
{
"name": "pubEndDate",
"value": "={{ new Date().toISOString() }}"
},
{
"name": "resultsPerPage",
"value": "200"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "76e8e8ef-1a8b-452b-8fb7-4d8003b5f005",
"name": "计划触发器",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-128,
-352
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 30
}
]
}
},
"typeVersion": 1.2
},
{
"id": "45da6138-b79e-4303-b11f-353c19f74185",
"name": "解析 NVD",
"type": "n8n-nodes-base.code",
"position": [
320,
-352
],
"parameters": {
"jsCode": "// Parse NVD list → newest top N CVEs with Vendor/Product/Version, severity/CVSS,\n// exploit signal, and brief English summary.\n\nconst LIMIT = 20; // how many to keep\n\nconst vulns = $json.vulnerabilities ?? [];\n\n/* ---------------- helpers ---------------- */\n\nfunction getCvssAndSeverity(cve) {\n const m31 = cve?.metrics?.cvssMetricV31?.[0];\n const m30 = cve?.metrics?.cvssMetricV30?.[0];\n const m2 = cve?.metrics?.cvssMetricV2 ?. [0];\n\n const cvss =\n m31?.cvssData?.baseScore ??\n m30?.cvssData?.baseScore ??\n m2 ?.cvssData?.baseScore ?? null;\n\n const severity =\n m31?.cvssData?.baseSeverity ??\n m30?.cvssData?.baseSeverity ??\n (cvss != null\n ? (cvss >= 9 ? 'CRITICAL' : cvss >= 7 ? 'HIGH' : cvss >= 4 ? 'MEDIUM' : 'LOW')\n : 'UNKNOWN');\n\n const vector =\n m31?.cvssData?.vectorString ??\n m30?.cvssData?.vectorString ??\n m2 ?.cvssData?.vectorString ?? null;\n\n return { cvss, severity, vector };\n}\n\n// Pull vendor/product/version from CPE 2.3 (when available)\nfunction getCpeTriples(cve) {\n const triples = [];\n const seen = new Set();\n\n const walk = (node) => {\n const matches = node?.cpeMatch || node?.cpeMatches || [];\n for (const m of matches) {\n const cpe = m.criteria || m.cpe23Uri || m.cpeName || '';\n // cpe:2.3:<part>:<vendor>:<product>:<version>:...\n const parts = cpe.split(':');\n if (parts.length >= 6) {\n const vendor = (parts[3] || '').toLowerCase();\n const product = (parts[4] || '').toLowerCase();\n const version = (parts[5] && parts[5] !== '*' && parts[5] !== '-') ? parts[5] : null;\n const key = `${vendor}|${product}|${version||''}`;\n if (vendor && product && !seen.has(key)) {\n seen.add(key);\n triples.push({ vendor, product, version });\n }\n }\n }\n (node?.children || node?.nodes || []).forEach(walk);\n };\n\n (cve?.configurations?.nodes || []).forEach(walk);\n return triples;\n}\n\n// Heuristic vendor/product from English summary (no version required)\nfunction vendorProductFromText(desc) {\n const text = String(desc || '');\n\n // Known vendors (extend as needed)\n const vendorCatalog = [\n 'ibm','microsoft','cisco','oracle','red hat','apache','nginx','wordpress',\n 'kapsch trafficcom','hpe','hp','dell','lenovo','sap','vmware','fortinet',\n 'palo alto','juniper','citrix','f5','adobe','atlassian','gitlab','github',\n 'grafana','jetbrains','sonicwall','progress','solarwinds','check point'\n ];\n\n let vendor = null, product = null;\n\n for (const v of vendorCatalog) {\n const re = new RegExp(`\\\\b${v.replace(/\\s+/g,'\\\\s+')}\\\\b`, 'i');\n if (re.test(text)) { vendor = v; break; }\n }\n\n // WordPress plugin: \"<Plugin Name> plugin for WordPress\"\n const mWp = text.match(/([\\w()[\\] .&\\-]{3,80}?)\\s+plugin\\s+for\\s+WordPress/i);\n if (mWp) {\n product = mWp[1].trim();\n vendor = vendor || 'wordpress';\n }\n\n // If we know the vendor, capture product phrase right after it\n if (vendor && !product) {\n const vRe = new RegExp(\n `\\\\b${vendor.replace(/\\s+/g,'\\\\s+')}\\\\b\\\\s+` +\n `([A-Z][\\\\w()&\\\\-]+(?:\\\\s[A-Z][\\\\w()&\\\\-]+){0,5})\\\\s+` +\n `(?:is|are|was|were|contains|includes|allows|vulnerable|prior|before|up|due)`,\n 'i'\n );\n const m = text.match(vRe);\n if (m) product = m[1].trim();\n }\n\n // Generic fallback: first capitalized phrase before a verb\n if (!product) {\n const m2 = text.match(\n /([A-Z][\\w()&\\-]+(?:\\s[A-Z][\\w()&\\-]+){1,5})\\s+(?:is|are|was|were|contains|includes|allows|vulnerable)/);\n if (m2) product = m2[1].trim();\n }\n\n return {\n vendor: (vendor || 'unknown').toLowerCase(),\n product: (product || 'unknown').toLowerCase(),\n };\n}\n\n// Collect version strings from description (e.g., \"v3.2.0\", \"version 4.6.0\")\nfunction collectTextVersions(desc) {\n const text = String(desc || '');\n const out = new Set();\n for (const m of text.matchAll(/\\b(?:v(?:ersion)?\\s*)(\\d[\\dA-Za-z.\\-_]+)\\b/gi)) {\n out.add(m[1]);\n }\n return [...out];\n}\n\n// Detect public exploit presence from references\nfunction exploitInfo(cve) {\n const refs = cve.references ?? [];\n const urls = [];\n for (const r of refs) {\n const tags = (r.tags || []).map(t => String(t).toLowerCase());\n const url = r.url || '';\n const tagHit = tags.includes('exploit') || tags.includes('proof of concept');\n const urlHit = /exploitdb|packetstorm|metasploit|github\\.com\\/.*(poc|exploit)/i.test(url);\n if (tagHit || urlHit) urls.push(url);\n }\n return { hasExploit: urls.length > 0, exploitUrls: urls.slice(0, 3) };\n}\n\n/* ---------------- main ---------------- */\n\n// newest first (published → lastModified)\nvulns.sort((a,b) =>\n Date.parse(b.cve?.published ?? b.cve?.lastModified ?? 0) -\n Date.parse(a.cve?.published ?? a.cve?.lastModified ?? 0)\n);\n\n// take top N\nconst selected = vulns.slice(0, LIMIT);\n\n// map to output items\nreturn selected.map(v => {\n const c = v.cve;\n const { cvss, severity, vector } = getCvssAndSeverity(c);\n\n const desc = (c.descriptions ?? []).find(d => d.lang === 'en')?.value\n ?? (c.descriptions?.[0]?.value ?? '');\n\n const triples = getCpeTriples(c);\n const fromText = vendorProductFromText(desc);\n\n const vendor = (triples[0]?.vendor) || fromText.vendor || 'unknown';\n const product = (triples[0]?.product) || fromText.product || 'unknown';\n\n // versions from CPE + description\n const versions = new Set();\n triples.forEach(t => { if (t.version) versions.add(t.version); });\n collectTextVersions(desc).forEach(vv => versions.add(vv));\n\n const { hasExploit, exploitUrls } = exploitInfo(c);\n const id = c.id;\n const link = `https://nvd.nist.gov/vuln/detail/${id}`;\n\n return {\n json: {\n cveId: id,\n link,\n vendor,\n product,\n versions: Array.from(versions).slice(0, 5), // keep tidy\n severity,\n cvss,\n cvssVector: vector,\n summary: desc.slice(0, 240),\n hasExploit,\n exploitUrls,\n published: c.published ?? null\n }\n };\n});\n"
},
"typeVersion": 2
},
{
"id": "c5b4fb12-ec88-4388-8d0a-a286c3a6d8ee",
"name": "构建摘要",
"type": "n8n-nodes-base.code",
"position": [
544,
-352
],
"parameters": {
"jsCode": "if (!items.length) {\n return [{ json: { send:false, subject:\"CVE Digest: 0 items\", text:\"No CVEs.\", html:\"<p>No CVEs.</p>\" } }];\n}\n\nfunction esc(s){return String(s??'').replace(/[&<>\"']/g,m=>({ '&':'&','<':'<','>':'>','\"':'"',\"'\":''' }[m]));}\n\nconst ord = { CRITICAL:0, HIGH:1, MEDIUM:2, LOW:3, UNKNOWN:4 };\nconst list = items.map(i=>i.json).sort((a,b)=>{\n const sa = ord[(a.severity||'UNKNOWN').toUpperCase()] ?? 9;\n const sb = ord[(b.severity||'UNKNOWN').toUpperCase()] ?? 9;\n return sa - sb || (b.cvss??-1) - (a.cvss??-1);\n});\n\nconst counts = list.reduce((acc,j)=>{\n const s=(j.severity||'UNKNOWN').toUpperCase();\n acc[s]=(acc[s]||0)+1; return acc;\n}, {});\n\nconst subject = `Top ${list.length} Latest CVEs — CRIT:${counts.CRITICAL||0} HIGH:${counts.HIGH||0}`;\n\nconst rows = list.map(j=>{\n const link = j.link ? `<a href=\"${esc(j.link)}\">${esc(j.cveId)}</a>` : esc(j.cveId);\n const versions = (j.versions||[]).join(', ') || '—';\n const exploit = j.hasExploit ? 'Yes' : 'No';\n return `\n <tr>\n <td>${link}</td>\n <td>${esc(j.vendor||'unknown')}</td>\n <td>${esc(j.product||'unknown')}</td>\n <td>${versions}</td>\n <td>${esc(j.severity||'UNKNOWN')}</td>\n <td>${j.cvss!=null?esc(j.cvss):'n/a'}</td>\n <td>${exploit}</td>\n <td>${esc(j.summary||'')}</td>\n </tr>`;\n}).join('');\n\nconst html = `\n <div style=\"font-family:system-ui,Segoe UI,Roboto,Arial,sans-serif\">\n <h3 style=\"margin:0 0 8px 0;\">${esc(subject)}</h3>\n <table border=\"1\" cellpadding=\"6\" cellspacing=\"0\" style=\"border-collapse:collapse;font-size:14px;min-width:900px\">\n <thead>\n <tr style=\"background:#f3f4f6;\">\n <th align=\"left\">CVE</th>\n <th align=\"left\">Vendor</th>\n <th align=\"left\">Product</th>\n <th align=\"left\">Version(s)</th>\n <th align=\"left\">Severity</th>\n <th align=\"left\">CVSS</th>\n <th align=\"left\">Exploit?</th>\n <th align=\"left\">Brief description</th>\n </tr>\n </thead>\n <tbody>${rows}</tbody>\n </table>\n </div>\n`;\n\n// plain text fallback\nconst textLines = list.map(j=>{\n const versions = (j.versions||[]).join(', ') || '—';\n const sc = j.cvss!=null?j.cvss:'n/a';\n const ex = j.hasExploit?'Yes':'No';\n return `• ${j.cveId} | ${j.vendor||'unknown'} | ${j.product||'unknown'} | versions: ${versions} | ${j.severity||'UNKNOWN'} | CVSS ${sc} | Exploit: ${ex}\\n ${j.link}\\n ${j.summary||''}`;\n});\nconst text = [subject, '', ...textLines].join('\\n');\n\nreturn [{ json: { send:true, subject, html, text } }];\n"
},
"typeVersion": 2
},
{
"id": "fc4831af-3d0c-4bfd-a090-3c7155248d7c",
"name": "OpenAI 邮件撰写器",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
768,
-352
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {
"temperature": 0
},
"messages": {
"values": [
{
"content": "=Subject:\n{{$json.subject}}\n\nHTML:\n{{$json.html}}\n\nText:\n{{$json.text}}\n"
},
{
"role": "system",
"content": "=Rewrite this CVE digest email into a concise, professional format.\nReturn ONLY valid JSON: {\"subject\":\"...\",\"html\":\"...\",\"text\":\"...\"}.\n"
}
]
},
"jsonOutput": true
},
"credentials": {
"openAiApi": {
"id": "fYx3byxTCCCmN2m2",
"name": "OpenAi account"
}
},
"typeVersion": 1.8
},
{
"id": "23172815-2e17-43e8-b492-3328a3904d92",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-608,
-496
],
"parameters": {
"color": 5,
"width": 336,
"height": 144,
"content": "## 工作流触发器"
},
"typeVersion": 1
},
{
"id": "20f08eba-82e5-4ef5-98b8-02f168c2a9c7",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-608,
-256
],
"parameters": {
"width": 336,
"height": 144,
"content": "## HTTP 请求"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "52a44b53-cc27-4740-adc2-ca4de10c9d04",
"connections": {
"Parse NVD": {
"main": [
[
{
"node": "Build Digest",
"type": "main",
"index": 0
}
]
]
},
"Build Digest": {
"main": [
[
{
"node": "OpenAI Email Crafter",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "Parse NVD",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Email Crafter": {
"main": [
[
{
"node": "Send a message",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
中级 - AI 摘要总结, 多模态 AI
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
每日商业新闻
使用OpenAI和Gmail从多个RSS源生成每日商业新闻摘要
If
Code
Gmail
+7
15 节点Calistus Christian
市场调研
每日安全新闻
每日技术与网络安全简报:RSS、OpenAI GPT-4o 和 Gmail
If
Code
Gmail
+7
19 节点Calistus Christian
个人效率
AI分类处理的安全中心警报
使用GPT-4.1 Mini分类AWS安全配置错误并向Gmail发送警报
If
Set
Code
+6
12 节点Calistus Christian
安全运维
Mini SOC事件分类
自动化安全事件分类:GPT-4o-mini和Gmail通知
Set
Gmail
Webhook
+2
7 节点Calistus Christian
AI 摘要总结
新闻自动收集器 → Google表格
使用NewsAPI、OpenAI和Google表格收集并总结多语言新闻
If
Set
Code
+6
19 节点Supira Inc.
AI 摘要总结
每日Gmail收件箱摘要转Discord(GPT-4.1-mini + PDF转换)
使用GPT-4.1-mini和PDF转换的每日Gmail收件箱摘要发送到Discord
Code
Gmail
Discord
+8
18 节点moosa
杂项
工作流信息
难度等级
中级
节点数量8
分类2
节点类型6
作者
Calistus Christian
@ca7aiAutomation strategist and AI workflow architect creating intelligent, agent-driven processes that streamline operations. I build advanced n8n solutions integrating AI, cloud services, and real-time data orchestration for scalable impact. Explore my free workflows to get started. Premium, custom-built solutions are also available. Connect with me on LinkedIn for tailored automation expertise.
外部链接
在 n8n.io 查看 →
分享此工作流