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 →
{
"name": "workflowV3",
"nodes": [
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-768,
96
],
"id": "9fde1db2-e92b-4d59-af1f-0eb6c04a854a",
"name": "When clicking \u2018Execute workflow\u2019"
},
{
"parameters": {
"dataPropertyName": "stdout",
"options": {}
},
"type": "n8n-nodes-base.xml",
"typeVersion": 1,
"position": [
-400,
96
],
"id": "4fd42d5a-995c-4aa5-a0fd-8d91f491449a",
"name": "XML"
},
{
"parameters": {
"jsCode": "const { execSync } = require('child_process');\n\n// 1. Get the previous output (Task Creation Result)\nconst prevOutput = items[0].json.stdout;\n\n// 2. Extract the Task ID using Regex\nconst match = prevOutput.match(/id=\"([^\"]+)\"/);\nconst taskId = match ? match[1] : null;\n\nif (!taskId) {\n throw new Error(\"Could not find Task ID in previous output\");\n}\n\n// 3. Construct Command\nconst xml = `<start_task task_id='${taskId}'/>`;\nconst cmd = `/home/node/gvm-env/bin/gvm-cli --gmp-username admin --gmp-password admin123 socket --socketpath /run/gvmd/gvmd.sock --xml \"${xml}\"`;\n\ntry {\n const stdout = execSync(cmd).toString();\n return [{ json: { stdout } }];\n} catch (error) {\n throw new Error(error.message);\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
624,
0
],
"id": "d6c4ea46-fe82-4e91-a373-113d0a07a2cd",
"name": "StartTask"
},
{
"parameters": {
"jsCode": "const { execSync } = require('child_process');\n\n// 1. Get the previous output (Target Creation Result)\nconst prevOutput = items[0].json.stdout;\n\n// 2. Extract the Target ID using Regex\nconst match = prevOutput.match(/id=\"([^\"]+)\"/);\nconst targetId = match ? match[1] : null;\n\nif (!targetId) {\n throw new Error(\"Could not find Target ID in previous output\");\n}\n\n// 3. Get the IP Name from the earlier 'Code' node\nconst ipObjetivo = $('Code').first().json.ip_objetivo;\n\n// 4. Construct Command\nconst xml = `<create_task><name>Scan ${ipObjetivo}</name><config id='daba56c8-73ec-11df-a475-002264764cea'/><target id='${targetId}'/><scanner id='08b69003-5fc2-4037-a479-93b440211c73'/></create_task>`;\nconst cmd = `/home/node/gvm-env/bin/python3 -m gvmtools.cli --gmp-username admin --gmp-password admin123 socket --socketpath /run/gvmd/gvmd.sock --xml \"${xml}\"`;\n\ntry {\n const stdout = execSync(cmd).toString();\n return [{ json: { stdout } }];\n} catch (error) {\n throw new Error(error.message);\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
400,
0
],
"id": "8f397ef1-3157-4bab-951f-9f530c8e260d",
"name": "CreateTask"
},
{
"parameters": {
"jsCode": "const { execSync } = require('child_process');\n\n// 1. Get data from previous node\nconst hosts = items[0].json.all_ips;\nconst count = items[0].json.cantidad_hosts;\nconst now = new Date().toISOString();\n\n// 2. Construct Command\nconst xml = `<create_target><name>Target Masivo (${count} hosts) - ${now}</name><hosts>${hosts}</hosts><port_list id='33d0cd82-57c6-11e1-8ed1-406186ea4fc5'/></create_target>`;\nconst cmd = `/home/node/gvm-env/bin/python3 -m gvmtools.cli --gmp-username admin --gmp-password admin123 socket --socketpath /run/gvmd/gvmd.sock --xml \"${xml}\"`;\n\ntry {\n const stdout = execSync(cmd).toString();\n return [{ json: { stdout } }];\n} catch (error) {\n throw new Error(error.message);\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
208,
0
],
"id": "2842783d-e19b-4b72-acae-59a25ebfebca",
"name": "CreateTarget"
},
{
"parameters": {
"jsCode": "// 1. Capturamos la salida de Nmap\nconst nmapData = items[0].json.nmaprun;\n\n// 2. CONFIGURACI\u00d3N: Lista Negra (IPs que NO queremos escanear)\nconst ipsIgnoradas = [\"192.168.122.1\", \"192.168.122.2\", \"192.168.122.79\"];\n\n// --- VERIFICACI\u00d3N INICIAL ---\nif (!nmapData || !nmapData.host) {\n return [{\n json: { all_ips: \"\", ip_objetivo: \"\", cantidad_hosts: 0, encontrados: false, error: \"Nmap no devolvi\u00f3 datos\" }\n }];\n}\n\n// 3. Normalizamos los hosts\nlet hostsArray = nmapData.host;\nif (!Array.isArray(hostsArray)) {\n hostsArray = [hostsArray];\n}\n\nconst ipsValidas = [];\n\n// 4. Procesamos y filtramos\nfor (const host of hostsArray) {\n // Solo si la m\u00e1quina responde ('up')\n if (host.status && host.status.state === 'up') {\n let ip = \"\";\n if (Array.isArray(host.address)) {\n const ipv4 = host.address.find(addr => addr.addrtype === 'ipv4');\n ip = ipv4 ? ipv4.addr : host.address[0].addr;\n } else {\n ip = host.address.addr;\n }\n\n // \ud83d\udd25 FILTRO DE EXCLUSI\u00d3N \ud83d\udd25\n if (ip && !ipsIgnoradas.includes(ip)) {\n ipsValidas.push(ip);\n }\n }\n}\n\n// 5. RESPUESTA FINAL\nif (ipsValidas.length === 0) {\n return [{\n json: {\n all_ips: \"\",\n ip_objetivo: \"\",\n cantidad_hosts: 0,\n encontrados: false,\n mensaje: \"No se encontraron hosts nuevos (todos fueron ignorados o no responden).\"\n }\n }];\n}\n\nconst listaFinal = ipsValidas.join(\", \");\n\nreturn [{\n json: {\n all_ips: listaFinal, \n ip_objetivo: listaFinal, \n cantidad_hosts: ipsValidas.length,\n encontrados: true\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
0,
0
],
"id": "1a36633e-ea1b-4709-b384-8177042ee1be",
"name": "Code"
},
{
"parameters": {
"jsCode": "const { execSync } = require('child_process');\n\n// Command updated for your KVM Network (192.168.122.0/24)\nconst cmd = 'nmap -sn -n -oX - 192.168.122.0/24';\n\ntry {\n const stdout = execSync(cmd).toString();\n return [{ json: { stdout } }];\n} catch (error) {\n throw new Error(error.message);\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-560,
96
],
"id": "389c8de6-0d18-47ac-8ad1-e2c087f459d4",
"name": "Nmap"
},
{
"parameters": {
"chatId": "8381107402",
"text": "=\ud83d\udee1\ufe0f Reporte de Red Unificado\n\n\ud83d\udccb Hosts: {{ $json.email_body.match(/Hosts Escaneados:<\\/strong> ([^<]+)/)[1] }}\n\ud83d\udcc5 Fecha: {{ $json.email_body.match(/Fecha:<\\/strong> ([^<]+)/)[1] }}\n\ud83d\udcca Total Hallazgos: {{ $json.email_body.match(/Total Hallazgos:<\\/strong> ([^<]+)/)[1] }}\n\n\ud83d\udd34 Cr\u00edtica: {{ $json.email_body.match(/color: red[^>]+>(\\d+)/)[1] }}\n\ud83d\udfe0 Alta: {{ $json.email_body.match(/color: orange[^>]+>(\\d+)/)[1] }}\n\ud83d\udfe1 Media: {{ $json.email_body.match(/color: #d4a017[^>]+>(\\d+)/)[1] }}\n\ud83d\udfe2 Baja: {{ $json.email_body.match(/color: green[^>]+>(\\d+)/)[1] }}\n\nThis message was sent automatically with n8n",
"additionalFields": {}
},
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1920,
-32
],
"id": "bea1260e-4f35-4356-8ed1-7b438317008f",
"name": "Send a text message1",
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const { execSync } = require('child_process');\n\n// 1. Get the previous output (Start Task Result)\n// This output contains the <report_id> we need to download the results.\nconst prevOutput = items[0].json.stdout;\n\n// 2. Extract Report ID using Regex\nconst match = prevOutput.match(/<report_id>([^<]+)<\\/report_id>/);\nconst reportId = match ? match[1] : null;\n\nif (!reportId) {\n throw new Error(\"Could not find Report ID in StartTask output\");\n}\n\n// 3. Construct Command to get full report\nconst xml = `<get_reports report_id='${reportId}' format_id='a994b278-1f62-11e1-96ac-406186ea4fc5' details='1'/>`;\nconst cmd = `/home/node/gvm-env/bin/gvm-cli --gmp-username admin --gmp-password admin123 socket --socketpath /run/gvmd/gvmd.sock --xml \"${xml}\"`;\n\ntry {\n const stdout = execSync(cmd).toString();\n return [{ json: { stdout } }];\n} catch (error) {\n throw new Error(error.message);\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1040,
0
],
"id": "b7a5528e-4f22-48af-9841-877275b0c619",
"name": "DownloadPDF"
},
{
"parameters": {
"amount": 30,
"unit": "minutes"
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
832,
0
],
"id": "a1c23dbf-8eb8-494d-ab5f-9c8d34684fb1",
"name": "Wait"
},
{
"parameters": {
"dataPropertyName": "stdout",
"options": {
"mergeAttrs": true
}
},
"type": "n8n-nodes-base.xml",
"typeVersion": 1,
"position": [
1248,
0
],
"id": "a5a674ac-dc9e-4ffc-bd81-af89f4053255",
"name": "XML1"
},
{
"parameters": {
"jsCode": "// 1. Obtenemos todos los resultados crudos\nconst vulns = items.map(item => item.json);\n\nif (!vulns.length) {\n return [{ json: { email_body: \"Sin resultados\", subject: \"Scan vac\u00edo\" } }];\n}\n\n// --- HELPERS ---\nconst toText = (val) => {\n if (!val) return \"N/A\";\n if (typeof val === 'string') return val;\n if (typeof val === 'object' && val._) return val._; \n return JSON.stringify(val);\n};\n\n// Funci\u00f3n para sacar la IP limpia de cada item\nconst getIP = (v) => {\n if (v.host && v.host._) return v.host._;\n if (typeof v.host === 'string') return v.host;\n return \"Desconocido\";\n};\n\n// 2. L\u00f3gica Multi-Host\n// Sacamos la lista de IPs \u00fanicas que hay en el escaneo (ej: 192.168.100.10, 192.168.100.20)\nconst uniqueHosts = [...new Set(vulns.map(v => getIP(v)))];\nconst hostsString = uniqueHosts.join(', ');\n\nconst scanDate = vulns[0].timestamp || new Date().toISOString();\n\n// 3. Estad\u00edsticas Globales (Sumamos todo lo que encontramos en la red)\nconst critical = vulns.filter(v => v.severity >= 9.0);\nconst high = vulns.filter(v => v.severity >= 7.0 && v.severity < 9.0);\nconst medium = vulns.filter(v => v.severity >= 4.0 && v.severity < 7.0);\nconst low = vulns.filter(v => v.severity < 4.0);\n\n// 4. Armado del HTML Multi-Host\nconst htmlReport = `\n<div style=\"font-family: sans-serif; color: #222;\">\n <h2 style=\"border-bottom: 2px solid #444;\">\ud83d\udee1\ufe0f Reporte de Red Unificado</h2>\n <div style=\"background-color: #f8f9fa; padding: 10px; border-radius: 5px; margin-bottom: 20px;\">\n <p style=\"margin: 0;\">\n <strong>Hosts Escaneados:</strong> ${hostsString}<br>\n <strong>Fecha:</strong> ${scanDate}<br>\n <strong>Total Hallazgos:</strong> ${vulns.length}\n </p>\n </div>\n\n <table style=\"width: 100%; border-collapse: collapse; margin-bottom: 20px;\">\n <tr style=\"background: #333; color: white;\">\n <th style=\"padding: 8px;\">Cr\u00edtica</th>\n <th style=\"padding: 8px;\">Alta</th>\n <th style=\"padding: 8px;\">Media</th>\n <th style=\"padding: 8px;\">Baja</th>\n </tr>\n <tr style=\"text-align: center; font-size: 1.2em;\">\n <td style=\"border: 1px solid #ccc; color: red; font-weight: bold; padding: 10px;\">${critical.length}</td>\n <td style=\"border: 1px solid #ccc; color: orange; font-weight: bold; padding: 10px;\">${high.length}</td>\n <td style=\"border: 1px solid #ccc; color: #d4a017; padding: 10px;\">${medium.length}</td>\n <td style=\"border: 1px solid #ccc; color: green; padding: 10px;\">${low.length}</td>\n </tr>\n </table>\n\n <h3>\ud83d\udea8 Hallazgos Cr\u00edticos y Altos</h3>\n ${[...critical, ...high].length === 0 ? '<p>No hay vulnerabilidades cr\u00edticas o altas en la red.</p>' : ''}\n \n ${[...critical, ...high].map(v => `\n <div style=\"border: 1px solid #ddd; padding: 15px; margin-bottom: 15px; border-left: 5px solid ${v.severity >= 9 ? 'red' : 'orange'}; background-color: #fff;\">\n \n <div style=\"display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;\">\n <h3 style=\"margin: 0; color: #333;\">${toText(v.name)}</h3>\n <span style=\"background-color: #333; color: #fff; padding: 3px 8px; border-radius: 3px; font-size: 0.8em;\">\n \ud83d\udda5\ufe0f ${getIP(v)}\n </span>\n </div>\n\n <div style=\"font-size: 0.9em; color: #555; margin-bottom: 10px;\">\n <strong>Severidad:</strong> ${v.severity} | \n <strong>Puerto:</strong> ${toText(v.port)} | \n <strong>CVE:</strong> ${toText(v.cve)}\n </div>\n \n <p style=\"margin: 0 0 10px 0;\"><strong>Descripci\u00f3n:</strong><br> ${toText(v.description).substring(0, 300)}...</p>\n \n <div style=\"background: #f1f1f1; padding: 8px; border-radius: 4px; font-size: 0.9em;\">\n <strong>\ud83d\udca1 Soluci\u00f3n:</strong> ${toText(v.solution).substring(0, 250)}...\n </div>\n </div>\n `).join('')}\n\n <hr>\n <small>Reporte autom\u00e1tico N8N - Hosts: ${uniqueHosts.length}</small>\n</div>\n`;\n\nreturn [{\n json: {\n email_body: htmlReport,\n subject: `Reporte de Red (${uniqueHosts.length} Hosts) - ${critical.length > 0 ? '\ud83d\udd34 CRITICO' : '\ud83d\udd35 INFO'}`\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1664,
0
],
"id": "a7e6ce3c-a7c1-4683-8aa4-006d7dc7f4d3",
"name": "Code in JavaScript2"
},
{
"parameters": {
"jsCode": "// Este c\u00f3digo asume que el nodo anterior se llama \"XML Parse\"\n// Ajusta el nombre si le pusiste otro.\n\nconst reportData = items[0].json;\n\n// Navegamos la estructura de OpenVAS para encontrar los resultados\n// A veces es report.report.results.result, a veces var\u00eda un poco seg\u00fan la versi\u00f3n.\n// El 'try-catch' es por seguridad.\n\nlet rawResults = [];\ntry {\n // Ajuste seg\u00fan la estructura simplificada del nodo XML de n8n\n rawResults = reportData.get_reports_response.report.report.results.result;\n} catch (error) {\n // Si no hay resultados o fall\u00f3 la ruta\n return [];\n}\n\n// Si rawResults no es un array (es un solo resultado), lo convertimos\nif (!Array.isArray(rawResults)) {\n rawResults = [rawResults];\n}\n\nconst cleanVulnerabilities = rawResults.map(vuln => {\n return {\n json: {\n scan_id: reportData.get_reports_response.report.id,\n timestamp: reportData.get_reports_response.report.creation_time,\n host: vuln.host['#text'] || vuln.host, // A veces viene como objeto\n port: vuln.port,\n // La severidad viene como string \"10.0\", la pasamos a numero\n severity: parseFloat(vuln.severity),\n name: vuln.name,\n description: vuln.description,\n // Intentamos sacar el CVE y la Soluci\u00f3n si existen\n cve: vuln.nvt?.cve || 'N/A',\n solution: vuln.nvt?.solution || 'No solution provided',\n solution_type: vuln.nvt?.solution_type || 'Unspecified'\n }\n };\n});\n\n// FILTRO OPCIONAL: Sacar logs informativos (Severity 0.0)\n// Descomenta la linea de abajo si solo queres cosas peligrosas\n// return cleanVulnerabilities.filter(item => item.json.severity > 0.0);\n\nreturn cleanVulnerabilities;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1424,
0
],
"id": "00860938-5583-4d5e-acdb-c2896d37491d",
"name": "parseo"
}
],
"connections": {
"When clicking \u2018Execute workflow\u2019": {
"main": [
[
{
"node": "Nmap",
"type": "main",
"index": 0
}
]
]
},
"XML": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"CreateTask": {
"main": [
[
{
"node": "StartTask",
"type": "main",
"index": 0
}
]
]
},
"StartTask": {
"main": [
[
{
"node": "Wait",
"type": "main",
"index": 0
}
]
]
},
"CreateTarget": {
"main": [
[
{
"node": "CreateTask",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "CreateTarget",
"type": "main",
"index": 0
}
]
]
},
"Nmap": {
"main": [
[
{
"node": "XML",
"type": "main",
"index": 0
}
]
]
},
"DownloadPDF": {
"main": [
[
{
"node": "XML1",
"type": "main",
"index": 0
}
]
]
},
"Wait": {
"main": [
[
{
"node": "DownloadPDF",
"type": "main",
"index": 0
}
]
]
},
"XML1": {
"main": [
[
{
"node": "parseo",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript2": {
"main": [
[
{
"node": "Send a text message1",
"type": "main",
"index": 0
}
]
]
},
"parseo": {
"main": [
[
{
"node": "Code in JavaScript2",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "04a4ab40-e633-4038-b2d6-fb33cc17374c",
"meta": {
"templateCredsSetupCompleted": true
},
"id": "Dg1asgeWQqs32Cp5",
"tags": []
}
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.
telegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
workflowV3. Uses xml, telegram. Event-driven trigger; 13 nodes.
Source: https://github.com/LucianoMasAndres/TesisCiberseguridad/blob/31e8baa6ae07285b6f3a507eee1e80ded611c754/workflows/workflowV3.json — 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.
This workflow creates a powerful, multi-country Google Trends bot on Telegram. Users can request the top trending search queries for any country by simply sending its two-letter country code (e.g., ,
N8N Complete Final. Uses telegramTrigger, dataTable, telegram, mqtt. Event-driven trigger; 58 nodes.
TextMain. Uses telegramTrigger, stopAndError, telegram, httpRequest. Event-driven trigger; 56 nodes.
Pede Ai. Uses httpRequest, telegram, postgres, telegramTrigger. Event-driven trigger; 53 nodes.
📄 Documentation: Notion Guide