{
  "name": "Nmap response",
  "nodes": [
    {
      "parameters": {
        "content": "## Setup Required\n\n**1. Telegram Bot**\n- Create bot via @BotFather\n- Add credentials in n8n UI -> Settings -> Credentials -> Telegram API\n- Replace `TELEGRAM_CRED_ID` in all Telegram nodes\n\n**2. TELEGRAM_CHAT_ID**\n- Edit `docker-compose.yml` and set a real value for `TELEGRAM_CHAT_ID`\n- Get your chat ID from @userinfobot on Telegram\n\n**3. Kali Container**\n- `KALI_CONTAINER_ID` is set in docker-compose.yml environment\n- The Kali container must be running when workflow executes\n- nmap runs inside Kali, scanning host.docker.internal (Windows host, resolved dynamically by Docker)\n\n**4. Rebuild n8n after docker-compose.yml changes:**\n```\ndocker-compose up -d --build n8n\n```\n\n**5. Verify setup:**\n```\ndocker exec n8n-agent-system docker ps\ndocker exec n8n-agent-system docker exec $KALI_ID nmap --version\n```",
        "height": 420,
        "width": 580,
        "color": 4
      },
      "id": "e43f5f5b-44b7-46c7-a967-19955885915e",
      "name": "Setup Notes",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        7696,
        1200
      ]
    },
    {
      "parameters": {},
      "id": "f547f178-e11f-4239-a81c-00280e065fd8",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        8336,
        1440
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "id": "18d7e388-cfab-4ed5-8ed1-df817ab2520e",
      "name": "Daily Scan Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        8336,
        1600
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:3001/scan",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "target",
              "value": "host.docker.internal"
            }
          ]
        },
        "options": {
          "timeout": 600000
        }
      },
      "id": "42c6e382-1a07-4d75-bbe7-4b2b734b8c73",
      "name": "Execute: nmap via Kali container",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        8560,
        1520
      ]
    },
    {
      "parameters": {
        "jsCode": "const output = $input.first().json;\nconst xml = output.stdout || output.output || '';\n\nif (!xml || xml.length < 100) {\n  const stderr = output.stderr || '';\n  return [{ json: {\n    error: 'Nmap scan failed or returned no output',\n    details: stderr.substring(0, 300),\n    ports: [],\n    target: output.target || 'host.docker.internal',\n    totalPorts: 0,\n    highRiskCount: 0,\n    medRiskCount: 0,\n    lowRiskCount: 0,\n    summary: 'Scan failed - check KALI_CONTAINER_ID and container status'\n  }}];\n}\n\nconst ports = [];\nconst portRegex = /<port protocol=\"(\\w+)\" portid=\"(\\d+)\">[\\s\\S]*?<\\/port>/g;\nlet portMatch;\n\nwhile ((portMatch = portRegex.exec(xml)) !== null) {\n  const portBlock = portMatch[0];\n  const protocol = portMatch[1];\n  const portId = portMatch[2];\n\n  const stateMatch = portBlock.match(/state=\"(\\w+)\"/);\n  if (!stateMatch || stateMatch[1] !== 'open') continue;\n\n  const serviceMatch = portBlock.match(/<service[^>]*name=\"([^\"]*?)\"(?:[^>]*product=\"([^\"]*?)\")?(?:[^>]*version=\"([^\"]*?)\")?/);\n  const service = serviceMatch ? serviceMatch[1] : 'unknown';\n  const product = serviceMatch ? (serviceMatch[2] || '') : '';\n  const version = serviceMatch ? (serviceMatch[3] || '') : '';\n\n  const vulns = [];\n  const scriptRegex = /<script id=\"([^\"]+)\"[^>]*output=\"([^\"]+)\"/g;\n  let sm;\n  while ((sm = scriptRegex.exec(portBlock)) !== null) {\n    const sid = sm[1];\n    const sout = sm[2]\n      .replace(/&amp;/g, '&')\n      .replace(/&lt;/g, '<')\n      .replace(/&gt;/g, '>')\n      .replace(/&quot;/g, '\"');\n    if (sid.includes('vuln') || sid === 'vulners' || sout.includes('CVE') || sout.includes('VULNERABLE')) {\n      vulns.push({ script: sid, output: sout.substring(0, 300) });\n    }\n  }\n\n  const highRiskList = [21, 23, 25, 53, 111, 135, 139, 445, 512, 513, 514, 1433, 1521, 2375, 2376, 3306, 3389, 4444, 5432, 5900, 5985, 5986, 6379, 8080, 9200, 27017];\n  const medRiskList = [22, 80, 443, 1080, 3000, 4000, 8000, 8081, 8082, 8443, 8888, 9000, 9090];\n  let risk = 'low';\n  const portNum = parseInt(portId);\n  if (highRiskList.includes(portNum) || vulns.length > 0) risk = 'high';\n  else if (medRiskList.includes(portNum)) risk = 'medium';\n\n  ports.push({ protocol, port: portId, service, product, version, vulns, risk });\n}\n\nconst riskOrder = { high: 0, medium: 1, low: 2 };\nports.sort((a, b) => riskOrder[a.risk] - riskOrder[b.risk]);\n\nconst high = ports.filter(p => p.risk === 'high');\nconst med = ports.filter(p => p.risk === 'medium');\nconst low = ports.filter(p => p.risk === 'low');\n\nreturn [{ json: {\n  target: output.target || 'host.docker.internal',\n  scanTime: new Date().toISOString(),\n  totalPorts: ports.length,\n  highRiskCount: high.length,\n  medRiskCount: med.length,\n  lowRiskCount: low.length,\n  ports,\n  highRiskPorts: high,\n  medRiskPorts: med,\n  summary: `Found ${ports.length} open ports: ${high.length} high risk, ${med.length} medium risk, ${low.length} low risk`\n}}];"
      },
      "id": "a5fcc544-81b8-4c2f-a448-d6816c3c566a",
      "name": "Code: Parse Nmap XML",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        8784,
        1520
      ]
    },
    {
      "parameters": {
        "jsCode": "const target = $input.first().json.target || 'host.docker.internal';\nconst scan = $input.first().json;\nconst env = typeof $env !== 'undefined' ? $env : {};\nconst configuredModel = typeof env.OLLAMA_MODEL === 'string' ? env.OLLAMA_MODEL.trim() : '';\nconst model = configuredModel || 'qwen3.5:4b';\n\nconst clean = (value = '') => String(value).replace(/\\s+/g, ' ').trim();\n\nconst buildRequestBody = (prompt, numPredict = 180) => ({\n  model,\n  prompt,\n  stream: false,\n  keep_alive: '10m',\n  options: {\n    temperature: 0.15,\n    num_predict: numPredict,\n    num_ctx: 2048\n  }\n});\n\nif (scan.error) {\n  return [{ json: {\n    requestBody: buildRequestBody(\n      `Security scan failed: ${scan.error}. Details: ${scan.details || 'none'}. Give 3 short troubleshooting steps.`,\n      140\n    ),\n    scanData: scan,\n    ollamaModel: model\n  }}];\n}\n\nconst ports = Array.isArray(scan.ports) ? scan.ports : [];\nconst riskWeight = { high: 2, medium: 1, low: 0 };\nconst rankedPorts = [...ports]\n  .sort((a, b) => (riskWeight[b.risk] ?? 0) - (riskWeight[a.risk] ?? 0))\n  .slice(0, 8);\n\nconst portsText = rankedPorts.map((p) => {\n  const proto = clean(p.protocol || 'tcp');\n  const svc = clean(p.service || 'unknown');\n  const product = clean(`${p.product || ''} ${p.version || ''}`);\n  const risk = clean((p.risk || 'low').toUpperCase());\n  const vulns = Array.isArray(p.vulns) ? p.vulns : [];\n  const vulnNames = vulns.slice(0, 3).map(v => clean(v.script || 'vuln')).join(', ');\n\n  let line = `- ${p.port}/${proto} ${svc}`;\n  if (product) line += ` (${product})`;\n  line += ` [${risk}]`;\n  if (vulnNames) line += ` | vulns: ${vulnNames}`;\n  return line;\n}).join('\\n');\n\nconst omitted = ports.length - rankedPorts.length;\nconst omittedLine = omitted > 0 ? `\\n- (${omitted} additional lower-priority ports omitted)` : '';\n\nconst prompt = `You are a cybersecurity analyst. Analyze this nmap scan for host ' + target + '.\n\nSummary:\n- Total open ports: ${scan.totalPorts}\n- High risk: ${scan.highRiskCount}\n- Medium risk: ${scan.medRiskCount}\n- Low risk: ${scan.lowRiskCount}\n\nTop ports:\n${portsText || '- No open ports found'}${omittedLine}\n\nReturn exactly:\nOVERALL RISK: [Critical/High/Medium/Low]\nTOP THREATS:\n1. ...\n2. ...\n3. ...\nIMMEDIATE ACTIONS:\n1. ...\n2. ...\n3. ...\nSUMMARY: ...\n\nKeep it concise (max 180 words), technical, and actionable.`;\n\nreturn [{ json: {\n  requestBody: buildRequestBody(prompt, 180),\n  scanData: scan,\n  ollamaModel: model\n}}];"
      },
      "id": "e0de7196-8eb8-46f6-a42e-b125f3a0c63e",
      "name": "Code: Build Ollama Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        9008,
        1520
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://ollama:11434/api/generate",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.requestBody }}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 180000
        }
      },
      "id": "5d7dd314-61f1-45ee-ae58-7e35c201ca3d",
      "name": "HTTP: Ollama AI Analysis",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        9216,
        1520
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "jsCode": "const ollamaResp = $input.first().json;\nconst upstream = $('Code: Build Ollama Prompt').first().json;\nconst scanData = upstream.scanData;\nconst ollamaModel = upstream.ollamaModel || 'qwen3.5:4b';\n\nconst fixText = (value = '') => {\n  if (typeof value !== 'string') return '';\n  let text = value;\n\n  // Repair common mojibake where UTF-8 bytes were decoded as latin1/cp1252.\n  if (/(?:\\u00C3|\\u00C2|\\u00E2|\\u00F0|\\u00EF)/.test(text)) {\n    try {\n      text = Buffer.from(text, 'latin1').toString('utf8');\n    } catch (e) {\n      // Keep original text if conversion fails.\n    }\n  }\n\n  return text\n    .replace(/[\\u200B-\\u200D\\uFEFF]/g, '')\n    .replace(/\\uFFFD/g, '');\n};\n\nlet aiText = 'AI analysis unavailable';\n\nconst readError = (err) => {\n  if (!err) return '';\n  if (typeof err === 'string') return err;\n  if (typeof err.message === 'string') return err.message;\n  if (typeof err.error === 'string') return err.error;\n  try {\n    return JSON.stringify(err);\n  } catch (e) {\n    return '';\n  }\n};\n\nconst directResp = typeof ollamaResp.response === 'string' ? ollamaResp.response.trim() : '';\nconst directErr = readError(ollamaResp.error);\n\nif (directResp) {\n  aiText = directResp;\n} else if (directErr) {\n  aiText = `AI analysis unavailable (${directErr.substring(0, 220)}). Check that Ollama is running and the model ${ollamaModel} is installed.`;\n} else {\n  try {\n    const body = typeof ollamaResp.body === 'string' ? JSON.parse(ollamaResp.body) : ollamaResp.body;\n    const bodyResp = typeof body?.response === 'string' ? body.response.trim() : '';\n    const bodyErr = readError(body?.error);\n    if (bodyResp) {\n      aiText = bodyResp;\n    } else if (bodyErr) {\n      aiText = `AI analysis unavailable (${bodyErr.substring(0, 220)}). Check that Ollama is running and the model ${ollamaModel} is installed.`;\n    }\n  } catch (e) {\n    // Keep default fallback text.\n  }\n}\n\naiText = fixText(aiText);\nif (!aiText.trim()) aiText = 'AI analysis unavailable';\n\nconst ports = Array.isArray(scanData.ports) ? scanData.ports : [];\nconst { target, scanTime, totalPorts, highRiskCount, medRiskCount, lowRiskCount } = scanData;\nconst date = new Date(scanTime).toLocaleString();\n\nconst high = ports.filter(p => p.risk === 'high');\nconst med = ports.filter(p => p.risk === 'medium');\nconst low = ports.filter(p => p.risk === 'low');\n\nlet riskBadge = 'LOW';\nif (scanData.error) riskBadge = 'SCAN FAILED';\nelse if (highRiskCount > 3) riskBadge = 'CRITICAL';\nelse if (highRiskCount > 0) riskBadge = 'HIGH';\nelse if (medRiskCount > 2) riskBadge = 'MEDIUM';\n\nlet msg = `*Nmap Security Report*\\n`;\nmsg += `Target: \\`${target}\\`\\n`;\nmsg += `Time: ${date}\\n`;\nmsg += `Risk Level: *${riskBadge}*\\n`;\nmsg += `----------------------\\n\\n`;\n\nif (scanData.error) {\n  msg += `Scan Error: ${scanData.error}\\n${scanData.details || ''}\\n\\n`;\n} else {\n  msg += `Open Ports: ${totalPorts}\\n`;\n  if (high.length) msg += `High Risk: ${high.length}\\n`;\n  if (med.length) msg += `Medium Risk: ${med.length}\\n`;\n  if (low.length) msg += `Low Risk: ${low.length}\\n`;\n  msg += `\\n`;\n\n  if (high.length > 0) {\n    msg += `High Risk Ports:\\n`;\n    high.forEach(p => {\n      const svc = fixText(p.service || 'unknown');\n      const prod = fixText((p.product || '').trim());\n      const ver = fixText((p.version || '').trim());\n\n      msg += `- \\`${p.port}/${p.protocol}\\` - ${svc}`;\n      if (prod || ver) msg += ` (${`${prod} ${ver}`.trim()})`;\n      if (p.vulns.length > 0) msg += ` [${p.vulns.length} vuln(s)]`;\n      msg += `\\n`;\n    });\n    msg += `\\n`;\n  }\n\n  if (med.length > 0) {\n    msg += `Medium Risk Ports:\\n`;\n    med.forEach(p => {\n      const svc = fixText(p.service || 'unknown');\n      const prod = fixText((p.product || '').trim());\n\n      msg += `- \\`${p.port}/${p.protocol}\\` - ${svc}`;\n      if (prod) msg += ` (${prod})`;\n      msg += `\\n`;\n    });\n    msg += `\\n`;\n  }\n\n  if (low.length > 0) {\n    const lowStr = low.map(p => `\\`${p.port}/${p.protocol}\\``).join(', ');\n    if (low.length <= 6) msg += `Low Risk: ${lowStr}\\n\\n`;\n    else msg += `Low Risk: ${low.length} ports\\n\\n`;\n  }\n}\n\nconst aiUnavailable = /^AI analysis unavailable/i.test(aiText);\nif (aiUnavailable) {\n  const topExposure = high.slice(0, 3).map(p => `${p.port}/${p.protocol}`).join(', ');\n  const focus = topExposure || 'exposed high-risk services';\n  aiText = `Local fallback assessment: ${riskBadge} exposure. Prioritize restricting ${focus}, patching exposed services, and enforcing host firewall allow-list rules.`;\n}\n\nmsg += `AI Analysis:\\n`;\nconst safeAnalysis = fixText(aiText.substring(0, 700)).replace(/[_`]/g, '');\nmsg += safeAnalysis;\nif (aiText.length > 700) msg += '...';\n\nlet vulnMsg = `*Vulnerability Details - ${target}*\\n----------------------\\n\\n`;\nconst withVulns = ports.filter(p => p.vulns.length > 0);\nif (withVulns.length === 0) {\n  vulnMsg += 'No CVEs or vulnerabilities detected by nmap scripts.\\n\\n';\n  vulnMsg += '_Tip: Run nmap with sudo inside Kali for deeper SYN scans (-sS) and more script coverage._';\n} else {\n  withVulns.forEach(p => {\n    vulnMsg += `Port ${p.port}/${p.protocol} (${fixText(p.service || 'unknown')}):\\n`;\n    p.vulns.forEach(v => {\n      vulnMsg += `  - ${fixText(v.script || 'script')}:\\n`;\n      vulnMsg += `    ${fixText((v.output || '').substring(0, 250)).replace(/[_`*]/g, '')}\\n`;\n    });\n    vulnMsg += `\\n`;\n  });\n}\n\nmsg = fixText(msg);\nvulnMsg = fixText(vulnMsg);\n\nconst keyboard = {\n  inline_keyboard: [\n    [\n      { text: 'Acknowledge',       callback_data: 'ack|all|all'               },\n      { text: 'Vuln Details',      callback_data: 'vulndetail|all|all'        }\n    ],\n    [\n      { text: 'Rescan',            callback_data: 'rescan|0|0'                },\n      { text: 'Deep: Full TCP',    callback_data: 'scanprofile|deep_fulltcp|0'}\n    ],\n    [\n      { text: 'Deep: Vuln NSE',    callback_data: 'scanprofile|deep_vuln|0'   },\n      { text: 'Deep: UDP',         callback_data: 'scanprofile|deep_udp|0'    }\n    ],\n    [\n      { text: 'WiFi Scan',         callback_data: 'wifiscan|0|0'              },\n      { text: 'Deauth (send MAC)', callback_data: 'deauth_prompt|0|0'         }\n    ],\n    [\n      { text: 'Scan Custom IP',    callback_data: 'scanip_prompt|0|0'         }\n    ]\n  ]\n};\nreturn [{ json: {\n  message: msg,\n  keyboard: JSON.stringify(keyboard),\n  vulnDetailMessage: vulnMsg,\n  scanData\n}}];"
      },
      "id": "6f7c4833-2652-4fa9-8927-77872288d793",
      "name": "Code: Build Telegram Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        9440,
        1520
      ]
    },
    {
      "parameters": {
        "chatId": "=-1003709631864",
        "text": "={{ $json.message }}",
        "replyMarkup": "inlineKeyboard",
        "inlineKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "Acknowledge",
                    "additionalFields": {
                      "callback_data": "ack|all|all"
                    }
                  },
                  {
                    "text": "Vuln Details",
                    "additionalFields": {
                      "callback_data": "vulndetail|all|all"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "Rescan",
                    "additionalFields": {
                      "callback_data": "rescan|0|0"
                    }
                  },
                  {
                    "text": "Deep: Full TCP",
                    "additionalFields": {
                      "callback_data": "scanprofile|deep_fulltcp|0"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "Deep: Vuln NSE",
                    "additionalFields": {
                      "callback_data": "scanprofile|deep_vuln|0"
                    }
                  },
                  {
                    "text": "Deep: UDP",
                    "additionalFields": {
                      "callback_data": "scanprofile|deep_udp|0"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "WiFi Scan",
                    "additionalFields": {
                      "callback_data": "wifiscan|0|0"
                    }
                  },
                  {
                    "text": "Deauth (send MAC)",
                    "additionalFields": {
                      "callback_data": "deauth_prompt|0|0"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "Scan Custom IP",
                    "additionalFields": {
                      "callback_data": "scanip_prompt|0|0"
                    }
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "id": "fa368e56-49bc-445e-9d3a-14e9e4a2bbee",
      "name": "Telegram: Send Security Report",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        9664,
        1520
      ],
      "webhookId": "ef775a6a-4b82-47ce-9a7d-1319368a92b9",
      "credentials": {
        "telegramApi": {
          "id": "cJ0SVLSNdhqE2Bnh",
          "name": "NMAP Alert"
        }
      }
    },
    {
      "parameters": {
        "updates": [
          "callback_query",
          "message"
        ],
        "additionalFields": {}
      },
      "id": "0fef5cbe-6228-4ad9-b5f2-8a26013c917e",
      "name": "Telegram Trigger: Button Clicks",
      "type": "n8n-nodes-base.telegramTrigger",
      "typeVersion": 1.1,
      "position": [
        8336,
        1824
      ],
      "webhookId": "nmap-callback-v2",
      "credentials": {
        "telegramApi": {
          "id": "cJ0SVLSNdhqE2Bnh",
          "name": "NMAP Alert"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const data = $input.first().json;\n\n// Handle plain text messages (user typed an IP or MAC)\nif (data.message && data.message.text) {\n  const text = (data.message.text || '').trim();\n  const chatId = String((data.message.chat && data.message.chat.id) || '');\n  const macRe = /^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/;\n  const ipRe  = /^(\\d{1,3}\\.){3}\\d{1,3}$/;\n  if (macRe.test(text)) {\n    return [{ json: { action: 'deauth', chatId, callbackId: '', bssid: text, customTarget: '',\n      replyMsg: '*Deauth attack starting...*\\nBSSID: `' + text + '`\\nSending 50 deauth frames to all clients.',\n      isRescan: 'true', scanMode: 'deauth' } }];\n  }\n  if (ipRe.test(text)) {\n    return [{ json: { action: 'custom_scan', chatId, callbackId: '', bssid: '', customTarget: text,\n      replyMsg: '*Custom scan starting...*\\nTarget: `' + text + '`',\n      isRescan: 'true', scanMode: 'custom_scan' } }];\n  }\n  return [{ json: { action: 'unknown_text', chatId, callbackId: '', bssid: '', customTarget: '',\n    replyMsg: 'Unknown input. Send:\\n- An IP (e.g. `192.168.0.1`) to scan it\\n- A MAC (e.g. `AA:BB:CC:DD:EE:FF`) to deauth',\n    isRescan: 'false', scanMode: '' } }];\n}\n\n// Handle button callback_query\nconst cq = data.callback_query || data;\nconst callbackData = String(cq.data || '');\nconst chatId = String((cq.message && cq.message.chat && cq.message.chat.id) || (cq.from && cq.from.id) || '');\nconst callbackId = cq.id;\n\nconst parts = callbackData.split('|');\nconst action = (parts[0] || '').trim().toLowerCase().replace(/[^a-z_]/g, '');\nconst arg    = (parts[1] || '').trim();\n\nlet replyMsg = '', isRescan = 'false', scanMode = '', bssid = '', customTarget = '';\n\nconst profileMap = {\n  deep_fulltcp: { mode: 'deep_fulltcp', label: 'Deep Full TCP (all ports)' },\n  deep_vuln:    { mode: 'deep_vuln',    label: 'Deep Vulnerability NSE'    },\n  deep_udp:     { mode: 'deep_udp',     label: 'Deep UDP Top 200'          },\n};\n\nswitch (action) {\n  case 'ack':\n    replyMsg = '*Acknowledged*\\n\\nScan noted. Block unnecessary ports via Windows Firewall or your router.';\n    break;\n  case 'vulndetail':\n    isRescan = 'true'; scanMode = 'deep_vuln';\n    replyMsg = '*Deep Vulnerability Scan triggered...*\\nRunning extended Kali NSE scripts.';\n    break;\n  case 'rescan':\n    isRescan = 'true'; scanMode = 'standard';\n    replyMsg = '*Rescan triggered...*\\nStarting standard nmap scan.';\n    break;\n  case 'scanprofile': {\n    const sel = profileMap[arg] || profileMap.deep_vuln;\n    isRescan = 'true'; scanMode = sel.mode;\n    replyMsg = '*Scan profile: ' + sel.label + '*';\n    break;\n  }\n  case 'wifiscan':\n    isRescan = 'true'; scanMode = 'wifiscan';\n    replyMsg = '*WiFi scan starting...*\\nListing nearby networks via iw.';\n    break;\n  case 'deauth_prompt':\n    replyMsg = '*Deauth Attack*\\n\\nReply with the BSSID of the AP:\\nFormat: `AA:BB:CC:DD:EE:FF`\\n\\n_Ensure monitor mode first: `airmon-ng start wlan0`_';\n    break;\n  case 'scanip_prompt':\n    replyMsg = '*Custom IP Scan*\\n\\nReply with the IP address to scan:\\nExample: `192.168.0.50`';\n    break;\n  default:\n    replyMsg = 'Unknown action: ' + (action || callbackData);\n}\n\nreturn [{ json: { action, chatId, callbackId, replyMsg, isRescan, scanMode, bssid, customTarget } }];"
      },
      "id": "b35ec012-8d3f-4d9e-98ef-0b7c6227e55f",
      "name": "Code: Parse Callback",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        8560,
        1824
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-rescan",
              "leftValue": "={{ $json.isRescan }}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "d7b42e5a-d1cb-4462-8f3b-21ab54deedba",
      "name": "IF: Rescan?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        8784,
        1824
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.replyMsg }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "id": "8ad37d05-4629-4db8-af25-ddc23e72b2c8",
      "name": "Telegram: Notify Rescan Started",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        9232,
        1728
      ],
      "webhookId": "5cc3847b-d93e-40e8-9eba-a798064f07a4",
      "credentials": {
        "telegramApi": {
          "id": "cJ0SVLSNdhqE2Bnh",
          "name": "NMAP Alert"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:3001/scan",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "target",
              "value": "={{ $json.customTarget || 'host.docker.internal' }}"
            },
            {
              "name": "profile",
              "value": "={{ $json.scanMode === 'custom_scan' ? 'standard' : ($json.scanMode || 'standard') }}"
            }
          ]
        },
        "options": {
          "timeout": 600000
        }
      },
      "id": "b552d191-c9dd-42fa-af33-67f398d8b729",
      "name": "Execute: Rescan via Kali",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        9456,
        1728
      ]
    },
    {
      "parameters": {
        "jsCode": "// Re-parse nmap XML (same logic as main flow)\nconst output = $input.first().json;\nconst xml = output.stdout || output.output || '';\n\nif (!xml || xml.length < 100) {\n  return [{ json: {\n    error: 'Rescan failed', ports: [], target: output.target || 'host.docker.internal',\n    totalPorts: 0, highRiskCount: 0, medRiskCount: 0, lowRiskCount: 0,\n    summary: 'Rescan failed'\n  }}];\n}\n\nconst ports = [];\nconst portRegex = /<port protocol=\"(\\w+)\" portid=\"(\\d+)\">[\\s\\S]*?<\\/port>/g;\nlet portMatch;\n\nwhile ((portMatch = portRegex.exec(xml)) !== null) {\n  const portBlock = portMatch[0];\n  const protocol = portMatch[1];\n  const portId = portMatch[2];\n  const stateMatch = portBlock.match(/state=\"(\\w+)\"/);\n  if (!stateMatch || stateMatch[1] !== 'open') continue;\n  const serviceMatch = portBlock.match(/<service[^>]*name=\"([^\"]*?)\"(?:[^>]*product=\"([^\"]*?)\")?(?:[^>]*version=\"([^\"]*?)\")?/);\n  const service = serviceMatch ? serviceMatch[1] : 'unknown';\n  const product = serviceMatch ? (serviceMatch[2] || '') : '';\n  const version = serviceMatch ? (serviceMatch[3] || '') : '';\n  const vulns = [];\n  const scriptRegex = /<script id=\"([^\"]+)\"[^>]*output=\"([^\"]+)\"/g;\n  let sm;\n  while ((sm = scriptRegex.exec(portBlock)) !== null) {\n    const sout = sm[2].replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');\n    if (sm[1].includes('vuln') || sm[1]==='vulners' || sout.includes('CVE')) {\n      vulns.push({ script: sm[1], output: sout.substring(0, 300) });\n    }\n  }\n  const highList = [21,23,25,53,111,135,139,445,512,513,514,1433,1521,2375,2376,3306,3389,4444,5432,5900,5985,5986,6379,8080,9200,27017];\n  const medList = [22,80,443,1080,3000,4000,8000,8081,8082,8443,8888,9000,9090];\n  let risk = 'low';\n  const portNum = parseInt(portId);\n  if (highList.includes(portNum) || vulns.length > 0) risk = 'high';\n  else if (medList.includes(portNum)) risk = 'medium';\n  ports.push({ protocol, port: portId, service, product, version, vulns, risk });\n}\n\nports.sort((a,b) => ({high:0,medium:1,low:2}[a.risk] - {high:0,medium:1,low:2}[b.risk]));\nconst high = ports.filter(p=>p.risk==='high');\nconst med = ports.filter(p=>p.risk==='medium');\nconst low = ports.filter(p=>p.risk==='low');\n\nreturn [{ json: {\n  target: output.target || 'host.docker.internal', scanTime: new Date().toISOString(),\n  totalPorts: ports.length, highRiskCount: high.length,\n  medRiskCount: med.length, lowRiskCount: low.length,\n  ports, highRiskPorts: high, medRiskPorts: med,\n  summary: `Found ${ports.length} open ports: ${high.length} high risk, ${med.length} medium risk`\n}}];"
      },
      "id": "732e3edf-b799-4e5d-8ce1-8eb852fc6488",
      "name": "Code: Parse Rescan XML",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        9664,
        1728
      ]
    },
    {
      "parameters": {
        "jsCode": "const scan = $input.first().json;\nconst chatId = $('Code: Parse Callback').first().json.chatId;\n\nconst fixText = (value = '') => {\n  if (typeof value !== 'string') return '';\n  let text = value;\n  if (/(?:\\u00C3|\\u00C2|\\u00E2|\\u00F0|\\u00EF)/.test(text)) {\n    try {\n      text = Buffer.from(text, 'latin1').toString('utf8');\n    } catch (e) {\n      // Keep original text if conversion fails.\n    }\n  }\n  return text\n    .replace(/[\\u200B-\\u200D\\uFEFF]/g, '')\n    .replace(/\\uFFFD/g, '');\n};\n\nconst high = scan.ports.filter(p => p.risk === 'high');\nconst med = scan.ports.filter(p => p.risk === 'medium');\nconst low = scan.ports.filter(p => p.risk === 'low');\n\nlet riskBadge = 'LOW';\nif (scan.highRiskCount > 3) riskBadge = 'CRITICAL';\nelse if (scan.highRiskCount > 0) riskBadge = 'HIGH';\nelse if (scan.medRiskCount > 2) riskBadge = 'MEDIUM';\n\nlet msg = `*Rescan Complete - ${scan.target}*\\n`;\nmsg += `Time: ${new Date(scan.scanTime).toLocaleString()}\\n`;\nmsg += `Risk Level: *${riskBadge}*\\n`;\nmsg += `----------------------\\n\\n`;\nmsg += `Open Ports: ${scan.totalPorts}\\n`;\nif (high.length) msg += `High Risk: ${high.length}\\n`;\nif (med.length) msg += `Medium Risk: ${med.length}\\n`;\nif (low.length) msg += `Low Risk: ${low.length}\\n`;\n\nif (high.length > 0) {\n  msg += `\\nHigh Risk:\\n`;\n  high.forEach(p => {\n    msg += `- \\`${p.port}/${p.protocol}\\` ${fixText(p.service || 'unknown')}`;\n    if (p.vulns.length) msg += ` [${p.vulns.length} vuln(s)]`;\n    msg += `\\n`;\n  });\n}\n\nmsg = fixText(msg);\n\nconst keyboard = JSON.stringify({ inline_keyboard: [\n  [{ text: 'Acknowledge', callback_data: 'ack|all|all' }, { text: 'Vuln Details', callback_data: 'vulndetail|all|all' }],\n  [{ text: 'Rescan Again', callback_data: 'rescan|0|0' }],\n  [{ text: '🚀 Advanced Recon', callback_data: 'nextflow|kali_recon|0' }, { text: '💻 Exploit Search', callback_data: 'nextflow|kali_exploit|0' }],\n  [{ text: '🔑 Credential Crack', callback_data: 'nextflow|kali_crack|0' }, { text: '🔥 Stress Test', callback_data: 'nextflow|kali_stress|0' }]\n]});\n\nreturn [{ json: { message: msg, keyboard, chatId, scanData: scan } }];"
      },
      "id": "80dead94-a7c4-4233-8536-728682129161",
      "name": "Code: Build Rescan Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        9888,
        1728
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.message }}",
        "replyMarkup": "inlineKeyboard",
        "inlineKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "Acknowledge",
                    "additionalFields": {
                      "callback_data": "ack|all|all"
                    }
                  },
                  {
                    "text": "Vuln Details",
                    "additionalFields": {
                      "callback_data": "vulndetail|all|all"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "Rescan Again",
                    "additionalFields": {
                      "callback_data": "rescan|0|0"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "Deep: Full TCP",
                    "additionalFields": {
                      "callback_data": "scanprofile|deep_fulltcp|0"
                    }
                  },
                  {
                    "text": "Deep: Vuln NSE",
                    "additionalFields": {
                      "callback_data": "scanprofile|deep_vuln|0"
                    }
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "Deep: UDP Top200",
                    "additionalFields": {
                      "callback_data": "scanprofile|deep_udp|0"
                    }
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "id": "63b01920-7463-46ef-ad55-cc6b99a3c142",
      "name": "Telegram: Send Rescan Report",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        10096,
        1728
      ],
      "webhookId": "1146e73e-23d7-4810-8441-cdcbb8378cc6",
      "credentials": {
        "telegramApi": {
          "id": "cJ0SVLSNdhqE2Bnh",
          "name": "NMAP Alert"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.replyMsg }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "id": "36aceadc-e11a-4ab1-ad28-0f8a3b7ab2bd",
      "name": "Telegram: Send Ack/Info",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        9232,
        2160
      ],
      "webhookId": "2fba925b-3826-4da3-aa24-96f329218b5f",
      "credentials": {
        "telegramApi": {
          "id": "cJ0SVLSNdhqE2Bnh",
          "name": "NMAP Alert"
        }
      }
    },
    {
      "id": "aaaa0001-0000-0000-0000-000000000001",
      "name": "IF: Is WiFi or Deauth?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        9008,
        1824
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-wifi",
              "leftValue": "={{ $json.scanMode }}",
              "rightValue": "wifiscan",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "cond-deauth",
              "leftValue": "={{ $json.scanMode }}",
              "rightValue": "deauth",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      }
    },
    {
      "id": "aaaa0001-0000-0000-0000-000000000002",
      "name": "IF: Deauth or WiFi?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        9232,
        1920
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-is-deauth",
              "leftValue": "={{ $json.scanMode }}",
              "rightValue": "deauth",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "aaaa0001-0000-0000-0000-000000000003",
      "name": "Execute: Deauth",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        9456,
        2064
      ],
      "parameters": {
        "method": "POST",
        "url": "http://localhost:3001/deauth",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "bssid",
              "value": "={{ $json.bssid }}"
            },
            {
              "name": "interface",
              "value": "wlan0mon"
            },
            {
              "name": "count",
              "value": "50"
            },
            {
              "name": "client",
              "value": "FF:FF:FF:FF:FF:FF"
            }
          ]
        },
        "options": {
          "timeout": 60000
        }
      }
    },
    {
      "id": "aaaa0001-0000-0000-0000-000000000004",
      "name": "Execute: WiFi Scan",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        9456,
        1920
      ],
      "parameters": {
        "method": "POST",
        "url": "http://localhost:3001/wifi-scan",
        "options": {
          "timeout": 30000
        }
      }
    },
    {
      "id": "aaaa0001-0000-0000-0000-000000000005",
      "name": "Code: Build Deauth Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        9680,
        2064
      ],
      "parameters": {
        "jsCode": "const res = $input.first().json;\nconst prev = $('IF: Deauth or WiFi?').first().json;\nconst chatId = prev.chatId;\nlet msg;\nif (res.success) {\n  msg = '*Deauth Complete*\\nBSSID: `' + res.bssid + '`\\nFrames: ' + res.count + '\\n```\\n' + (res.output || '').slice(0, 400) + '\\n```';\n} else {\n  msg = '*Deauth Failed*\\n' + (res.error || 'Unknown error') + '\\n\\nEnsure wlan0mon is up: `airmon-ng start wlan0`';\n}\nreturn [{ json: { chatId, message: msg } }];"
      }
    },
    {
      "id": "aaaa0001-0000-0000-0000-000000000006",
      "name": "Code: Build WiFi Message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        9680,
        1920
      ],
      "parameters": {
        "jsCode": "const res = $input.first().json;\nconst prev = $('IF: Deauth or WiFi?').first().json;\nconst chatId = prev.chatId;\nconst out = (res.stdout || res.stderr || 'No output');\nconst ssids   = [...out.matchAll(/SSID: (.+)/g)].map(m => m[1].trim()).filter(Boolean);\nconst signals = [...out.matchAll(/signal: (.+)/g)].map(m => m[1].trim());\nlet msg = '*WiFi Scan Results*\\n';\nif (ssids.length > 0) {\n  msg += 'Found ' + ssids.length + ' network(s):\\n';\n  ssids.slice(0, 20).forEach((s, i) => {\n    msg += '- `' + s + '`' + (signals[i] ? ' ' + signals[i] : '') + '\\n';\n  });\n} else {\n  msg += '```\\n' + out.slice(0, 800) + '\\n```';\n}\nreturn [{ json: { chatId, message: msg } }];"
      }
    },
    {
      "id": "aaaa0001-0000-0000-0000-000000000007",
      "name": "Telegram: Send Deauth Result",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        9904,
        2064
      ],
      "credentials": {
        "telegramApi": {
          "id": "cJ0SVLSNdhqE2Bnh",
          "name": "NMAP Alert"
        }
      },
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.message }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      }
    },
    {
      "id": "aaaa0001-0000-0000-0000-000000000008",
      "name": "Telegram: Send WiFi Result",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        9904,
        1920
      ],
      "credentials": {
        "telegramApi": {
          "id": "cJ0SVLSNdhqE2Bnh",
          "name": "NMAP Alert"
        }
      },
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ $json.message }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      }
    }
  ],
  "pinData": {},
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Execute: nmap via Kali container",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Scan Trigger": {
      "main": [
        [
          {
            "node": "Execute: nmap via Kali container",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute: nmap via Kali container": {
      "main": [
        [
          {
            "node": "Code: Parse Nmap XML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Parse Nmap XML": {
      "main": [
        [
          {
            "node": "Code: Build Ollama Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Build Ollama Prompt": {
      "main": [
        [
          {
            "node": "HTTP: Ollama AI Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: Ollama AI Analysis": {
      "main": [
        [
          {
            "node": "Code: Build Telegram Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Build Telegram Message": {
      "main": [
        [
          {
            "node": "Telegram: Send Security Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram Trigger: Button Clicks": {
      "main": [
        [
          {
            "node": "Code: Parse Callback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Parse Callback": {
      "main": [
        [
          {
            "node": "IF: Rescan?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: Rescan?": {
      "main": [
        [
          {
            "node": "IF: Is WiFi or Deauth?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Telegram: Send Ack/Info",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram: Notify Rescan Started": {
      "main": [
        [
          {
            "node": "Execute: Rescan via Kali",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute: Rescan via Kali": {
      "main": [
        [
          {
            "node": "Code: Parse Rescan XML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Parse Rescan XML": {
      "main": [
        [
          {
            "node": "Code: Build Rescan Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Build Rescan Message": {
      "main": [
        [
          {
            "node": "Telegram: Send Rescan Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: Is WiFi or Deauth?": {
      "main": [
        [
          {
            "node": "IF: Deauth or WiFi?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Telegram: Notify Rescan Started",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: Deauth or WiFi?": {
      "main": [
        [
          {
            "node": "Execute: Deauth",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Execute: WiFi Scan",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute: Deauth": {
      "main": [
        [
          {
            "node": "Code: Build Deauth Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Build Deauth Message": {
      "main": [
        [
          {
            "node": "Telegram: Send Deauth Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute: WiFi Scan": {
      "main": [
        [
          {
            "node": "Code: Build WiFi Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Build WiFi Message": {
      "main": [
        [
          {
            "node": "Telegram: Send WiFi Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "versionId": "e958112f-5b56-4c6b-9ef6-9d6fb43997ac",
  "meta": {
    "instanceId": "e09d1f0f8b9f78e80ac70b6dc8726dd263b0b6ffc4300b0eee3b58f6da316f29"
  },
  "id": "urPPleiJDJYmXsCi",
  "tags": [
    {
      "updatedAt": "2026-03-17T19:44:24.806Z",
      "createdAt": "2026-03-17T19:44:24.806Z",
      "id": "DlrHRWqhewJdEXVm",
      "name": "Security"
    }
  ]
}