{
  "name": "My workflow",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 22
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        -288,
        16
      ],
      "id": "aff61066-b43b-4a38-8b9d-dc65d5dfb18d",
      "name": "Schedule Trigger",
      "notesInFlow": true,
      "alwaysOutputData": false
    },
    {
      "parameters": {
        "url": "https://services.nvd.nist.gov/rest/json/cves/2.0",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "pubStartDate",
              "value": "={{$now.minus({days:7}).toISO()}}"
            },
            {
              "name": "pubEndDate",
              "value": "={{$now.toISO()}}"
            },
            {
              "name": "resultsPerPage",
              "value": "20"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        -80,
        16
      ],
      "id": "6ce1225e-b264-4ced-ab94-5c6771a580a9",
      "name": "NVD CVE"
    },
    {
      "parameters": {
        "jsCode": "const input = $input.first();\nconst data = input.json;\nconst vulns = data.vulnerabilities || [];\n\nfunction getEnglishDescription(descriptions) {\n  if (!Array.isArray(descriptions)) return \"\";\n  const en = descriptions.find(d => d.lang === \"en\");\n  if (en && en.value) return en.value;\n  return descriptions.map(d => d.value).join(\" \");\n}\n\nfunction detectStack(text) {\n  const t = String(text || '').toLowerCase();\n  const tags = [];\n\n  // PHP ecosystem\n  if (/(php|laravel|thinkphp|wordpress|joomla|drupal|phpmyadmin|symfony|wpforo|woocommerce|magento)/.test(t)) {\n    tags.push(\"PHP\");\n  }\n\n  // Java ecosystem\n  if (/(java|spring|spring boot|struts|tomcat|jboss|weblogic|fastjson|log4j|jsp|java servlet|jenkins|jackson)/.test(t)) {\n    tags.push(\"Java\");\n  }\n\n  // ASP.NET ecosystem\n  if (/(asp\\.net|aspnet|\\.net|viewstate|iis|sharepoint|exchange|dotnet|c#|sitecore)/.test(t)) {\n    tags.push(\"ASP.NET\");\n  }\n\n  return [...new Set(tags)];\n}\n\nfunction detectVulnType(text) {\n  const t = String(text || '').toLowerCase();\n\n  if (/remote code execution|rce/.test(t)) return \"RCE\";\n  if (/deserialization|deserialize|unserialize/.test(t)) return \"Deserialization\";\n  if (/sql injection/.test(t)) return \"SQL Injection\";\n  if (/command injection/.test(t)) return \"Command Injection\";\n  if (/path traversal|directory traversal/.test(t)) return \"Path Traversal\";\n  if (/file inclusion|local file inclusion|remote file inclusion/.test(t)) return \"File Inclusion\";\n  if (/cross-site scripting|xss/.test(t)) return \"XSS\";\n  if (/server-side request forgery|ssrf/.test(t)) return \"SSRF\";\n  if (/authentication bypass|auth bypass/.test(t)) return \"Auth Bypass\";\n  if (/file upload/.test(t)) return \"File Upload\";\n  if (/template injection/.test(t)) return \"Template Injection\";\n  if (/viewstate/.test(t)) return \"ViewState\";\n  if (/ognl/.test(t)) return \"OGNL Injection\";\n  if (/jndi/.test(t)) return \"JNDI Injection\";\n  if (/xxe|xml external entity/.test(t)) return \"XXE\";\n  if (/open redirect/.test(t)) return \"Open Redirect\";\n  if (/csrf|cross-site request forgery/.test(t)) return \"CSRF\";\n\n  return \"Other\";\n}\n\nfunction getCvss(cve) {\n  return (\n    cve?.metrics?.cvssMetricV31?.[0]?.cvssData?.baseScore ||\n    cve?.metrics?.cvssMetricV30?.[0]?.cvssData?.baseScore ||\n    cve?.metrics?.cvssMetricV2?.[0]?.cvssData?.baseScore ||\n    0\n  );\n}\n\nfunction scoreItem(text, cvss, stack, vulnType) {\n  const t = String(text || '').toLowerCase();\n  let score = 0;\n\n  if (stack.length > 0) score += 2;\n\n  if (/remote code execution|rce/.test(t)) score += 3;\n  if (/deserialization|deserialize|unserialize/.test(t)) score += 2;\n  if (/auth bypass|authentication bypass/.test(t)) score += 2;\n  if (/file upload|template injection|viewstate|ognl|jndi|sql injection|file inclusion|xxe/.test(t)) score += 2;\n\n  if (cvss >= 9) score += 3;\n  else if (cvss >= 7) score += 2;\n  else if (cvss >= 5) score += 1;\n\n  if ([\n    \"RCE\",\n    \"Deserialization\",\n    \"SQL Injection\",\n    \"Command Injection\",\n    \"File Inclusion\",\n    \"Auth Bypass\",\n    \"Template Injection\",\n    \"ViewState\",\n    \"OGNL Injection\",\n    \"JNDI Injection\",\n    \"XXE\"\n  ].includes(vulnType)) {\n    score += 1;\n  }\n\n  return score;\n}\n\nfunction extractReferences(cve) {\n  const refs = cve?.references || [];\n\n  const allRefs = refs\n    .map(r => ({\n      url: r.url || \"\",\n      source: r.source || \"\",\n      tags: r.tags || []\n    }))\n    .filter(r => r.url);\n\n  const pocLinks = allRefs.filter(r => {\n    const u = r.url.toLowerCase();\n    return (\n      u.includes(\"github.com\") ||\n      u.includes(\"exploit-db.com\") ||\n      u.includes(\"packetstormsecurity.com\") ||\n      u.includes(\"0day.today\") ||\n      u.includes(\"gist.github.com\") ||\n      u.includes(\"gitlab.com\")\n    );\n  });\n\n  const advisoryLinks = allRefs.filter(r => {\n    const u = r.url.toLowerCase();\n    return !(\n      u.includes(\"github.com\") ||\n      u.includes(\"exploit-db.com\") ||\n      u.includes(\"packetstormsecurity.com\") ||\n      u.includes(\"0day.today\") ||\n      u.includes(\"gist.github.com\") ||\n      u.includes(\"gitlab.com\")\n    );\n  });\n\n  return {\n    allRefs,\n    pocLinks,\n    advisoryLinks\n  };\n}\n\nconst out = [];\n\nfor (const item of vulns) {\n  const cve = item.cve;\n  if (!cve) continue;\n\n  const description = getEnglishDescription(cve.descriptions || []);\n  const stack = detectStack(description);\n\n  // \u53ea\u4fdd\u7559 PHP / Java / ASP.NET\n  if (stack.length === 0) continue;\n\n  const vulnType = detectVulnType(description);\n  const cvss = getCvss(cve);\n  const score = scoreItem(description, cvss, stack, vulnType);\n\n  const { allRefs, pocLinks, advisoryLinks } = extractReferences(cve);\n\n  out.push({\n    json: {\n      cveId: cve.id || \"\",\n      description,\n      stack,\n      vulnType,\n      cvss,\n      score,\n      published: cve.published || \"\",\n      modified: cve.lastModified || \"\",\n      nvdUrl: cve.id ? `https://nvd.nist.gov/vuln/detail/${cve.id}` : \"\",\n      references: allRefs,\n      advisoryLinks,\n      pocLinks,\n      hasPublicPocCandidate: pocLinks.length > 0\n    }\n  });\n}\n\nreturn out;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        128,
        16
      ],
      "id": "7a106a04-78b0-420e-bda0-53a463ae2012",
      "name": "filter and score"
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.premium}}",
                    "rightValue": "true",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    },
                    "id": "abda42ee-91d8-4a85-a8ec-01f903ef3503"
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "anthropic"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 3
                },
                "conditions": [
                  {
                    "id": "03afc28a-4fe7-4c67-a5af-463779a165f7",
                    "leftValue": "={{$json.premium}}",
                    "rightValue": false,
                    "operator": {
                      "type": "boolean",
                      "operation": "false",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "ollama"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.4,
      "position": [
        1520,
        128
      ],
      "id": "e09dd35b-f9c5-45e3-83f0-aed87bdc522c",
      "name": "Switch"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=\u4f60\u662f\u4e00\u4f4d\u8cc7\u5b89\u6f0f\u6d1e\u5206\u6790\u52a9\u624b\u3002\n\n\u8acb\u6839\u64da\u4ee5\u4e0b\u6f0f\u6d1e\u8cc7\u8a0a\u8f38\u51fa\u300c\u5b78\u7fd2\u7b46\u8a18\u300d\u3002\n\n\u8acb\u7528\u4ee5\u4e0b\u683c\u5f0f\u56de\u7b54\uff1a\n\n\u6f0f\u6d1e\u6458\u8981:\n(\u4e00\u53e5\u8a71)\n\n\u6f0f\u6d1e\u539f\u7406:\n(\u7c21\u77ed\u8aaa\u660e)\n\n\u653b\u64ca\u6d41\u7a0b:\n1.\n2.\n3.\n\n\u53ef\u80fd\u7684 Exploit Payload:\n(\u7c21\u55ae\u4f8b\u5b50)\n\n\u4fee\u88dc\u65b9\u5f0f:\n(\u5982\u4f55\u4fee\u88dc)\n\n\u5b78\u7fd2\u91cd\u9ede:\n(\u5217\u51fa3\u500b\u76f8\u95dc\u6280\u8853)\n\n\u8acb\u4f7f\u7528\u7e41\u9ad4\u4e2d\u6587\u3002\n\n\u6f0f\u6d1e\u8cc7\u8a0a:\n{{$json.description}}",
        "messages": {
          "messageValues": [
            {
              "message": "=\u4f60\u662f\u4e00\u4f4d\u8cc7\u5b89\u6f0f\u6d1e\u5206\u6790\u52a9\u624b\uff0c\u5c08\u9580\u628a CVE \u6574\u7406\u6210\u9069\u5408\u521d\u5b78\u8005\u5b78\u7fd2\u7684\u7b46\u8a18\u3002\n\n\u8acb\u9075\u5b88\u4ee5\u4e0b\u898f\u5247\uff1a\n1. \u4e00\u5f8b\u4f7f\u7528\u7e41\u9ad4\u4e2d\u6587\u3002\n2. \u5167\u5bb9\u8981\u6e05\u695a\u3001\u7c21\u6f54\u3001\u7d50\u69cb\u5316\u3002\n3. \u4e0d\u8981\u63d0\u4f9b\u53ef\u76f4\u63a5\u653b\u64ca\u4ed6\u4eba\u7684\u5177\u9ad4\u5229\u7528\u6b65\u9a5f\u3002\n4. \u53ef\u4ee5\u7528\u9ad8\u5c64\u6b21\u65b9\u5f0f\u8aaa\u660e\u653b\u64ca\u6d41\u7a0b\u8207\u53ef\u80fd\u7684 payload \u985e\u578b\uff0c\u4f46\u4e0d\u8981\u7d66\u5b8c\u6574\u53ef\u76f4\u63a5\u57f7\u884c\u7684 exploit\u3002\n5. \u8acb\u56b4\u683c\u4f9d\u7167\u4f7f\u7528\u8005\u6307\u5b9a\u7684\u683c\u5f0f\u8f38\u51fa\u3002\n6. \u82e5\u8cc7\u8a0a\u4e0d\u8db3\uff0c\u660e\u78ba\u5beb\u51fa\u300c\u8cc7\u8a0a\u4e0d\u8db3\u300d\u3002\n7. \u91cd\u9ede\u653e\u5728\uff1a\u6f0f\u6d1e\u539f\u7406\u3001\u653b\u64ca\u601d\u8def\u3001\u4fee\u88dc\u65b9\u5f0f\u3001\u5b78\u7fd2\u91cd\u9ede\u3002\n\u8acb\u4e0d\u8981\u8f38\u51fa\u683c\u5f0f\u4ee5\u5916\u7684\u5167\u5bb9\u3002"
            }
          ]
        },
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        1760,
        256
      ],
      "id": "5565b49d-f666-435e-bb0f-198b0ec5199b",
      "name": "Basic LLM Chain"
    },
    {
      "parameters": {
        "model": "llama3.1:8b",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "typeVersion": 1,
      "position": [
        1872,
        464
      ],
      "id": "a0f1dadc-ceea-4254-b827-9c55f6d9d905",
      "name": "Ollama Chat Model",
      "credentials": {
        "ollamaApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "claude-sonnet-4-6",
          "mode": "list",
          "cachedResultName": "claude-sonnet-4-6"
        },
        "messages": {
          "values": [
            {
              "content": "=\u4f60\u662f\u4e00\u4f4d\u8cc7\u5b89\u7814\u7a76\u54e1\u8207\u6f0f\u6d1e\u5206\u6790\u5c08\u5bb6\u3002\n\n\u8acb\u5206\u6790\u4ee5\u4e0b\u6f0f\u6d1e\u8cc7\u8a0a\uff0c\u4e26\u7528\u300c\u6f0f\u6d1e\u5b78\u7fd2\u7b46\u8a18\u300d\u65b9\u5f0f\u6574\u7406\u3002\n\n\u8acb\u8f38\u51fa\u4ee5\u4e0b\u6bb5\u843d\uff1a\n\n\u6f0f\u6d1e\u6458\u8981\uff1a\n\u7c21\u55ae\u63cf\u8ff0\u6f0f\u6d1e\u3002\n\n\u6f0f\u6d1e\u539f\u7406\uff1a\n\u89e3\u91cb\u70ba\u4ec0\u9ebc\u6703\u51fa\u73fe\u9019\u500b\u6f0f\u6d1e\u3002\n\n\u653b\u64ca\u6d41\u7a0b\uff1a\n\u653b\u64ca\u8005\u5982\u4f55\u5229\u7528\u9019\u500b\u6f0f\u6d1e\u3002\n\n\u53ef\u80fd\u7684 Exploit Payload\uff1a\n\u8209\u4f8b\u53ef\u80fd\u7684 payload \u6216\u5229\u7528\u65b9\u5f0f\u3002\n\n\u4fee\u88dc\u65b9\u5f0f\uff1a\n\u958b\u767c\u8005\u61c9\u5982\u4f55\u4fee\u88dc\u3002\n\n\u5b78\u7fd2\u91cd\u9ede\uff1a\n\u8aaa\u660e\u9019\u500b\u6f0f\u6d1e\u5c6c\u65bc\u54ea\u4e00\u7a2e\u6f0f\u6d1e\u5bb6\u65cf\uff0c\u4ee5\u53ca\u76f8\u95dc\u6280\u8853\u3002\n\n\u8acb\u4f7f\u7528\u7e41\u9ad4\u4e2d\u6587\u56de\u7b54\u3002\n\n\u6f0f\u6d1e\u8cc7\u8a0a\uff1a\n{{$json.description}}"
            }
          ]
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.anthropic",
      "typeVersion": 1,
      "position": [
        1760,
        48
      ],
      "id": "ceb20c72-b6f6-4966-a7a7-a66bc93c9957",
      "name": "Message a model",
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\n\nitems.sort((a, b) => {\n  return (b.json.score || 0) - (a.json.score || 0);\n});\n\nreturn items.slice(0, 3);"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        336,
        16
      ],
      "id": "0bb34cce-9c6d-491a-ac6f-7af271d227be",
      "name": "Top 3 web CVE"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2528,
        160
      ],
      "id": "b59751ff-2c11-4869-9a26-321ce03fbd9b",
      "name": "Merge"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const j = $json;\nconst packed = $items(\"Pack Original Anthropic\")[$itemIndex]?.json || {};\n\nfunction normalizeStack(stack) {\n  if (Array.isArray(stack)) return stack.join(', ');\n  if (typeof stack === 'string') return stack;\n  return '';\n}\n\nfunction normalizeLinks(links) {\n  if (Array.isArray(links)) {\n    return links\n      .map(x => {\n        if (typeof x === 'string') return x;\n        if (x && typeof x.url === 'string') return x.url;\n        return '';\n      })\n      .filter(Boolean)\n      .join('\\n');\n  }\n  if (typeof links === 'string') return links;\n  return '';\n}\n\nfunction normalizeReferences(refs) {\n  if (!Array.isArray(refs)) return '';\n  return refs\n    .map(r => {\n      if (typeof r === 'string') return r;\n      const url = r?.url || '';\n      const source = r?.source || '';\n      const tags = Array.isArray(r?.tags) ? r.tags.join(', ') : '';\n      return [url, source, tags].filter(Boolean).join(' | ');\n    })\n    .filter(Boolean)\n    .join('\\n');\n}\n\nfunction extractAnthropicSummary(obj) {\n  if (typeof obj.text === 'string' && obj.text.trim()) return obj.text.trim();\n\n  if (Array.isArray(obj.content)) {\n    const joined = obj.content\n      .map(part => {\n        if (typeof part === 'string') return part;\n        if (part && typeof part.text === 'string') return part.text;\n        return '';\n      })\n      .filter(Boolean)\n      .join('\\n');\n    if (joined.trim()) return joined.trim();\n  }\n\n  if (typeof obj.response === 'string' && obj.response.trim()) return obj.response.trim();\n  if (typeof obj.output === 'string' && obj.output.trim()) return obj.output.trim();\n\n  return '';\n}\n\nreturn {\n  cveId: packed.cveId || '',\n  stack: normalizeStack(packed.stack),\n  vulnType: packed.vulnType || '',\n  cvss: packed.cvss || 0,\n  score: packed.score || 0,\n  kev: packed.kev === true,\n  kevVendor: packed.kevVendor || '',\n  kevProduct: packed.kevProduct || '',\n  kevDueDate: packed.kevDueDate || '',\n  kevNotes: packed.kevNotes || '',\n  premium: packed.premium === true,\n  model_used: 'anthropic',\n  summary: extractAnthropicSummary(j),\n  description: packed.description || '',\n  published: packed.published || '',\n  modified: packed.modified || '',\n  nvdUrl: packed.nvdUrl || '',\n  references: normalizeReferences(packed.references),\n  pocLinks: normalizeLinks(packed.pocLinks),\n  githubLinks: normalizeLinks(packed.githubLinks),\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2112,
        48
      ],
      "id": "c4c09a94-ede5-473e-9bc2-633bf9bccc85",
      "name": "Normalize Anthropic"
    },
    {
      "parameters": {
        "url": "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        928,
        -448
      ],
      "id": "7e522e56-2763-4ebf-bbab-d3ee901ead58",
      "name": "Enrich KEV"
    },
    {
      "parameters": {
        "jsCode": "const kevList = $items(\"Enrich KEV\")[0].json.vulnerabilities || [];\nconst topItems = $items(\"Top 3 web CVE\");\n\nreturn topItems.map(item => {\n  const cveId = item.json.cveId;\n  const match = kevList.find(v => v.cveID === cveId);\n\n  return {\n    json: {\n      ...item.json,\n      kev: !!match,\n      kevVendor: match?.vendorProject || \"\",\n      kevProduct: match?.product || \"\",\n      kevDueDate: match?.dueDate || \"\",\n      kevNotes: match?.shortDescription || \"\"\n    }\n  };\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        928,
        -240
      ],
      "id": "5f574406-cccd-4061-bab7-0fcf4e59dca7",
      "name": "Join KEV"
    },
    {
      "parameters": {
        "url": "=https://api.github.com/search/repositories",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "={{$json.cveId}} poc"
            },
            {
              "name": "sort",
              "value": "stars"
            },
            {
              "name": "order",
              "value": "desc"
            },
            {
              "name": "per_page",
              "value": "5"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        928,
        0
      ],
      "id": "a9075909-3fed-4ea6-9553-75105f4f12d9",
      "name": "HTTP Request"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const repos = $json.items || [];\n\nreturn {\n  githubLinks: repos.slice(0, 5).map(r => ({\n    name: r.full_name,\n    url: r.html_url,\n    stars: r.stargazers_count,\n    description: r.description || \"\"\n  }))\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        928,
        288
      ],
      "id": "9d7c7256-13bd-4604-acc4-909b10dab8a3",
      "name": "Extract Github Links"
    },
    {
      "parameters": {
        "jsCode": "const baseItems = $items(\"Join KEV\");\nconst githubItems = $items(\"Extract Github Links\");\n\nreturn baseItems.map((item, index) => {\n  const githubLinks = githubItems[index]?.json?.githubLinks || [];\n\n  return {\n    json: {\n      ...item.json,\n      githubLinks\n    }\n  };\n});"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        928,
        528
      ],
      "id": "d16f2e14-0b17-47f6-8c2b-e4d2d9bc6c50",
      "name": "Join Github"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const j = $json;\nconst githubCount = Array.isArray(j.githubLinks) ? j.githubLinks.length : 0;\nconst stack = Array.isArray(j.stack) ? j.stack : [];\nconst vulnType = j.vulnType || \"\";\n\nlet premium = false;\n\nif ((j.score || 0) >= 7) premium = true;\nif (j.kev === true) premium = true;\nif (githubCount > 0 && (j.score || 0) >= 5) premium = true;\n\n// \u4f60\u7279\u5225\u60f3\u5b78\u7684\u5178\u578b\u6f0f\u6d1e\u985e\u578b\uff0c\u76f4\u63a5\u5347\u7d1a\nif ([\n  \"RCE\",\n  \"Deserialization\",\n  \"SQL Injection\",\n  \"File Inclusion\",\n  \"Auth Bypass\",\n  \"Template Injection\",\n  \"ViewState\",\n  \"OGNL Injection\",\n  \"JNDI Injection\"\n].includes(vulnType)) {\n  premium = true;\n}\n\nreturn {\n  ...j,\n  githubCount,\n  premium\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1264,
        336
      ],
      "id": "77aec9b6-e3c1-4b3b-b289-6971e45cb5ea",
      "name": "Set Premium Route"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const j = $json;\nconst packed = $items(\"Pack Original Ollama\")[$itemIndex]?.json || {};\n\nfunction normalizeStack(stack) {\n  if (Array.isArray(stack)) return stack.join(', ');\n  if (typeof stack === 'string') return stack;\n  return '';\n}\n\nfunction normalizeLinks(links) {\n  if (Array.isArray(links)) {\n    return links\n      .map(x => {\n        if (typeof x === 'string') return x;\n        if (x && typeof x.url === 'string') return x.url;\n        return '';\n      })\n      .filter(Boolean)\n      .join('\\n');\n  }\n  if (typeof links === 'string') return links;\n  return '';\n}\n\nfunction normalizeReferences(refs) {\n  if (!Array.isArray(refs)) return '';\n  return refs\n    .map(r => {\n      if (typeof r === 'string') return r;\n      const url = r?.url || '';\n      const source = r?.source || '';\n      const tags = Array.isArray(r?.tags) ? r.tags.join(', ') : '';\n      return [url, source, tags].filter(Boolean).join(' | ');\n    })\n    .filter(Boolean)\n    .join('\\n');\n}\n\nfunction extractOllamaSummary(obj) {\n  if (typeof obj.text === 'string' && obj.text.trim()) return obj.text.trim();\n  if (typeof obj.response === 'string' && obj.response.trim()) return obj.response.trim();\n  if (typeof obj.output === 'string' && obj.output.trim()) return obj.output.trim();\n  if (obj.message && typeof obj.message.content === 'string' && obj.message.content.trim()) {\n    return obj.message.content.trim();\n  }\n  return '';\n}\n\nreturn {\n  cveId: packed.cveId || '',\n  stack: normalizeStack(packed.stack),\n  vulnType: packed.vulnType || '',\n  cvss: packed.cvss || 0,\n  score: packed.score || 0,\n  kev: packed.kev === true,\n  kevVendor: packed.kevVendor || '',\n  kevProduct: packed.kevProduct || '',\n  kevDueDate: packed.kevDueDate || '',\n  kevNotes: packed.kevNotes || '',\n  premium: packed.premium === true,\n  model_used: 'ollama',\n  summary: extractOllamaSummary(j),\n  description: packed.description || '',\n  published: packed.published || '',\n  modified: packed.modified || '',\n  nvdUrl: packed.nvdUrl || '',\n  references: normalizeReferences(packed.references),\n  pocLinks: normalizeLinks(packed.pocLinks),\n  githubLinks: normalizeLinks(packed.githubLinks),\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2112,
        256
      ],
      "id": "d306b01f-c4fe-4b74-8c41-cea9f64452da",
      "name": "Normalize Ollama"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const j = $json;\n\nfunction safe(v) {\n  if (v === undefined || v === null) return \"\";\n  return v;\n}\n\nfunction dateOnly(d) {\n  if (!d) return \"\";\n  return String(d).split(\"T\")[0];\n}\n\nreturn {\n  title: j.cveId,\n\n  stack: safe(j.stack),\n  vulnType: safe(j.vulnType),\n\n  cvss: j.cvss || 0,\n  score: j.score || 0,\n\n  kev: j.kev === true,\n\n  kevVendor: safe(j.kevVendor),\n  kevProduct: safe(j.kevProduct),\n\n  model: safe(j.model_used),\n\n  summary: safe(j.summary),\n  description: safe(j.description),\n\n  published: dateOnly(j.published),\n  modified: safe(j.modified),\n\n  nvdUrl: safe(j.nvdUrl),\n\n  references: safe(j.references),\n  pocLinks: safe(j.pocLinks),\n  githubLinks: safe(j.githubLinks)\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2768,
        320
      ],
      "id": "bc27d74f-b88f-42b6-b035-3ecb2490ec0e",
      "name": "Prepare Notion"
    },
    {
      "parameters": {
        "jsCode": "const j = $json;\n\nreturn {\n  ...j,\n  originalData: {\n    cveId: j.cveId || '',\n    stack: j.stack || [],\n    vulnType: j.vulnType || '',\n    cvss: j.cvss || 0,\n    score: j.score || 0,\n    kev: j.kev === true,\n    kevVendor: j.kevVendor || '',\n    kevProduct: j.kevProduct || '',\n    kevDueDate: j.kevDueDate || '',\n    kevNotes: j.kevNotes || '',\n    premium: j.premium === true,\n    description: j.description || '',\n    published: j.published || '',\n    modified: j.modified || '',\n    nvdUrl: j.nvdUrl || '',\n    references: j.references || [],\n    pocLinks: j.pocLinks || [],\n    githubLinks: j.githubLinks || []\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1584,
        336
      ],
      "id": "861345e7-2d0c-4d7f-9470-157d0ed6767f",
      "name": "Pack Original Ollama"
    },
    {
      "parameters": {
        "jsCode": "const j = $json;\n\nreturn {\n  ...j,\n  originalData: {\n    cveId: j.cveId || '',\n    stack: j.stack || [],\n    vulnType: j.vulnType || '',\n    cvss: j.cvss || 0,\n    score: j.score || 0,\n    kev: j.kev === true,\n    kevVendor: j.kevVendor || '',\n    kevProduct: j.kevProduct || '',\n    kevDueDate: j.kevDueDate || '',\n    kevNotes: j.kevNotes || '',\n    premium: j.premium === true,\n    description: j.description || '',\n    published: j.published || '',\n    modified: j.modified || '',\n    nvdUrl: j.nvdUrl || '',\n    references: j.references || [],\n    pocLinks: j.pocLinks || [],\n    githubLinks: j.githubLinks || []\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1632,
        -64
      ],
      "id": "9e7ba70e-8bcb-43a4-b2e2-899aa399f4e4",
      "name": "Pack Original Anthropic"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const j = $json;\n\nfunction safe(v) {\n  if (v === undefined || v === null) return \"\";\n  return String(v);\n}\n\nfunction yesNo(v) {\n  return v ? \"Yes\" : \"No\";\n}\n\nfunction section(title, content) {\n  const text = safe(content).trim();\n  if (!text) return \"\";\n  return `## ${title}\\n${text}\\n\\n`;\n}\n\nconst markdown = `# ${safe(j.cveId)}\n\n- **Stack:** ${safe(j.stack)}\n- **Vulnerability Type:** ${safe(j.vulnType)}\n- **CVSS:** ${safe(j.cvss)}\n- **Score:** ${safe(j.score)}\n- **KEV:** ${yesNo(j.kev)}\n- **Model:** ${safe(j.model_used)}\n\n${section(\"AI Summary\", j.summary)}\n${section(\"Technical Description\", j.description)}\n${section(\"NVD\", j.nvdUrl)}\n${section(\"References\", j.references)}\n${section(\"PoC Links\", j.pocLinks)}\n${section(\"GitHub Links\", j.githubLinks)}\n${section(\n  \"KEV Information\",\n  [\n    j.kevVendor ? `Vendor: ${j.kevVendor}` : \"\",\n    j.kevProduct ? `Product: ${j.kevProduct}` : \"\",\n    j.kevDueDate ? `Due Date: ${j.kevDueDate}` : \"\",\n    j.kevNotes ? `Notes: ${j.kevNotes}` : \"\",\n  ].filter(Boolean).join(\"\\n\")\n)}\n${section(\n  \"Timeline\",\n  [\n    j.published ? `Published: ${j.published}` : \"\",\n    j.modified ? `Modified: ${j.modified}` : \"\",\n  ].filter(Boolean).join(\"\\n\")\n)}\n`;\n\nreturn {\n  ...j,\n  markdown\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2768,
        0
      ],
      "id": "dd6f6713-145b-4756-89b9-bbcc8d048d78",
      "name": "Generate Markdown"
    },
    {
      "parameters": {
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "value": "",
          "mode": "id"
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "title|rich_text",
              "textContent": "={{$json.title}}"
            },
            {
              "key": "Stack|rich_text",
              "textContent": "={{$json.stack}}"
            },
            {
              "key": "VulnType|rich_text",
              "textContent": "={{$json.vulnType}}"
            },
            {
              "key": "CVSS|number",
              "numberValue": "={{$json.cvss}}"
            },
            {
              "key": "Score|number",
              "numberValue": "={{$json.score}}"
            },
            {
              "key": "Kev|checkbox",
              "checkboxValue": "={{$json.kev}}"
            },
            {
              "key": "KevVendor|rich_text",
              "textContent": "={{$json.kevVendor}}"
            },
            {
              "key": "KevProduct|rich_text",
              "textContent": "={{$json.kevProduct}}"
            },
            {
              "key": "Model|rich_text",
              "textContent": "={{$json.model}}"
            },
            {
              "key": "Summary|rich_text",
              "textContent": "={{$json.summary}}"
            },
            {
              "key": "Desciption|rich_text",
              "textContent": "={{$json.description}}"
            },
            {
              "key": "Published|rich_text",
              "textContent": "={{$json.published}}"
            },
            {
              "key": "Modified|rich_text",
              "textContent": "={{$json.modified}}"
            },
            {
              "key": "NVDUrl|url",
              "urlValue": "={{$json.nvdUrl}}"
            },
            {
              "key": "References|rich_text",
              "textContent": "={{$json.references}}"
            },
            {
              "key": "PoC Links|rich_text",
              "textContent": "={{$json.pocLinks}}"
            },
            {
              "key": "Github Links|rich_text",
              "textContent": "={{$json.githubLinks}}"
            },
            {
              "key": "Fetch Time|date",
              "date": "={{$now}}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2.2,
      "position": [
        2976,
        320
      ],
      "id": "ba1e41e1-a88c-42e1-a89e-85539e3c2a16",
      "name": "Create a database page",
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const j = $json;\n\nfunction rt(content) {\n  return [\n    {\n      type: \"text\",\n      text: {\n        content: String(content || \"\")\n      }\n    }\n  ];\n}\n\nconst blocks = [];\n\n// Title section\nblocks.push({\n  type: \"heading_1\",\n  heading_1: {\n    rich_text: rt(j.cveId || \"Unknown CVE\")\n  }\n});\n\n// Overview\nblocks.push({\n  type: \"heading_2\",\n  heading_2: {\n    rich_text: rt(\"Overview\")\n  }\n});\n\nblocks.push({\n  type: \"bulleted_list_item\",\n  bulleted_list_item: {\n    rich_text: rt(`Stack: ${j.stack || \"\"}`)\n  }\n});\n\nblocks.push({\n  type: \"bulleted_list_item\",\n  bulleted_list_item: {\n    rich_text: rt(`Vulnerability Type: ${j.vulnType || \"\"}`)\n  }\n});\n\nblocks.push({\n  type: \"bulleted_list_item\",\n  bulleted_list_item: {\n    rich_text: rt(`CVSS: ${j.cvss || \"\"}`)\n  }\n});\n\nblocks.push({\n  type: \"bulleted_list_item\",\n  bulleted_list_item: {\n    rich_text: rt(`Score: ${j.score || \"\"}`)\n  }\n});\n\nblocks.push({\n  type: \"bulleted_list_item\",\n  bulleted_list_item: {\n    rich_text: rt(`KEV: ${j.kev ? \"Yes\" : \"No\"}`)\n  }\n});\n\nblocks.push({\n  type: \"bulleted_list_item\",\n  bulleted_list_item: {\n    rich_text: rt(`Model: ${j.model_used || \"\"}`)\n  }\n});\n\n// AI Summary\nif (j.summary) {\n  blocks.push({\n    type: \"heading_2\",\n    heading_2: {\n      rich_text: rt(\"AI Summary\")\n    }\n  });\n\n  blocks.push({\n    type: \"paragraph\",\n    paragraph: {\n      rich_text: rt(j.summary)\n    }\n  });\n}\n\n// Technical Description\nif (j.description) {\n  blocks.push({\n    type: \"heading_2\",\n    heading_2: {\n      rich_text: rt(\"Technical Description\")\n    }\n  });\n\n  blocks.push({\n    type: \"paragraph\",\n    paragraph: {\n      rich_text: rt(j.description)\n    }\n  });\n}\n\n// References\nif (j.nvdUrl || j.references) {\n  blocks.push({\n    type: \"heading_2\",\n    heading_2: {\n      rich_text: rt(\"References\")\n    }\n  });\n\n  if (j.nvdUrl) {\n    blocks.push({\n      type: \"paragraph\",\n      paragraph: {\n        rich_text: rt(`NVD: ${j.nvdUrl}`)\n      }\n    });\n  }\n\n  if (j.references) {\n    blocks.push({\n      type: \"paragraph\",\n      paragraph: {\n        rich_text: rt(j.references)\n      }\n    });\n  }\n}\n\n// PoC Links\nif (j.pocLinks) {\n  blocks.push({\n    type: \"heading_2\",\n    heading_2: {\n      rich_text: rt(\"PoC Links\")\n    }\n  });\n\n  blocks.push({\n    type: \"paragraph\",\n    paragraph: {\n      rich_text: rt(j.pocLinks)\n    }\n  });\n}\n\n// GitHub Links\nif (j.githubLinks) {\n  blocks.push({\n    type: \"heading_2\",\n    heading_2: {\n      rich_text: rt(\"GitHub Links\")\n    }\n  });\n\n  blocks.push({\n    type: \"paragraph\",\n    paragraph: {\n      rich_text: rt(j.githubLinks)\n    }\n  });\n}\n\n// KEV Information\nif (j.kevVendor || j.kevProduct || j.kevDueDate || j.kevNotes) {\n  blocks.push({\n    type: \"heading_2\",\n    heading_2: {\n      rich_text: rt(\"KEV Information\")\n    }\n  });\n\n  const kevText = [\n    j.kevVendor ? `Vendor: ${j.kevVendor}` : \"\",\n    j.kevProduct ? `Product: ${j.kevProduct}` : \"\",\n    j.kevDueDate ? `Due Date: ${j.kevDueDate}` : \"\",\n    j.kevNotes ? `Notes: ${j.kevNotes}` : \"\"\n  ].filter(Boolean).join(\"\\n\");\n\n  blocks.push({\n    type: \"paragraph\",\n    paragraph: {\n      rich_text: rt(kevText)\n    }\n  });\n}\n\nreturn {\n  ...j,\n  notionBlocks: blocks\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2976,
        0
      ],
      "id": "c81f9d9d-99aa-4291-b2ad-e2ddd3321550",
      "name": "Generate Notion Blocks"
    },
    {
      "parameters": {
        "pageId": {
          "__rl": true,
          "value": "",
          "mode": "url"
        },
        "title": "={{$json.cveId}} \u00b7 {{$json.stack}} \u00b7 {{$json.vulnType}}",
        "blockUi": {
          "blockValues": [
            {
              "type": "heading_1",
              "textContent": "={{$json.cveId}}"
            },
            {
              "textContent": "={{\"Stack: \" + $json.stack + \"\\nType: \" + $json.vulnType + \"\\nCVSS: \" + $json.cvss + \"\\nKEV: \" + ($json.kev ? \"Yes\" : \"No\")}}"
            },
            {
              "type": "heading_2",
              "textContent": "AI Summary"
            },
            {
              "textContent": "={{$json.summary}}"
            },
            {
              "type": "heading_2",
              "textContent": "Technical Description"
            },
            {
              "textContent": "={{$json.description}}"
            },
            {
              "type": "heading_2",
              "textContent": "=References"
            },
            {
              "textContent": "={{\"NVD: \" + $json.nvdUrl + \"\\n\\n\" + ($json.githubLinks || \"\")}}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.notion",
      "typeVersion": 2.2,
      "position": [
        3184,
        0
      ],
      "id": "aef58e73-09e8-45ec-a3bf-95d008e4b16f",
      "name": "Create a page",
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "NVD CVE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "NVD CVE": {
      "main": [
        [
          {
            "node": "filter and score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "filter and score": {
      "main": [
        [
          {
            "node": "Top 3 web CVE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ollama Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Switch": {
      "main": [
        [
          {
            "node": "Pack Original Anthropic",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Pack Original Ollama",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Top 3 web CVE": {
      "main": [
        [
          {
            "node": "Enrich KEV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message a model": {
      "main": [
        [
          {
            "node": "Normalize Anthropic",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Basic LLM Chain": {
      "main": [
        [
          {
            "node": "Normalize Ollama",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Prepare Notion",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Markdown",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Anthropic": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enrich KEV": {
      "main": [
        [
          {
            "node": "Join KEV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Join KEV": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "Extract Github Links",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Github Links": {
      "main": [
        [
          {
            "node": "Join Github",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Join Github": {
      "main": [
        [
          {
            "node": "Set Premium Route",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Premium Route": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Ollama": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Prepare Notion": {
      "main": [
        [
          {
            "node": "Create a database page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pack Original Anthropic": {
      "main": [
        [
          {
            "node": "Message a model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pack Original Ollama": {
      "main": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Markdown": {
      "main": [
        [
          {
            "node": "Generate Notion Blocks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Notion Blocks": {
      "main": [
        [
          {
            "node": "Create a page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  },
  "versionId": "244049e0-b001-47d9-9878-c666cdcff0fa",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "",
  "tags": []
}