{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "130cf806-eb72-40f6-9bcb-48cbb27ce486",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -640,
        -336
      ],
      "parameters": {
        "path": "e1e1da83-efe3-4f81-8428-09f31ad3531a",
        "options": {
          "responseData": "Process started!"
        },
        "httpMethod": "POST"
      },
      "typeVersion": 2
    },
    {
      "id": "6586f22d-fce5-49e4-ac61-c1da19c62938",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -976,
        240
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "5cd295a5-26b1-471b-bdac-0cae05a4ea86",
      "name": "1. Set Parameters",
      "type": "n8n-nodes-base.set",
      "notes": "Configure scan parameters",
      "position": [
        -336,
        -48
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "network",
              "type": "string",
              "value": "={{ $json.network }}{{ $json.body.network }}"
            },
            {
              "id": "2",
              "name": "timestamp",
              "type": "string",
              "value": "={{ $now.format('yyyyMMdd_HHmmss') }}"
            },
            {
              "id": "0a15fc2e-83fa-4f10-bfc4-5031a2b3381c",
              "name": "report_password",
              "type": "string",
              "value": "Almafa123456"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "2b694d9e-943d-446b-a03b-fbc42b7f8fc4",
      "name": "2. Execute Nmap Scan",
      "type": "n8n-nodes-base.executeCommand",
      "notes": "Run nmap version + script scan",
      "position": [
        352,
        -48
      ],
      "parameters": {
        "command": "=installdependencies=$(apk add util-linux-misc ncurses nmap nmap-scripts)\nrunnmapscan=$(script -q -c \"nmap -sV -sC -p- -sS {{ $json.timing }} {{ $json.setter }}{{ $json.setter }}{{ $json['min-parallelism'] }} {{ $json.setter }}{{ $json.setter }}{{ $json['max-parallelism'] }} {{ $json.setter }}{{ $json.setter }}{{ $json['max-retries'] }} -oX /tmp/nmap_{{ $('1. Set Parameters').item.json.timestamp }}.xml {{ $('1. Set Parameters').item.json.network }}\" /dev/null)\ncorrectxml=$(sed -i 's/type=\"user\"/type=\"USER\"/g' /tmp/nmap_{{ $('1. Set Parameters').item.json.timestamp }}.xml)\nconvertxmltojson=$(\n/tmp/nmap-helper/target/release/nmap-helper convert /tmp/nmap_{{ $('1. Set Parameters').item.json.timestamp }}.xml -o /tmp/nmap_{{ $('1. Set Parameters').item.json.timestamp }}.json)\ntput reset\ncat /tmp/nmap_{{ $('1. Set Parameters').item.json.timestamp }}.json"
      },
      "typeVersion": 1
    },
    {
      "id": "13896a62-80d9-4228-857a-6f81f782157d",
      "name": "4. Save Scan Data to File",
      "type": "n8n-nodes-base.readWriteFile",
      "notes": "Persist parsed scan data",
      "position": [
        912,
        -48
      ],
      "parameters": {
        "options": {},
        "fileName": "=/tmp/scan_data_{{ $('1. Set Parameters').item.json.timestamp }}.json",
        "operation": "write",
        "dataPropertyName": "=data"
      },
      "typeVersion": 1
    },
    {
      "id": "419ce6bb-f96a-4081-b4a8-0f4337650131",
      "name": "5. CVE Enrichment Loop",
      "type": "n8n-nodes-base.code",
      "notes": "Query NVD CVE API for each service with rate limiting",
      "position": [
        1216,
        -48
      ],
      "parameters": {
        "jsCode": "// CVE ENRICHMENT - Query NVD API for each service\nconst services = $input.first().json.services;\nconst enriched = [];\n\nconsole.log('=== CVE ENRICHMENT START ===');\nconsole.log(`Processing ${services.length} services`);\n\nfor (let i = 0; i < services.length; i++) {\n  const service = services[i];\n  const base = {\n    ip: service.ip,\n    port: service.port,\n    protocol: service.protocol,\n    service: service.service,\n    product: service.product,\n    version: service.version,\n    cpe: service.cpe,\n    serviceKey: service.serviceKey\n  };\n\n  // --- OVERRIDE: nginx vendor fix ---\nif (base.product && base.product.toLowerCase() === 'nginx' && base.cpe) {\n  base.cpe = base.cpe.replace('igor_sysoev', 'nginx');\n}\n\n  console.log(`[${i+1}/${services.length}] ${base.ip}:${base.port} ${base.service} ${base.product} ${base.version}`);\n\n  // Check if CPE exists\n  if (!base.cpe || base.cpe === '' || base.cpe === ':') {\n    console.log('  \u2192 No CPE, skipping CVE lookup');\n    enriched.push({\n      ...base,\n      cveId: 'N/A',\n      cveSummary: 'No CPE available for vulnerability lookup',\n      cvssScore: 0,\n      severity: 'INFO',\n      publishedDate: 'N/A',\n      references: []\n    });\n    continue;\n  }\n\n  console.log(`  \u2192 CPE: ${base.cpe}`);\n\n  try {\n    // Query NVD CVE API\n    const apiUrl = `https://services.nvd.nist.gov/rest/json/cves/2.0?cpeName=${encodeURIComponent(base.cpe)}&resultsPerPage=10&isVulnerable`;\n    console.log('  \u2192 Querying NVD API...');\n\nconst response = await this.helpers.httpRequest({\n  method: 'GET',\n  url: apiUrl,\n  timeout: 30000,\n  headers: {\n    'User-Agent': 'n8n-vulnerability-scanner/1.0'\n  }\n});\n\n\n    const vulnerabilities = response.vulnerabilities || [];\n    console.log(`  \u2192 Found ${vulnerabilities.length} CVEs`);\n\n    if (vulnerabilities.length === 0) {\n      enriched.push({\n        ...base,\n        cveId: 'N/A',\n        cveSummary: 'No known vulnerabilities found in NVD database',\n        cvssScore: 0,\n        severity: 'INFO',\n        publishedDate: 'N/A',\n        references: []\n      });\n    } else {\n      // Process each CVE\n      for (const vuln of vulnerabilities) {\n        const cve = vuln.cve || {};\n        const cveId = cve.id || 'UNKNOWN';\n\n        // Get English description\n        const descriptions = cve.descriptions || [];\n        const cveSummary = descriptions.find(d => d.lang === 'en')?.value || 'No description available';\n\n        // Get CVSS metrics\n        const metrics = cve.metrics || {};\n        const cvssV31 = metrics.cvssMetricV31?.[0]?.cvssData;\n        const cvssV30 = metrics.cvssMetricV30?.[0]?.cvssData;\n        const cvssData = cvssV31 || cvssV30 || {};\n\n        const cvssScore = cvssData.baseScore || 0;\n        const severity = cvssData.baseSeverity || 'UNKNOWN';\n\n        // Get published date\n        const publishedDate = cve.published || 'N/A';\n\n        // Get references (limit to 3)\n        const references = (cve.references || []).slice(0, 3).map(ref => ref.url);\n\n        enriched.push({\n          ...base,\n          cveId,\n          cveSummary,\n          cvssScore,\n          severity,\n          publishedDate,\n          references\n        });\n\n        console.log(`    - ${cveId} (${severity}, CVSS: ${cvssScore})`);\n      }\n    }\n\n    // Rate limiting: 1 seconds between requests\n    if (i < services.length - 1) {\n      console.log('  \u2192 Waiting 1s (rate limit)...');\n      await new Promise(resolve => setTimeout(resolve, 1000));\n    }\n\n  } catch (error) {\n    console.log(`  \u2717 API ERROR: ${error.message}`);\n    enriched.push({\n      ...base,\n      cveId: 'N/A',\n      cveSummary: `API Error: ${error.message}`,\n      cvssScore: 0,\n      severity: 'ERROR',\n      publishedDate: 'N/A',\n      references: []\n    });\n  }\n}\n\nconsole.log(`=== CVE ENRICHMENT COMPLETE: ${enriched.length} results ===`);\n// return enriched.map(item => ({json: item}));\n\n\n// JSON string\nconst outputContent = JSON.stringify(enriched, null, 2);\n\n// Buffer\nconst buffer = Buffer.from(outputContent, 'utf-8');\n\n// Return \nreturn [{\n  json: { enriched: enriched },\n  binary: {\n    data: {\n      data: buffer.toString('base64'),\n      mimeType: 'application/json',\n      fileName: 'enriched.json'\n    }\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "ba2b5863-0124-41bf-a7ba-ddad989c1991",
      "name": "6. Save Enriched Data",
      "type": "n8n-nodes-base.readWriteFile",
      "notes": "Persist CVE-enriched data",
      "position": [
        1520,
        -48
      ],
      "parameters": {
        "options": {},
        "fileName": "=/tmp/enriched_data_{{ $('1. Set Parameters').item.json.timestamp }}.json",
        "operation": "write",
        "dataPropertyName": "=data"
      },
      "typeVersion": 1
    },
    {
      "id": "f1536f6c-e932-442d-81af-1a53a18ee3fc",
      "name": "7. Aggregate All Data",
      "type": "n8n-nodes-base.aggregate",
      "notes": "Collect all enriched items",
      "position": [
        1824,
        -48
      ],
      "parameters": {
        "include": "specifiedFields",
        "options": {},
        "aggregate": "aggregateAllItemData",
        "fieldsToInclude": "enriched, services"
      },
      "typeVersion": 1
    },
    {
      "id": "40b24046-c02f-4088-a1e3-83c2bac88f3f",
      "name": "8. Prepare Report Structure",
      "type": "n8n-nodes-base.code",
      "notes": "Group by IP, calculate statistics",
      "position": [
        2112,
        -48
      ],
      "parameters": {
        "jsCode": "// Prepare report data structure - FIXED for nested array structure\nconst input = $input.first().json;\nconst network = $('1. Set Parameters').first().json.network;\n\nconsole.log('=== PREPARING REPORT ===');\nconsole.log('Input type:', typeof input);\nconsole.log('Input keys:', Object.keys(input));\n\n// FIXED: Handle triple-nested structure [{ data: [{ enriched: [...] }] }]\nlet allData = [];\n\n// Step 1: Extract from data array\nif (input.data && Array.isArray(input.data)) {\n  console.log('Found data array, length:', input.data.length);\n\n  // Step 2: data is an array, get first element\n  if (input.data.length > 0) {\n    const dataFirstItem = input.data[0];\n    console.log('First data item keys:', Object.keys(dataFirstItem));\n\n    // Step 3: Extract enriched array\n    if (dataFirstItem.enriched && Array.isArray(dataFirstItem.enriched)) {\n      allData = dataFirstItem.enriched;\n      console.log('\u2713 Found enriched array in data[0].enriched, length:', allData.length);\n    } else {\n      console.log('ERROR: No enriched array in data[0]');\n    }\n  }\n} else {\n  console.log('ERROR: No data array found');\n}\n\n// Fallback: check other possible structures\nif (allData.length === 0) {\n  console.log('Trying fallback extraction methods...');\n\n  if (input.enriched && Array.isArray(input.enriched)) {\n    allData = input.enriched;\n    console.log('Found in input.enriched');\n  } else if (Array.isArray(input)) {\n    allData = input;\n    console.log('Input itself is array');\n  }\n}\n\nconsole.log(`Processing ${allData.length} enriched items`);\n\nif (allData.length === 0) {\n  console.log('ERROR: No data found! Full input:', JSON.stringify(input, null, 2));\n}\n\n// Group data by IP address\nconst hostsByIp = {};\n\nfor (const item of allData) {\n  console.log('Processing item:', item.ip, item.port, item.service);\n\n  const ip = item.ip;\n  const port = item.port;\n\n  // Skip undefined values\n  if (!ip || !port) {\n    console.log('Skipping item with undefined ip or port');\n    continue;\n  }\n\n  // Initialize host if not exists\n  if (!hostsByIp[ip]) {\n    hostsByIp[ip] = {\n      ip: ip,\n      hostname: ip,\n      services: {}\n    };\n    console.log(`Initialized host: ${ip}`);\n  }\n\n  // Initialize service/port if not exists\n  if (!hostsByIp[ip].services[port]) {\n    hostsByIp[ip].services[port] = {\n      port: port,\n      protocol: item.protocol,\n      service: item.service,\n      product: item.product,\n      version: item.version,\n      cpe: item.cpe,\n      vulnerabilities: []\n    };\n    console.log(`  Added service: ${port}/${item.protocol} ${item.service}`);\n  }\n\n  // Add vulnerability if CVE exists\n  if (item.cveId && item.cveId !== 'N/A') {\n    hostsByIp[ip].services[port].vulnerabilities.push({\n      cveId: item.cveId,\n      cveSummary: item.cveSummary,\n      cvssScore: item.cvssScore,\n      severity: item.severity,\n      publishedDate: item.publishedDate,\n      references: item.references\n    });\n    console.log(`    Added CVE: ${item.cveId} (${item.severity})`);\n  }\n}\n\nconsole.log('Hosts found:', Object.keys(hostsByIp));\n\n// Calculate statistics\nlet totalVulnerabilities = 0;\nlet criticalCount = 0;\nlet highCount = 0;\nlet mediumCount = 0;\nlet lowCount = 0;\nlet infoCount = 0;\nlet totalServices = 0;\n\nfor (const ip in hostsByIp) {\n  for (const port in hostsByIp[ip].services) {\n    totalServices++;\n    const vulns = hostsByIp[ip].services[port].vulnerabilities;\n    totalVulnerabilities += vulns.length;\n\n    for (const vuln of vulns) {\n      const severity = (vuln.severity || 'INFO').toLowerCase();\n      if (severity === 'critical') criticalCount++;\n      else if (severity === 'high') highCount++;\n      else if (severity === 'medium') mediumCount++;\n      else if (severity === 'low') lowCount++;\n      else infoCount++;\n    }\n  }\n}\n\nconst statistics = {\n  totalHosts: Object.keys(hostsByIp).length,\n  totalServices: totalServices,\n  totalVulnerabilities: totalVulnerabilities,\n  criticalCount: criticalCount,\n  highCount: highCount,\n  mediumCount: mediumCount,\n  lowCount: lowCount,\n  infoCount: infoCount,\n  scanDate: new Date().toISOString().split('T')[0],\n  scanTime: new Date().toISOString().split('T')[1].split('.')[0]\n};\n\nconsole.log(`\u2713 Statistics: ${statistics.totalHosts} hosts, ${statistics.totalServices} services, ${statistics.totalVulnerabilities} vulnerabilities`);\nconsole.log(`  - Critical: ${criticalCount}, High: ${highCount}, Medium: ${mediumCount}, Low: ${lowCount}, Info: ${infoCount}`);\n\nconst report = {\n  metadata: {\n    title: 'Network Infrastructure Vulnerability Assessment Report',\n    subtitle: 'Comprehensive Security Analysis',\n    version: '1.0.0',\n    scanDate: statistics.scanDate,\n    scanTime: statistics.scanTime,\n    scanNetwork: network\n  },\n  statistics: statistics,\n  executiveSummary: `This vulnerability assessment analyzed the network ${network}. ` +\n    `The scan identified ${statistics.totalHosts} active hosts with ${statistics.totalServices} exposed services. ` +\n    `A total of ${statistics.totalVulnerabilities} vulnerabilities were discovered, ` +\n    `including ${statistics.criticalCount} critical, ${statistics.highCount} high, ` +\n    `${statistics.mediumCount} medium, ${statistics.lowCount} low severity issues, ` +\n    `and ${statistics.infoCount} informational findings.`,\n  hostData: hostsByIp\n};\n\nconsole.log('\u2713 Report structure prepared successfully');\nconsole.log('Host IPs:', Object.keys(hostsByIp));\n\nreturn [{json: report}];"
      },
      "typeVersion": 2
    },
    {
      "id": "2b475881-2fb9-4a29-aa57-1de5ef91a194",
      "name": "9. Generate HTML Report",
      "type": "n8n-nodes-base.code",
      "notes": "Professional HTML report with styling",
      "position": [
        2416,
        -48
      ],
      "parameters": {
        "jsCode": "// Generate Professional HTML Report\nconst report = items[0].json;\nconst meta = report.metadata;\nconst stats = report.statistics;\nconst hosts = report.hostData;\n\nconsole.log('=== GENERATING HTML REPORT ===');\n\n// Helper functions\nfunction severityColor(severity) {\n  const colors = {\n    'critical': '#c62828',\n    'high': '#f57c00',\n    'medium': '#fbc02d',\n    'low': '#388e3c',\n    'info': '#1976d2',\n    'error': '#9e9e9e',\n    'unknown': '#757575'\n  };\n  return colors[(severity || 'info').toLowerCase()] || '#757575';\n}\n\nfunction severityBadge(severity) {\n  const color = severityColor(severity);\n  return `<span class=\"severity-badge\" style=\"background-color:${color}\">${(severity || 'INFO').toUpperCase()}</span>`;\n}\n\nfunction generateServiceTable(host) {\n  let html = '<table class=\"service-table\"><thead><tr>';\n  html += '<th>Port</th><th>Protocol</th><th>Service</th><th>Product</th><th>Version</th><th>CVE Count</th><th>Max Severity</th>';\n  html += '</tr></thead><tbody>';\n\n  for (const portKey in host.services) {\n    const svc = host.services[portKey];\n    const vulnCount = svc.vulnerabilities.length;\n\n    let maxSeverity = 'INFO';\n    if (vulnCount > 0) {\n      const severities = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO'];\n      for (const sev of severities) {\n        if (svc.vulnerabilities.some(v => (v.severity || '').toUpperCase() === sev)) {\n          maxSeverity = sev;\n          break;\n        }\n      }\n    }\n\n    html += '<tr>';\n    html += `<td><strong>${svc.port}</strong></td>`;\n    html += `<td>${svc.protocol}</td>`;\n    html += `<td>${svc.service}</td>`;\n    html += `<td>${svc.product || '-'}</td>`;\n    html += `<td>${svc.version || '-'}</td>`;\n    html += `<td>${vulnCount}</td>`;\n    html += `<td>${severityBadge(maxSeverity)}</td>`;\n    html += '</tr>';\n  }\n\n  html += '</tbody></table>';\n  return html;\n}\n\nfunction generateVulnerabilityDetails(host) {\n  let html = '';\n\n  for (const portKey in host.services) {\n    const svc = host.services[portKey];\n\n    html += `<div class=\"port-section\">`;\n    html += `<h4 class=\"port-title\">Port ${svc.port}/${svc.protocol} - ${svc.service}</h4>`;\n    html += `<div class=\"service-info\">`;\n    html += `<strong>Product:</strong> ${svc.product || 'Unknown'} `;\n    html += `<strong>Version:</strong> ${svc.version || 'Unknown'}`;\n    if (svc.cpe) {\n      html += `<br><strong>CPE:</strong> <code>${svc.cpe}</code>`;\n    }\n    html += `</div>`;\n\n    if (svc.vulnerabilities.length === 0) {\n      html += `<div class=\"no-vuln\">No known vulnerabilities found for this service.</div>`;\n    } else {\n      html += `<div class=\"vulnerabilities\">`;\n      for (const vuln of svc.vulnerabilities) {\n        html += `<div class=\"vulnerability-card\">`;\n        html += `<div class=\"vuln-header\">`;\n        html += `<span class=\"cve-id\">${vuln.cveId}</span>`;\n        html += severityBadge(vuln.severity);\n        html += `<span class=\"cvss-score\">CVSS: ${vuln.cvssScore}</span>`;\n        html += `</div>`;\n        html += `<div class=\"vuln-published\">Published: ${vuln.publishedDate}</div>`;\n        html += `<div class=\"vuln-description\">${vuln.cveSummary}</div>`;\n        if (vuln.references && vuln.references.length > 0) {\n          html += `<div class=\"vuln-references\"><strong>References:</strong><ul>`;\n          for (const ref of vuln.references) {\n            html += `<li><a href=\"${ref}\" target=\"_blank\">${ref}</a></li>`;\n          }\n          html += `</ul></div>`;\n        }\n        html += `</div>`;\n      }\n      html += `</div>`;\n    }\n\n    html += `</div>`;\n  }\n\n  return html;\n}\n\n// Build HTML\nlet html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>${meta.title}</title>\n  <style>\n    * { margin: 0; padding: 0; box-sizing: border-box; }\n    body {\n      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n      line-height: 1.6;\n      color: #333;\n      background-color: #f5f5f5;\n    }\n    .container {\n      max-width: 1200px;\n      margin: 0 auto;\n      padding: 20px;\n      background-color: white;\n      box-shadow: 0 0 20px rgba(0,0,0,0.1);\n    }\n    .header {\n      background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);\n      color: white;\n      padding: 40px 30px;\n      margin: -20px -20px 30px -20px;\n    }\n    .header h1 {\n      font-size: 28px;\n      margin-bottom: 10px;\n    }\n    .header .subtitle {\n      font-size: 16px;\n      opacity: 0.9;\n    }\n    .meta-info {\n      background-color: #f9f9f9;\n      border-left: 4px solid #1976d2;\n      padding: 15px 20px;\n      margin-bottom: 30px;\n    }\n    .meta-info div {\n      margin: 5px 0;\n    }\n    h2 {\n      color: #1976d2;\n      font-size: 24px;\n      margin: 40px 0 20px 0;\n      padding-bottom: 10px;\n      border-bottom: 2px solid #e0e0e0;\n    }\n    h3 {\n      color: #424242;\n      font-size: 20px;\n      margin: 30px 0 15px 0;\n    }\n    h4 {\n      color: #616161;\n      font-size: 18px;\n      margin: 20px 0 10px 0;\n    }\n    .stats-grid {\n      display: grid;\n      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n      gap: 20px;\n      margin: 20px 0;\n    }\n    .stat-card {\n      background-color: #f9f9f9;\n      padding: 20px;\n      border-radius: 8px;\n      border-left: 4px solid #1976d2;\n    }\n    .stat-card .label {\n      font-size: 14px;\n      color: #757575;\n      margin-bottom: 8px;\n    }\n    .stat-card .value {\n      font-size: 32px;\n      font-weight: bold;\n      color: #1976d2;\n    }\n    .severity-breakdown {\n      display: flex;\n      gap: 15px;\n      margin: 20px 0;\n      flex-wrap: wrap;\n    }\n    .severity-item {\n      flex: 1;\n      min-width: 120px;\n      padding: 15px;\n      background-color: #f9f9f9;\n      border-radius: 6px;\n      text-align: center;\n    }\n    .severity-item .count {\n      font-size: 28px;\n      font-weight: bold;\n      margin-bottom: 5px;\n    }\n    .severity-item .label {\n      font-size: 12px;\n      text-transform: uppercase;\n      font-weight: 600;\n    }\n    .severity-badge {\n      display: inline-block;\n      padding: 4px 12px;\n      border-radius: 4px;\n      color: white;\n      font-size: 12px;\n      font-weight: 600;\n      text-transform: uppercase;\n    }\n    .service-table {\n      width: 100%;\n      border-collapse: collapse;\n      margin: 20px 0;\n      background-color: white;\n      box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n    }\n    .service-table th,\n    .service-table td {\n      padding: 12px;\n      text-align: left;\n      border-bottom: 1px solid #e0e0e0;\n    }\n    .service-table th {\n      background-color: #f5f5f5;\n      font-weight: 600;\n      color: #424242;\n      text-transform: uppercase;\n      font-size: 12px;\n    }\n    .service-table tr:hover {\n      background-color: #fafafa;\n    }\n    .port-section {\n      margin: 30px 0;\n      padding: 20px;\n      background-color: #fafafa;\n      border-radius: 8px;\n    }\n    .port-title {\n      color: #1976d2;\n      margin-bottom: 10px;\n    }\n    .service-info {\n      margin: 10px 0;\n      padding: 10px;\n      background-color: white;\n      border-radius: 4px;\n      font-size: 14px;\n    }\n    .service-info code {\n      background-color: #f5f5f5;\n      padding: 2px 6px;\n      border-radius: 3px;\n      font-size: 12px;\n    }\n    .no-vuln {\n      margin: 15px 0;\n      padding: 15px;\n      background-color: #e8f5e9;\n      color: #388e3c;\n      border-radius: 4px;\n      border-left: 4px solid #4caf50;\n    }\n    .vulnerabilities {\n      margin: 15px 0;\n    }\n    .vulnerability-card {\n      margin: 15px 0;\n      padding: 20px;\n      background-color: white;\n      border-radius: 6px;\n      border-left: 4px solid #f57c00;\n      box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n    }\n    .vuln-header {\n      display: flex;\n      gap: 15px;\n      align-items: center;\n      margin-bottom: 10px;\n    }\n    .cve-id {\n      font-size: 18px;\n      font-weight: bold;\n      color: #1976d2;\n    }\n    .cvss-score {\n      font-size: 14px;\n      font-weight: 600;\n      color: #757575;\n    }\n    .vuln-published {\n      font-size: 13px;\n      color: #757575;\n      margin-bottom: 10px;\n    }\n    .vuln-description {\n      margin: 15px 0;\n      line-height: 1.7;\n    }\n    .vuln-references {\n      margin-top: 15px;\n      font-size: 14px;\n    }\n    .vuln-references ul {\n      margin: 10px 0 0 20px;\n    }\n    .vuln-references a {\n      color: #1976d2;\n      text-decoration: none;\n      word-break: break-all;\n    }\n    .vuln-references a:hover {\n      text-decoration: underline;\n    }\n    .executive-summary {\n      background-color: #e3f2fd;\n      padding: 20px;\n      border-radius: 6px;\n      border-left: 4px solid #1976d2;\n      margin: 20px 0;\n      line-height: 1.8;\n    }\n    .recommendations {\n      background-color: #fff3e0;\n      padding: 20px;\n      border-radius: 6px;\n      border-left: 4px solid #f57c00;\n      margin: 20px 0;\n    }\n    .recommendations ul {\n      margin: 15px 0 0 20px;\n      line-height: 2;\n    }\n    @media print {\n      .container {\n        box-shadow: none;\n      }\n      .header {\n        background: #1976d2;\n        -webkit-print-color-adjust: exact;\n        print-color-adjust: exact;\n      }\n    }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <div class=\"header\">\n      <h1>${meta.title}</h1>\n      <div class=\"subtitle\">${meta.subtitle}</div>\n    </div>\n\n    <div class=\"meta-info\">\n      <div><strong>Target Network:</strong> ${meta.scanNetwork}</div>\n      <div><strong>Scan Date:</strong> ${meta.scanDate} ${meta.scanTime}</div>\n      <div><strong>Report Version:</strong> ${meta.version}</div>\n    </div>\n\n    <h2>Executive Summary</h2>\n    <div class=\"executive-summary\">\n      ${report.executiveSummary}\n    </div>\n\n    <h2>Overall Statistics</h2>\n    <div class=\"stats-grid\">\n      <div class=\"stat-card\">\n        <div class=\"label\">Total Hosts</div>\n        <div class=\"value\">${stats.totalHosts}</div>\n      </div>\n      <div class=\"stat-card\">\n        <div class=\"label\">Total Services</div>\n        <div class=\"value\">${stats.totalServices}</div>\n      </div>\n      <div class=\"stat-card\">\n        <div class=\"label\">Total Vulnerabilities</div>\n        <div class=\"value\">${stats.totalVulnerabilities}</div>\n      </div>\n    </div>\n\n    <h3>Vulnerability Breakdown by Severity</h3>\n    <div class=\"severity-breakdown\">\n      <div class=\"severity-item\" style=\"border-top: 4px solid ${severityColor('critical')}\">\n        <div class=\"count\" style=\"color: ${severityColor('critical')}\">${stats.criticalCount}</div>\n        <div class=\"label\" style=\"color: ${severityColor('critical')}\">Critical</div>\n      </div>\n      <div class=\"severity-item\" style=\"border-top: 4px solid ${severityColor('high')}\">\n        <div class=\"count\" style=\"color: ${severityColor('high')}\">${stats.highCount}</div>\n        <div class=\"label\" style=\"color: ${severityColor('high')}\">High</div>\n      </div>\n      <div class=\"severity-item\" style=\"border-top: 4px solid ${severityColor('medium')}\">\n        <div class=\"count\" style=\"color: ${severityColor('medium')}\">${stats.mediumCount}</div>\n        <div class=\"label\" style=\"color: ${severityColor('medium')}\">Medium</div>\n      </div>\n      <div class=\"severity-item\" style=\"border-top: 4px solid ${severityColor('low')}\">\n        <div class=\"count\" style=\"color: ${severityColor('low')}\">${stats.lowCount}</div>\n        <div class=\"label\" style=\"color: ${severityColor('low')}\">Low</div>\n      </div>\n      <div class=\"severity-item\" style=\"border-top: 4px solid ${severityColor('info')}\">\n        <div class=\"count\" style=\"color: ${severityColor('info')}\">${stats.infoCount}</div>\n        <div class=\"label\" style=\"color: ${severityColor('info')}\">Info</div>\n      </div>\n    </div>\n\n    <h2>Detailed Findings by Host</h2>`;\n\n// Generate per-host sections\nfor (const ip in hosts) {\n  const host = hosts[ip];\n  html += `\n    <h3>Host: ${host.ip}</h3>\n    <h4>Discovered Services</h4>\n    ${generateServiceTable(host)}\n    <h4>Vulnerability Details</h4>\n    ${generateVulnerabilityDetails(host)}\n  `;\n}\n\nhtml += `\n    <h2>Recommendations</h2>\n    <div class=\"recommendations\">\n      <p><strong>Immediate Actions:</strong></p>\n      <ul>\n        <li>Prioritize patching of CRITICAL and HIGH severity vulnerabilities</li>\n        <li>Review and update all services showing outdated versions</li>\n        <li>Disable or restrict access to unnecessary services</li>\n        <li>Implement network segmentation to limit exposure</li>\n      </ul>\n      <p><strong>Long-term Improvements:</strong></p>\n      <ul>\n        <li>Establish regular vulnerability scanning schedule (weekly/monthly)</li>\n        <li>Implement automated patch management processes</li>\n        <li>Deploy intrusion detection/prevention systems (IDS/IPS)</li>\n        <li>Conduct security awareness training for staff</li>\n        <li>Maintain asset inventory and track software versions</li>\n      </ul>\n    </div>\n  </div>\n</body>\n</html>`;\n\nconsole.log('HTML report generated successfully');\n\n// Return as both JSON and binary\nconst buffer = Buffer.from(html, 'utf-8');\nreturn [{\n  json: { html: html },\n  binary: {\n    data: {\n      data: buffer.toString('base64'),\n      mimeType: 'text/html',\n      fileName: 'vulnerability_report.html'\n    }\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "bc0271d6-751b-4ce0-984c-0d38a13802ae",
      "name": "10. Save Report to File",
      "type": "n8n-nodes-base.readWriteFile",
      "notes": "Write HTML report to /tmp",
      "position": [
        2720,
        -48
      ],
      "parameters": {
        "options": {},
        "fileName": "=/tmp/vulnerability_report_{{ $('1. Set Parameters').item.json.timestamp }}.html",
        "operation": "write",
        "dataPropertyName": "=data"
      },
      "typeVersion": 1
    },
    {
      "id": "1eee0642-57ec-4846-8354-fc37f5b8a988",
      "name": "11. Read Report for Output",
      "type": "n8n-nodes-base.readWriteFile",
      "notes": "Return report file",
      "position": [
        3024,
        -48
      ],
      "parameters": {
        "options": {},
        "fileSelector": "=/tmp/vulnerability_report_{{ $('1. Set Parameters').item.json.timestamp }}.html"
      },
      "typeVersion": 1
    },
    {
      "id": "f447bad9-718e-4ad4-bd43-1cca0a5fad57",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1168,
        -224
      ],
      "parameters": {
        "color": 5,
        "width": 816,
        "height": 128,
        "content": "## Get CVE datas\n**CPE Search** : https://nvd.nist.gov/products/cpe/search\n**CVE Search** : https://services.nvd.nist.gov/rest/json/cves/2.0?cpeName={CPE Tag}&resultsPerPage=10&isVulnerable"
      },
      "typeVersion": 1
    },
    {
      "id": "552fec50-fb88-4214-9536-97d5b6b95212",
      "name": "12. Convert the Report to PDF from HTML",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        3280,
        -48
      ],
      "parameters": {
        "command": "=apk add --no-cache wget tar giflib libwebp libwebpdemux libavif lcms2 fontconfig\ncd /tmp\nwget https://www.princexml.com/download/prince-16.1-alpine3.20-x86_64.tar.gz\ntar -xf prince-16.1-alpine3.20-x86_64.tar.gz\n\n/tmp/prince-16.1-alpine3.20-x86_64/lib/prince/bin/prince /tmp/vulnerability_report_{{ $('1. Set Parameters').item.json.timestamp }}.html -o /tmp/vulnerability_report_{{ $('1. Set Parameters').item.json.timestamp }}.pdf --media=print --encrypt --user-password={{ $('1. Set Parameters').item.json.report_password }} --css-dpi=128\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "3adf22b0-cb68-4065-8ef4-482491881275",
      "name": "13. Read Report PDF for Output",
      "type": "n8n-nodes-base.readWriteFile",
      "position": [
        3504,
        -48
      ],
      "parameters": {
        "options": {
          "dataPropertyName": "=vulnerability_report"
        },
        "fileSelector": "=/tmp/vulnerability_report_{{ $('1. Set Parameters').item.json.timestamp }}.pdf"
      },
      "typeVersion": 1
    },
    {
      "id": "23c3aee7-d384-4a95-9369-3649a3c7b49d",
      "name": "On form submission",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -640,
        240
      ],
      "parameters": {
        "options": {
          "path": "target"
        },
        "formTitle": "Add scan parameters",
        "formFields": {
          "values": [
            {
              "fieldLabel": "network",
              "placeholder": "Add target ip,ip-range,fqdn",
              "requiredField": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "9a2a64ea-a996-4ead-b5cf-5de8218bd40f",
      "name": "Add NMAP-HELPER",
      "type": "n8n-nodes-base.executeCommand",
      "position": [
        -112,
        -48
      ],
      "parameters": {
        "command": "=apk add --no-cache git make rust cargo\ncd /tmp\ngit clone https://github.com/net-shaper/nmap-helper.git\ncd nmap-helper\nmake\nmake install\n"
      },
      "typeVersion": 1
    },
    {
      "id": "2c0b7dcc-176a-4c72-a228-496078fb4a07",
      "name": "Pre-Set-Target",
      "type": "n8n-nodes-base.set",
      "position": [
        -640,
        -48
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "5767d8e7-5746-4713-8a22-9b2cea985269",
              "name": "network",
              "type": "string",
              "value": "scanme.nmap.org"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "fc2c83f8-f167-47be-9700-9cc018d70cb3",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -976,
        -336
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 1
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "08f20486-655b-4cf0-a287-e643df9ec96d",
      "name": "3. Parse NMAP JSON to Services",
      "type": "n8n-nodes-base.code",
      "notes": "Extract all services with details",
      "position": [
        624,
        -48
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json;\n\nconst nmapJson = raw.stdout ? JSON.parse(raw.stdout) : raw;\n\nlet services = [];\n\nconst hosts = nmapJson.host || [];\nfor (const host of hosts) {\n  const ip = host.address?.[0]?.addr || 'unknown';\n  const ports = host.ports?.port || [];\n\n  for (const portObj of ports) {\n    if (portObj.state?.state !== 'open') continue;\n\n    const serviceObj = portObj.service || {};\n    services.push({\n      ip,\n      port: portObj.portid,\n      protocol: portObj.protocol,\n      service: serviceObj.name || 'unknown',\n      product: serviceObj.product || '',\n      version: serviceObj.version || '',\n      cpe: Array.isArray(serviceObj.cpe) && serviceObj.cpe.length > 0\n        ? serviceObj.cpe[0].replace(/^cpe:\\//, 'cpe:2.3:') + ':'\n        : '',\n      serviceKey: `${ip}:${portObj.portid}`\n    });\n  }\n}\n\nconst outputContent = JSON.stringify(services, null, 2);\nconst buffer = Buffer.from(outputContent, 'utf-8');\n\nreturn [{\n  json: { services: services },\n  binary: {\n    data: {\n      data: buffer.toString('base64'),\n      mimeType: 'application/json',\n      fileName: 'services.json'\n    }\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "ca7d013a-e407-46bf-8bd1-c5b0256a477a",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3264,
        -192
      ],
      "parameters": {
        "color": 5,
        "width": 352,
        "height": 96,
        "content": "## HTML to PDF\nhttps://www.princexml.com/doc/intro-userguide/"
      },
      "typeVersion": 1
    },
    {
      "id": "1564f1df-b1d4-4683-aad1-4bb2a77f16c1",
      "name": "14/a. Send Report in Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        3856,
        -336
      ],
      "parameters": {
        "chatId": "=-123456789012",
        "operation": "sendDocument",
        "binaryData": true,
        "additionalFields": {},
        "binaryPropertyName": "=vulnerability_report"
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "2dca7335-376a-4f2d-ab0f-4566366904a7",
      "name": "14/b. Send Report in Email with SMTP (example POSTFIX)",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        3856,
        256
      ],
      "parameters": {
        "text": "=The requested Automatic NMAP Scan finished, we attached the Report for '{{ $('1. Set Parameters').item.json.network }}'",
        "options": {
          "attachments": "=vulnerability_report",
          "allowUnauthorizedCerts": true
        },
        "subject": "=Automatic NMAP Report for '{{ $('1. Set Parameters').item.json.network }}'",
        "toEmail": "=report.receiver@example.com",
        "fromEmail": "=report.creator@example.com"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "813b1c02-20d6-455e-abbc-863f60d8acb2",
      "name": "Optional Params Setter",
      "type": "n8n-nodes-base.set",
      "position": [
        112,
        -48
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "993bd434-83d3-4c09-b9c9-d80ed1273802",
              "name": "setter",
              "type": "string",
              "value": "-"
            },
            {
              "id": "cd15bd15-9e15-4cd4-852b-fa3ab0ccbdf8",
              "name": "max-retries",
              "type": "string",
              "value": "max-retries 2"
            },
            {
              "id": "7906c4b3-05e6-4660-a22f-0a5ac056d42e",
              "name": "min-parallelism",
              "type": "string",
              "value": "min-parallelism 40"
            },
            {
              "id": "65ece4fd-13bd-4b94-b281-f2d1d5109e03",
              "name": "max-parallelism",
              "type": "string",
              "value": "max-parallelism 250"
            },
            {
              "id": "4c963160-4085-49ce-baaa-209f8b76ba81",
              "name": "timing",
              "type": "string",
              "value": "-T4"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "218eb838-e1db-4014-a99e-9ebdf415788b",
      "name": "Sticky Note - Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1792,
        -288
      ],
      "parameters": {
        "width": 480,
        "height": 568,
        "content": "## How it works\n\nThis workflow scans network targets using Nmap, enriches findings with CVE data from the NVD API, and generates password-protected PDF reports. It detects services, matches them to CPE identifiers, queries vulnerabilities with CVSS scores, and outputs professional reports via Telegram and email.\n\n## Setup steps\n\n1. **Triggers**: Choose webhook (API), form (web UI), manual, or scheduled execution\n2. **Configure targets**: Edit 'Pre-Set-Target' node for manual/scheduled runs, or submit via webhook/form\n3. **Set report password**: Update 'report_password' in '1. Set Parameters' node\n4. **Add credentials**: Configure Telegram bot token and SMTP settings in n8n credentials\n5. **Update recipients**: Set Telegram chat ID and email addresses in distribution nodes (14/a and 14/b)\n6. **Dependencies**: First run auto-installs nmap-helper; PrinceXML downloads on PDF conversion\n\nNVD API queries are rate-limited (1 second between requests). Scan duration depends on target size."
      },
      "typeVersion": 1
    },
    {
      "id": "50ec880a-44ee-489e-a390-6fdf224fa619",
      "name": "Sticky Note - Triggers",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1200,
        -80
      ],
      "parameters": {
        "color": 4,
        "width": 400,
        "height": 144,
        "content": "## Triggers\n\nFour ways to start scans: webhook API for automation, web form for users, manual button for testing, or scheduled cron for routine scans."
      },
      "typeVersion": 1
    },
    {
      "id": "4dfbda37-2e01-4b63-9914-e5f3c22057cb",
      "name": "Sticky Note - Dependency",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        -320
      ],
      "parameters": {
        "color": 5,
        "width": 392,
        "height": 220,
        "content": "## Dependency Check\n\nVerifies nmap-helper installation (converts Nmap XML to JSON). First run installs from GitHub; subsequent runs skip this step.\n\nhttps://github.com/net-shaper/nmap-helper\n\nhttps://crates.io/crates/nmap-helper"
      },
      "typeVersion": 1
    },
    {
      "id": "8afde198-d3c2-46b2-bc67-df6745e23a52",
      "name": "Sticky Note - Scanning",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 360,
        "height": 136,
        "content": "## Nmap Scanning\n\nExecutes full port scan with service/version detection. Configure timing and parallelism in 'Optional Params Setter' node."
      },
      "typeVersion": 1
    },
    {
      "id": "2b0abc7e-ae05-4ff4-b9e6-09ce4ca8df22",
      "name": "Sticky Note - CVE",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1200,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 420,
        "height": 144,
        "content": "## CVE Enrichment\n\nQueries NVD API for each detected service using CPE identifiers. Returns CVE IDs, CVSS scores, severity levels, and references. Rate-limited to 1 req/sec."
      },
      "typeVersion": 1
    },
    {
      "id": "22463e33-b06b-4ac4-8db9-c4f5c71a90b5",
      "name": "Sticky Note - Report Gen",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2432,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 360,
        "height": 140,
        "content": "## Report Generation\n\nGroups findings by host, calculates statistics, and generates HTML report with severity breakdown and detailed vulnerability info."
      },
      "typeVersion": 1
    },
    {
      "id": "ff4d8b95-d992-4b1c-97ca-598f329febcf",
      "name": "Sticky Note - PDF",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3280,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 136,
        "content": "## PDF Conversion\n\nUses PrinceXML to convert HTML report to password-protected PDF. Password set in '1. Set Parameters' node."
      },
      "typeVersion": 1
    },
    {
      "id": "eff61d08-4360-446d-b57b-71634c8d09d0",
      "name": "Sticky Note - Distribution",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3840,
        -48
      ],
      "parameters": {
        "color": 4,
        "width": 320,
        "height": 136,
        "content": "## Distribution\n\nSends PDF reports via Telegram and email. Update credentials and recipients in these nodes before first use."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "1. Set Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Pre-Set-Target",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pre-Set-Target": {
      "main": [
        [
          {
            "node": "1. Set Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add NMAP-HELPER": {
      "main": [
        [
          {
            "node": "Optional Params Setter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Pre-Set-Target",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Set Parameters": {
      "main": [
        [
          {
            "node": "Add NMAP-HELPER",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On form submission": {
      "main": [
        [
          {
            "node": "1. Set Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Execute Nmap Scan": {
      "main": [
        [
          {
            "node": "3. Parse NMAP JSON to Services",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. Save Enriched Data": {
      "main": [
        [
          {
            "node": "7. Aggregate All Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7. Aggregate All Data": {
      "main": [
        [
          {
            "node": "8. Prepare Report Structure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. CVE Enrichment Loop": {
      "main": [
        [
          {
            "node": "6. Save Enriched Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Optional Params Setter": {
      "main": [
        [
          {
            "node": "2. Execute Nmap Scan",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "10. Save Report to File": {
      "main": [
        [
          {
            "node": "11. Read Report for Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9. Generate HTML Report": {
      "main": [
        [
          {
            "node": "10. Save Report to File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Save Scan Data to File": {
      "main": [
        [
          {
            "node": "5. CVE Enrichment Loop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "11. Read Report for Output": {
      "main": [
        [
          {
            "node": "12. Convert the Report to PDF from HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8. Prepare Report Structure": {
      "main": [
        [
          {
            "node": "9. Generate HTML Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "13. Read Report PDF for Output": {
      "main": [
        [
          {
            "node": "14/a. Send Report in Telegram",
            "type": "main",
            "index": 0
          },
          {
            "node": "14/b. Send Report in Email with SMTP (example POSTFIX)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. Parse NMAP JSON to Services": {
      "main": [
        [
          {
            "node": "4. Save Scan Data to File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12. Convert the Report to PDF from HTML": {
      "main": [
        [
          {
            "node": "13. Read Report PDF for Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}