This workflow corresponds to n8n.io template #11007 — we link there as the canonical source.
This workflow follows the Emailsend → Form Trigger recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"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
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
smtptelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This n8n workflow provides comprehensive network vulnerability scanning with automated CVE enrichment and professional report generation. It performs Nmap scans, queries the National Vulnerability Database (NVD) for CVE information, generates detailed HTML/PDF reports, and…
Source: https://n8n.io/workflows/11007/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
[CloudFly] Import Workflows, Credentials. Uses formTrigger, executeCommand, telegram, readWriteFile. Event-driven trigger; 18 nodes.
This n8n workflow automates task creation and scheduled reminders for users via a Telegram bot, ensuring timely notifications across multiple channels like email and Slack. It streamlines task managem
This smart onboarding automation handles new customer signups by:
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.
Uptime Monitor - Multi Service (v3). Uses readWriteFile, telegram. Webhook trigger; 9 nodes.