8
n8n 中文网amn8n.com

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=>({ '&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;' }[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)可能需要您自行付费。

工作流信息
难度等级
中级
节点数量8
分类2
节点类型6
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

作者
Calistus Christian

Calistus Christian

@ca7ai

Automation 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 查看

分享此工作流