AutomationFlowsAI & RAG › 3314

3314

3314. Uses formTrigger, httpRequest, lmChatOpenAi, agent. Event-driven trigger; 19 nodes.

Event trigger★★★★☆ complexityAI-powered19 nodesForm TriggerHTTP RequestOpenAI ChatAgentGmail
AI & RAG Trigger: Event Nodes: 19 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Agent → 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 →

Download .json
{
  "nodes": [
    {
      "id": "634f2fc5-0ba7-42ad-bdf5-ade3415dd288",
      "name": "Landing Page Url",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        -200,
        580
      ],
      "parameters": {
        "options": {},
        "formTitle": "Website Security Scanner",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Landing Page Url",
              "placeholder": "https://example.com",
              "requiredField": true
            }
          ]
        },
        "formDescription": "Check your website for security vulnerabilities and get a detailed report"
      },
      "typeVersion": 2.2
    },
    {
      "id": "6cee63ca-d0f6-444a-b882-22da1a9fd70c",
      "name": "Scrape Website",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        0,
        580
      ],
      "parameters": {
        "url": "={{ $json['Landing Page Url'] }}",
        "options": {
          "redirect": {
            "redirect": {
              "maxRedirects": 5
            }
          },
          "response": {
            "response": {
              "fullResponse": true,
              "responseFormat": "text"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "0d5d1e76-e627-4565-a1ee-6a610f4b2028",
      "name": "OpenAI Headers Analysis",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        340,
        600
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "04427ef7-515d-4a1a-88d2-ade10aeefc87",
      "name": "OpenAI Content Analysis",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        340,
        980
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d4ee4db8-aa04-4068-9b97-d16acf98c027",
      "name": "Security Vulnerabilities Audit",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        360,
        780
      ],
      "parameters": {
        "text": "=You are an elite cybersecurity expert specializing in web application security.\n\nIn this task, you will analyze the HTML and visible content of the webpage to identify potential security vulnerabilities.\n\nAudit Structure\nYou will review all client-side security aspects of the page and present your findings in three sections:\n- Critical Vulnerabilities \u2013 Issues that could lead to immediate compromise\n- Information Leakage \u2013 Sensitive data exposed in page source\n- Client-Side Weaknesses \u2013 JavaScript vulnerabilities, XSS opportunities, etc.\n\nFor each issue found, provide:\n1. A clear description of the vulnerability\n2. The potential impact\n3. A specific recommendation to fix it\n\nIf you find no issues in a particular section, explicitly state that no issues were found in that category.\n\nEnsure the output is properly formatted, clean, and highly readable. Focus only on issues that can be detected from the client-side code.\n\nHere is the content of the webpage: {{ $json.data }}",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "c9702f2b-845b-464d-9c32-3d5be308ef77",
      "name": "Security Configuration Audit",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        360,
        380
      ],
      "parameters": {
        "text": "=You are an elite web security expert specializing in secure configurations.\n\nIn this task, you will analyze the HTTP headers, cookies, and overall configuration of a webpage to identify security misconfigurations.\n\nAudit Structure\nYou will begin by listing ALL security headers that ARE present and properly configured.\n\nBe very clear and explicit about which headers are present and which are missing. For each header, clearly state whether it is present or missing, and if present, what its value is.\n\nThen, present your findings in three sections:\n- Header Security \u2013 Missing or misconfigured security headers\n- Cookie Security \u2013 Insecure cookie configurations\n- Content Security \u2013 CSP issues, mixed content, etc.\n\nFor each finding, provide:\n1. A clear description of the misconfiguration\n2. The security implications\n3. The recommended secure configuration with example code\n\nIf you find no issues in a particular section, explicitly state that no issues were found.\n\nUse proper formatting with code blocks for configuration examples. Only include issues that can be detected from client-side inspection.\nHere are the response headers: {{ $json.formattedHeaders }}\n\nPlease Respond like this\n\n### [any section heading that includes \"Headers]\n\n1. **[Header Title]**\n   - **Present?** Yes/No\n   - **Value:** `actual-header-value`\n",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "3b43be75-c35c-44e4-8ecc-a29c48e3625c",
      "name": "Merge Security Results",
      "type": "n8n-nodes-base.merge",
      "position": [
        860,
        580
      ],
      "parameters": {},
      "typeVersion": 3,
      "alwaysOutputData": true
    },
    {
      "id": "da134256-d7fa-4a3f-ba24-acc320a944a2",
      "name": "Aggregate Audit Results",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        1060,
        580
      ],
      "parameters": {
        "options": {},
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {
              "fieldToAggregate": "output"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "aef1da93-0b01-4a7f-9439-1f74c2af12d6",
      "name": "Process Audit Results",
      "type": "n8n-nodes-base.code",
      "position": [
        1240,
        580
      ],
      "parameters": {
        "jsCode": "// \u2705 Updated extractSecurityHeaders and related logic remains unchanged\n\nfunction extractSecurityHeaders(rawHeaders = {}, configOutput = '') {\n  const securityHeaders = [\n    'Content-Security-Policy',\n    'Strict-Transport-Security',\n    'X-Content-Type-Options',\n    'X-Frame-Options',\n    'Referrer-Policy',\n    'Permissions-Policy',\n    'X-XSS-Protection',\n    'Cross-Origin-Embedder-Policy',\n    'Cross-Origin-Opener-Policy',\n    'X-Permitted-Cross-Domain-Policies'\n  ];\n\n  const headerStatus = {};\n  for (const header of securityHeaders) {\n    headerStatus[header] = { present: false, value: '' };\n  }\n\n  for (const header in rawHeaders) {\n    const norm = header.trim().toLowerCase();\n    for (const standard of securityHeaders) {\n      if (norm === standard.toLowerCase()) {\n        headerStatus[standard].present = true;\n        headerStatus[standard].value = rawHeaders[header];\n      }\n    }\n  }\n\n  const presentSection = configOutput.match(/(?:###|##|\\*\\*)[^\\n]*?\\bheaders?\\b[\\s\\S]*?(?=###|##|\\*\\*|$)/i);\n  if (presentSection) {\n    const section = presentSection[0];\n    for (const header of securityHeaders) {\n      const title = header.replace(/-/g, ' ').replace(/\\b\\w/g, c => c.toUpperCase());\n      const regex = new RegExp(`\\\\*\\\\*${title}\\\\*\\\\*[^\\\\n]*?\\\\*\\\\*Present\\\\?\\\\*\\\\*\\\\s*Yes[^\\\\n]*?\\\\*\\\\*Value:\\\\*\\\\*\\\\s*\\`([^\\\\\\`]+)\\``, 'is');\n      const match = section.match(regex);\n      if (match && match[1]) {\n        headerStatus[header].present = true;\n        headerStatus[header].value = match[1].trim();\n      }\n    }\n  }\n\n  return headerStatus;\n}\n\nfunction hasUnsafeInline(value) {\n  return value && value.includes('unsafe-inline');\n}\n\nfunction determineGrade(headerStatus) {\n  const critical = [\n    'Content-Security-Policy',\n    'Strict-Transport-Security',\n    'X-Content-Type-Options',\n    'X-Frame-Options'\n  ];\n  const important = ['Referrer-Policy', 'Permissions-Policy'];\n  const additional = [\n    'X-XSS-Protection',\n    'Cross-Origin-Embedder-Policy',\n    'Cross-Origin-Opener-Policy',\n    'X-Permitted-Cross-Domain-Policies'\n  ];\n\n  let criticalCount = 0;\n  let importantCount = 0;\n  let hasCSPIssue = false;\n\n  for (const h of critical) {\n    if (headerStatus[h]?.present) {\n      criticalCount++;\n      if (h === 'Content-Security-Policy' && hasUnsafeInline(headerStatus[h].value)) {\n        hasCSPIssue = true;\n      }\n    }\n  }\n\n  for (const h of important) {\n    if (headerStatus[h]?.present) importantCount++;\n  }\n\n  if (criticalCount === critical.length) {\n    if (importantCount === important.length) return hasCSPIssue ? 'A-' : 'A+';\n    if (importantCount >= 1) return hasCSPIssue ? 'B+' : 'A-';\n    return hasCSPIssue ? 'B' : 'B+';\n  } else if (criticalCount >= critical.length - 1) {\n    return importantCount >= 1 ? 'B' : 'C+';\n  } else if (criticalCount >= 2) {\n    return 'C';\n  } else if (criticalCount >= 1) {\n    return 'D';\n  } else {\n    return 'F';\n  }\n}\n\nfunction formatHeadersForDisplay(headerStatus) {\n  const present = Object.keys(headerStatus).filter(h => headerStatus[h].present);\n  return present.length > 0 ? present.join(', ') : 'No security headers detected';\n}\n\nfunction processSecurityHeaders(items) {\n  try {\n    const json = items[0].json || items[0];\n\n    // \u26cf\ufe0f Try to grab from originalHeaders if available\n    const rawHeaders =\n      json?.originalHeaders ||\n      $('Extract Headers for Debug')?.first()?.json?.originalHeaders ||\n      json?.headers ||\n      {};\n\n    const configOutput = json.configOutput || json.output?.[0] || '';\n    const vulnOutput = json.vulnOutput || json.output?.[1] || '';\n\n    const headerStatus = extractSecurityHeaders(rawHeaders, configOutput);\n    const presentHeaders = formatHeadersForDisplay(headerStatus);\n    const grade = determineGrade(headerStatus);\n\n    const timestamp = new Date().toLocaleString('en-US', {\n      year: 'numeric',\n      month: 'long',\n      day: 'numeric',\n      hour: '2-digit',\n      minute: '2-digit'\n    });\n\n    const url =\n      json?.formValues?.url ||\n      json?.['Landing Page Url'] ||\n      $('Landing Page Url')?.first()?.json?.['Landing Page Url'] ||\n      json?.Landing_Page_Url ||\n      json?.landingPageUrl ||\n      json?.url ||\n      'https://example.com';\n\n    return [\n      {\n        json: {\n          ...json,\n          auditData: {\n            url,\n            timestamp,\n            grade,\n            criticalCount:\n              headerStatus['Content-Security-Policy'].present &&\n              hasUnsafeInline(headerStatus['Content-Security-Policy'].value)\n                ? 1\n                : 0,\n            warningCount: Object.keys(headerStatus).filter(\n              h =>\n                !headerStatus[h].present &&\n                !['Strict-Transport-Security', 'Content-Security-Policy'].includes(h)\n            ).length,\n            presentHeaders,\n            configOutput,\n            vulnOutput,\n            headerStatus,\n            originalHeaders: rawHeaders\n          }\n        }\n      }\n    ];\n  } catch (err) {\n    return [{ json: { ...items[0].json, error: err.message } }];\n  }\n}\n\nreturn processSecurityHeaders(items);\n"
      },
      "typeVersion": 2
    },
    {
      "id": "ced29b26-474c-4d62-808a-3284103c9d60",
      "name": "Send Security Report",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1580,
        580
      ],
      "parameters": {
        "sendTo": "=example@here.com",
        "message": "={{ $json.emailHtml }}",
        "options": {},
        "subject": "=Website Security Audit - {{ $json.auditData.url }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "918c0fc4-2f02-4594-bfc9-e36035f2d802",
      "name": "Sticky Note - Setup Instructions",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -820,
        400
      ],
      "parameters": {
        "width": 500,
        "height": 440,
        "content": "## \ube60\ub978 \uc124\uc815 \uac00\uc774\ub4dc\n\n1. **OpenAI API \uc790\uaca9 \uc99d\uba85 \ucd94\uac00**\n   - \uc124\uc815 \u2192 \uc790\uaca9 \uc99d\uba85 \u2192 \uc0c8\ub85c \ub9cc\ub4e4\uae30 \u2192 OpenAI API\ub85c \uc774\ub3d9\n   - platform.openai.com\uc5d0\uc11c API \ud0a4\ub97c \uc785\ub825\ud558\uc138\uc694\n\n2. **Gmail \uc790\uaca9 \uc99d\uba85 \ucd94\uac00**\n   - \uc124\uc815 \u2192 \uc790\uaca9 \uc99d\uba85 \u2192 \uc0c8\ub85c \ub9cc\ub4e4\uae30 \u2192 Gmail OAuth2 API\ub85c \uc774\ub3d9\n   - OAuth \uc124\uc815 \ud504\ub85c\uc138\uc2a4\ub97c \uc644\ub8cc\ud558\uc138\uc694\n\n3. **\uc774\uba54\uc77c \uad6c\uc131 \uc5c5\ub370\uc774\ud2b8**\n   - 'Send Security Report' \ub178\ub4dc\ub97c \uc5f4\uae30\n   - \uae30\ubcf8\uac12\uc5d0\uc11c \ubc1b\ub294 \uc0ac\ub78c \uc774\uba54\uc77c \uc8fc\uc18c\ub97c \ubcc0\uacbd\ud558\uc138\uc694\n\n4. **\uc6cc\ud06c\ud50c\ub85c\uc6b0 \ud65c\uc131\ud654 \ubc0f \ubc30\ud3ec**\n   - \uc624\ub978\ucabd \uc0c1\ub2e8\uc758 'Active' \ud1a0\uae00\uc744 \ud074\ub9ad\ud558\uc138\uc694\n   - \uc591\uc2dd URL\uc744 \ubcf5\uc0ac\ud558\uc5ec \ub2e4\ub978 \uc0ac\ub78c\uacfc \uacf5\uc720\ud558\uac70\ub098 \uc0ac\uc6a9\ud558\uc138\uc694"
      },
      "typeVersion": 1
    },
    {
      "id": "6e31b9b8-ae02-4da4-a75e-5d784b210c64",
      "name": "Sticky Note - OpenAI Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        300,
        120
      ],
      "parameters": {
        "color": 3,
        "width": 420,
        "height": 240,
        "content": "## OpenAI \ubcf4\uc548 \ubd84\uc11d\n\n- OpenAI \uc790\uaca9 \uc99d\uba85\uc744 \ucd94\uac00\ud558\uc138\uc694 (\ud544\uc218)\n- GPT-4o \ubaa8\ub378\uc744 \uc0ac\uc6a9\ud558\uba74 \ub354 \uc790\uc138\ud55c \ubcf4\uc548 \ubd84\uc11d\uc744 \uc81c\uacf5\ud569\ub2c8\ub2e4\n- XSS, \uc815\ubcf4 \uacf5\uac1c, CSRF \ub4f1\uc744 \ubd84\uc11d\ud569\ub2c8\ub2e4\n- \uac01 \uc5d0\uc774\uc804\ud2b8\ub294 \uc6f9\uc0ac\uc774\ud2b8 \ubcf4\uc548\uc758 \ub2e4\uc591\ud55c \uce21\uba74\uc744 \uc2a4\uce94\ud569\ub2c8\ub2e4\n- \ud504\ub85c\ub355\uc158 \uc6a9\ub3c4\ub85c GPT-4o (\ubbf8\ub2c8\uac00 \uc544\ub2cc)\uc73c\ub85c \uc5c5\uadf8\ub808\uc774\ub4dc\ud558\ub294 \uac83\uc744 \uace0\ub824\ud558\uc138\uc694"
      },
      "typeVersion": 1
    },
    {
      "id": "590b1f1c-024d-4002-a8eb-d9dc81528f89",
      "name": "Sticky Note - Email Configuration",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1480,
        220
      ],
      "parameters": {
        "color": 3,
        "width": 360,
        "height": 200,
        "content": "## \ubcf4\uc548 \ubcf4\uace0\uc11c \ubcf4\ub0b4\uae30\n\n- Gmail\uc5d0 \uc548\uc804\ud558\uac8c \uc5f0\uacb0\ud558\uc5ec \uc790\uc138\ud55c \ubcf4\uace0\uc11c\ub97c \ubcf4\ub0c5\ub2c8\ub2e4\n- \ubcf4\uace0\uc11c\uac00 HTML \ud615\uc2dd\uc758 \uc774\uba54\uc77c\ub85c \ubcf4\ub0b4\uc9d1\ub2c8\ub2e4\n- \uc81c\ubaa9 \uc904\uc5d0 \uc2a4\uce94\ub41c URL\uc774 \ud3ec\ud568\ub429\ub2c8\ub2e4\n- Gmail OAuth \uc790\uaca9 \uc99d\uba85\uc744 \uc124\uc815\ud574\uc57c \ud569\ub2c8\ub2e4"
      },
      "typeVersion": 1
    },
    {
      "id": "dc6223f8-a98c-497a-97c9-af39e80e6d66",
      "name": "Sticky Note - Audit Process",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -200,
        780
      ],
      "parameters": {
        "color": 2,
        "width": 420,
        "height": 300,
        "content": "## \ubcf4\uc548 \uac10\uc0ac \ud504\ub85c\uc138\uc2a4\n\n- \uc774 \uc6cc\ud06c\ud50c\ub85c\uc6b0\ub294 \ub450 \uac1c\uc758 \ubcd1\ub82c \ubcf4\uc548 \ubd84\uc11d\uc744 \uc218\ud589\ud569\ub2c8\ub2e4.\n- \uc0c1\ub2e8 \uacbd\ub85c: \ud5e4\ub354, \ucfe0\ud0a4 \ubc0f \ubcf4\uc548 \uad6c\uc131\uc744 \ud655\uc778\ud569\ub2c8\ub2e4.\n- \ud558\ub2e8 \uacbd\ub85c: \ud074\ub77c\uc774\uc5b8\ud2b8 \uce21 \ucde8\uc57d\uc810\uc744 \uc704\ud55c HTML/JavaScript\ub97c \ubd84\uc11d\ud569\ub2c8\ub2e4.\n- \uacb0\uacfc\uac00 \ubcd1\ud569\ub418\uc5b4 \ud3ec\uad04\uc801\uc778 \ubcf4\uace0\uc11c\ub85c \ud615\uc2dd\ud654\ub429\ub2c8\ub2e4.\n- \ubd84\uc11d\uc740 \ube44\uce68\uc785\uc801\uc774\uba70 \ud074\ub77c\uc774\uc5b8\ud2b8 \uce21 \ucf58\ud150\uce20\ub9cc \uac80\uc0ac\ud569\ub2c8\ub2e4."
      },
      "typeVersion": 1
    },
    {
      "id": "cbda16d4-f1f4-491c-b38c-43d7544e129b",
      "name": "Sticky Note - How To Use",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -240,
        240
      ],
      "parameters": {
        "color": 4,
        "width": 400,
        "height": 280,
        "content": "## \uc774 \uc6cc\ud06c\ud50c\ub85c\uc6b0 \uc0ac\uc6a9 \ubc29\ubc95\n\n1. **\uc6cc\ud06c\ud50c\ub85c\uc6b0\ub97c \ubc30\ud3ec\ud558\uace0 \ud65c\uc131\ud654\ud558\uc138\uc694**\n2. **\uc81c\uacf5\ub41c URL\uc744 \ud1b5\ud574 \uc591\uc2dd\uc5d0 \uc811\uadfc\ud558\uc138\uc694**\n3. **\uc2a4\uce94\ud560 \uc6f9\uc0ac\uc774\ud2b8 URL\uc744 \uc785\ub825\ud558\uc138\uc694 (http:// \ub610\ub294 https://\ub97c \ud3ec\ud568\ud574\uc57c \ud568)**\n4. **\uc591\uc2dd\uc744 \uc81c\ucd9c\ud558\uc5ec \ubd84\uc11d\uc744 \ud2b8\ub9ac\uac70\ud558\uc138\uc694**\n5. **\uc790\uc138\ud55c \ubcf4\uc548 \ubcf4\uace0\uc11c\ub97c \uc704\ud574 \uc774\uba54\uc77c\uc744 \ud655\uc778\ud558\uc138\uc694**\n6. **\uacb0\uacfc\ub97c \uac1c\ubc1c \ud300\uacfc \uacf5\uc720\ud558\uc5ec \uc218\uc815 \uc0ac\ud56d\uc744 \uad6c\ud604\ud558\uc138\uc694**"
      },
      "typeVersion": 1
    },
    {
      "id": "4859416f-4de3-43ea-9461-3ead8a38db6e",
      "name": "Sticky Note - Report Formatting",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1160,
        220
      ],
      "parameters": {
        "color": 5,
        "width": 300,
        "height": 280,
        "content": "## \ubcf4\uace0\uc11c \ud615\uc2dd\n\n- \uc544\ub984\ub2f5\uace0 \uc804\ubb38\uc801\uc778 HTML \uc774\uba54\uc77c \ubcf4\uace0\uc11c\ub97c \uc0dd\uc131\ud569\ub2c8\ub2e4\n- \ubc1c\uacac \uc0ac\ud56d\uc5d0 \uae30\ubc18\ud55c \uc2dc\uac01\uc801 \ub4f1\uae09 \ud45c\uc2dc\uae30 (A-F)\n- \uc911\uc694 \ubb38\uc81c\uc640 \uacbd\uace0\uc758 \uac1c\uc218\ub97c \ud3ec\ud568\ud569\ub2c8\ub2e4\n- \uc26c\uc6b4 \uac00\ub3c5\uc131\uc744 \uc704\ud55c \uc0c9\uc0c1 \ucf54\ub529\ub41c \uc139\uc158\n- \ubaa8\ubc14\uc77c \uce5c\ud654\uc801 \uc751\ub2f5\ud615 \ub514\uc790\uc778"
      },
      "typeVersion": 1
    },
    {
      "id": "a02db4c7-2cad-41ff-b5ad-e1b19604a699",
      "name": "Sticky Note - Results Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        840,
        240
      ],
      "parameters": {
        "width": 300,
        "height": 240,
        "content": "## \uacb0\uacfc \ucc98\ub9ac\n\n- AI \ucd9c\ub825 \ubd84\uc11d\ud558\uc5ec \ubcf4\uc548 \ub4f1\uae09 \uacb0\uc815\n- \uc911\uc694 \ubb38\uc81c \ubc0f \uacbd\uace0 \uc138\uae30\n- \ud604\uc7ac \ubcf4\uc548 \ud5e4\ub354 \ucd94\ucd9c\n- \uc774\uba54\uc77c \ubcf4\uace0\uc11c \ud15c\ud50c\ub9bf\uc744 \uc704\ud55c \ub370\uc774\ud130 \uc900\ube44\n- \ubcf4\uace0\uc11c\ub97c \uc704\ud55c \ud0c0\uc784\uc2a4\ud0ec\ud504 \uc0dd\uc131"
      },
      "typeVersion": 1
    },
    {
      "id": "41b834c8-62f7-47e7-9d9d-e0e1244faecb",
      "name": "Extract Headers for Debug",
      "type": "n8n-nodes-base.code",
      "position": [
        200,
        460
      ],
      "parameters": {
        "jsCode": "// Format headers into a readable string\nlet formattedHeaders = '';\nif (items[0].json.headers) {\n  for (const key in items[0].json.headers) {\n    formattedHeaders += `${key}: ${items[0].json.headers[key]}\\n`;\n  }\n}\n\n// Return both the original data and the formatted headers\nreturn [{\n  json: {\n    ...items[0].json,\n    formattedHeaders: formattedHeaders,\n    originalHeaders: items[0].json.headers // Keep the original headers too\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "0b76b396-fc96-41fc-a095-30971dd88271",
      "name": "convert to HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        1400,
        580
      ],
      "parameters": {
        "jsCode": "// Create a direct HTML template with improved styling\nconst auditData = items[0].json.auditData;\n\nfunction formatConfigurationIssues() {\n  if (!auditData.configOutput || auditData.configOutput.trim() === '') {\n    return '<p>No specific configuration issues detected.</p>';\n  }\n\n  try {\n    const config = auditData.configOutput.trim();\n    let html = '';\n    const renderedKeys = new Set();\n\n    const renderBlock = (title, description, impact, recommendation) => `\n      <div style=\"border-left: 4px solid #3498DB; padding: 10px; margin-bottom: 15px;\">\n        <div style=\"font-weight: bold; color: #3498DB;\">${title}</div>\n        ${description ? `<div style=\"margin-top: 5px;\">${description}</div>` : ''}\n        ${impact ? `<div style=\"margin-top: 5px; font-style: italic; color: #7F8C8D;\">Impact: ${impact}</div>` : ''}\n        ${recommendation ? `<div style=\"margin-top: 5px;\"><strong>Recommendation:</strong></div>\n          <pre style=\"background-color: #f8f9fa; padding: 10px; border-radius: 5px; overflow-x: auto; font-family: monospace;\">${recommendation}</pre>` : ''}\n      </div>`;\n\n    const sections = config.split(/(?=^###\\s+)/gm).filter(Boolean);\n\n    for (const section of sections) {\n      const sectionTitleMatch = section.match(/^###\\s+(.*)/);\n      const sectionTitle = sectionTitleMatch?.[1]?.trim() || 'Unnamed Section';\n      const sectionKey = sectionTitle.toLowerCase();\n\n      // Skip \"no issues found\" sections\n      if (/no issues? (found|were found)/i.test(section)) continue;\n\n      const lines = section.split(/\\n+/).filter(line => line.trim() !== '');\n\n      let currentTitle = '';\n      let description = '';\n      let impact = '';\n      let recommendation = '';\n\n      for (let i = 0; i < lines.length; i++) {\n        const line = lines[i].trim();\n\n        // Start of a new numbered or bolded issue\n        const numberedTitle = line.match(/^\\d+\\.\\s+\\*\\*(.*?)\\*\\*/);\n        const bulletTitle = line.match(/^\\*\\*(.*?)\\*\\*/);\n\n        if (numberedTitle || (!currentTitle && bulletTitle)) {\n          // Flush last block\n          if (currentTitle && !renderedKeys.has(`${sectionKey}::${currentTitle.toLowerCase()}`)) {\n            html += renderBlock(currentTitle, description, impact, recommendation);\n            renderedKeys.add(`${sectionKey}::${currentTitle.toLowerCase()}`);\n          }\n\n          currentTitle = (numberedTitle || bulletTitle)[1].trim();\n          description = '';\n          impact = '';\n          recommendation = '';\n          continue;\n        }\n\n        const valueMatch = line.match(/- \\*\\*Value:\\*\\*\\s*`?(.*?)`?$/i);\n        const presentMatch = line.match(/- \\*\\*Present\\?\\*\\*.*?(Yes|No)/i);\n        const descMatch = line.match(/- \\*\\*Description:\\*\\*\\s*(.*)/i);\n        const impactMatch = line.match(/- \\*\\*(?:Impact|Security Implication|Potential Impact):\\*\\*\\s*(.*)/i);\n        const recMatch = line.match(/```(?:\\w*)?\\n([\\s\\S]*?)```/i);\n\n        if (descMatch) {\n          description = descMatch[1].trim();\n        } else if (valueMatch || presentMatch) {\n          const present = presentMatch?.[1]?.trim() || 'Unknown';\n          const value = valueMatch?.[1]?.trim() || '[Not provided]';\n          description = `This header is ${present.toLowerCase()}. Value: ${value}.`;\n        }\n\n        if (impactMatch) {\n          impact = impactMatch[1].trim();\n        }\n\n        if (recMatch) {\n          recommendation = recMatch[1].trim();\n        }\n      }\n\n      // Final block in section\n      if (currentTitle && !renderedKeys.has(`${sectionKey}::${currentTitle.toLowerCase()}`)) {\n        html += renderBlock(currentTitle, description, impact, recommendation);\n        renderedKeys.add(`${sectionKey}::${currentTitle.toLowerCase()}`);\n      }\n    }\n\n    return html || '<p>No configuration issues detected.</p>';\n  } catch (e) {\n    console.error('Error in formatConfigurationIssues:', e);\n    return `<p>Error processing configuration issues: ${e.message}</p>`;\n  }\n}\n\n\n\n// Create header badge HTML\nfunction createHeaderBadge(headerName, isWarning = false) {\n  const isPresent = auditData.headerStatus && \n                   auditData.headerStatus[headerName] && \n                   auditData.headerStatus[headerName].present;\n  \n  const color = isWarning && isPresent ? \"#F39C12\" : (isPresent ? \"#27AE60\" : \"#E74C3C\");\n  const icon = isPresent ? \"\u2713\" : \"\u2717\";\n  \n  return `<span style=\"display: inline-block; margin: 2px; padding: 4px 8px; background-color: ${color}; color: white; border-radius: 4px; font-size: 12px;\">${icon} ${headerName}</span>`;\n}\n\n// Format warnings section\nfunction formatWarningsSection() {\n  if (!auditData.warningCount || auditData.warningCount === 0 || !auditData.headerStatus) {\n    return '<p>No warnings detected.</p>';\n  }\n\n  const csp = Object.entries(auditData.headerStatus).find(([k]) => k.toLowerCase() === 'content-security-policy');\n  const hsts = Object.entries(auditData.headerStatus).find(([k]) => k.toLowerCase() === 'strict-transport-security');\n  const xss = Object.entries(auditData.headerStatus).find(([k]) => k.toLowerCase() === 'x-xss-protection');\n\n  let warnings = '';\n\n  if (csp && csp[1].value && csp[1].value.includes('unsafe-inline')) {\n    warnings += `\n      <div style=\"margin-top: 15px;\">\n        <div style=\"border-left: 4px solid #F39C12; padding: 10px;\">\n          <strong style=\"color: #F39C12;\">Content-Security-Policy: unsafe-inline</strong>\n          <p>The use of 'unsafe-inline' allows potentially malicious scripts to execute.</p>\n        </div>\n      </div>`;\n  }\n\n  if (hsts && hsts[1].value) {\n    const match = hsts[1].value.match(/max-age=(\\d+)/);\n    const age = match ? parseInt(match[1]) : 0;\n    if (age < 2592000) {\n      warnings += `\n        <div style=\"margin-top: 15px;\">\n          <div style=\"border-left: 4px solid #F39C12; padding: 10px;\">\n            <strong style=\"color: #F39C12;\">Strict-Transport-Security</strong>\n            <p>max-age is too low (${age}). Should be at least 2592000 (30 days).</p>\n          </div>\n        </div>`;\n    }\n  }\n\n  if (xss && !xss[1].present) {\n    warnings += `\n      <div style=\"margin-top: 15px;\">\n        <div style=\"border-left: 4px solid #F39C12; padding: 10px;\">\n          <strong style=\"color: #F39C12;\">Missing X-XSS-Protection</strong>\n          <p>This header enables the browser's XSS filter. Lack of it increases XSS risks.</p>\n        </div>\n      </div>`;\n  }\n\n  if (!warnings) {\n    warnings = `\n      <div style=\"margin-top: 15px;\">\n        <div style=\"border-left: 4px solid #F39C12; padding: 10px;\">\n          <strong style=\"color: #F39C12;\">${auditData.warningCount} warnings detected</strong>\n          <p>See the Configuration Issues section below for more info.</p>\n        </div>\n      </div>`;\n  }\n\n  return warnings;\n}\n\nfunction formatLongValue(value) {\n  if (!value || typeof value !== 'string') return '[empty]';\n\n  // Convert URLs into clickable links\n  value = value.replace(/(https?:\\/\\/[^\\s]+)/g, '<a href=\"$1\" style=\"color: #3498DB; text-decoration: none;\" target=\"_blank\">$1</a>');\n\n  // Add line breaks after commas or semicolons for readability\n  if (value.length > 100) {\n    value = value.replace(/([,;])\\s*/g, '$1<br>');\n  }\n\n  return value;\n}\n\nfunction formatDetailedRawHeaders() {\n  const allHeaders = [];\n  const seen = new Set();\n\n  const addHeader = (name, value) => {\n    const key = name.toLowerCase();\n    if (seen.has(key)) return;\n    seen.add(key);\n\n    const status = Object.entries(auditData.headerStatus || {}).find(\n      ([k]) => k.toLowerCase() === name.toLowerCase()\n    );\n    const present = status ? status[1].present : !!value;\n\n    allHeaders.push({\n      name: name.trim(),\n      present,\n      value: value || '[empty]'\n    });\n  };\n\n  Object.entries(auditData.originalHeaders || {}).forEach(([key, value]) => {\n    if (key) addHeader(key, value);\n  });\n\n  const securityHeaders = [\n    'content-security-policy',\n    'strict-transport-security',\n    'x-content-type-options',\n    'x-frame-options',\n    'referrer-policy',\n    'permissions-policy',\n    'x-xss-protection'\n  ];\n\n  const isWarningHeader = (name, value) => {\n    const lower = name.toLowerCase();\n    if (lower === 'strict-transport-security') {\n      const match = value.match(/max-age=(\\d+)/);\n      return match && parseInt(match[1]) < 2592000;\n    }\n    if (lower === 'content-security-policy') return value.includes(\"'unsafe-inline'\");\n    return false;\n  };\n\n  const tableRows = allHeaders.map(header => {\n    const isSecurity = securityHeaders.includes(header.name.toLowerCase());\n    const warning = isSecurity && isWarningHeader(header.name, header.value);\n    const missing = isSecurity && !header.present;\n\n    let bgColor = '#F8F9FA';\n    let textColor = '#333';\n\n    if (isSecurity) {\n      if (missing) {\n        bgColor = '#FFEBEE';\n        textColor = '#C62828';\n      } else if (warning) {\n        bgColor = '#FFF9C4';\n        textColor = '#F57F17';\n      } else {\n        bgColor = '#E8F5E9';\n        textColor = '#2E7D32';\n      }\n    }\n\n    return `\n      <tr style=\"background-color: ${bgColor}; color: ${textColor};\">\n        <td title=\"${isSecurity ? (missing ? 'Missing' : (warning ? 'Needs review' : 'Secure')) : 'Informational'}\" style=\"padding: 8px; font-weight: bold;\">${header.name}</td>\n        <td style=\"padding: 8px; text-align: center;\">${header.present ? 'present' : 'absent'}</td>\n        <td style=\"padding: 8px; word-break: break-word; font-family: monospace;\">${formatLongValue(header.value)}</td>\n      </tr>`;\n  }).join('');\n\n  return `\n    <table style=\"width: 100%; border-collapse: collapse; margin-top: 10px;\">\n      <thead>\n        <tr style=\"background-color: #E0E0E0;\">\n          <th style=\"padding: 10px;\">Header</th>\n          <th style=\"padding: 10px;\">Status</th>\n          <th style=\"padding: 10px;\">Value</th>\n        </tr>\n      </thead>\n      <tbody>\n        ${tableRows}\n      </tbody>\n    </table>`;\n}\n\n// Format additional information section\nfunction formatAdditionalInfo() {\n  const headers = [\n    {\n      name: 'access-control-allow-origin',\n      description: 'This is a very lax CORS policy. Such a policy should only be used on a public CDN.'\n    },\n    {\n      name: 'strict-transport-security',\n      description: 'HTTP Strict Transport Security is an excellent feature to support on your site and strengthens your implementation of TLS by getting the User Agent to enforce the use of HTTPS.'\n    },\n    {\n      name: 'content-security-policy',\n      description: 'Content Security Policy is an effective measure to protect your site from XSS attacks. By whitelisting sources of approved content, you can prevent the browser from loading malicious assets. Analyse this policy in more detail. You can sign up for a free account on Report URI to collect reports about problems on your site.'\n    },\n    {\n      name: 'permissions-policy',\n      description: 'Permissions Policy is a new header that allows a site to control which features and APIs can be used in the browser.'\n    },\n    {\n      name: 'referrer-policy',\n      description: 'Referrer Policy is a new header that allows a site to control how much information the browser includes with navigations away from a document and should be set by all sites.'\n    },\n    {\n      name: 'x-content-type-options',\n      description: 'X-Content-Type-Options stops a browser from trying to MIME-sniff the content type and forces it to stick with the declared content-type. The only valid value for this header is \"X-Content-Type-Options: nosniff\".'\n    },\n    {\n      name: 'x-frame-options',\n      description: 'X-Frame-Options tells the browser whether you want to allow your site to be framed or not. By preventing a browser from framing your site you can defend against attacks like clickjacking.'\n    },\n    {\n      name: 'report-to',\n      description: 'Report-To enables the Reporting API. This allows a website to collect reports from the browser about various errors that may occur. You can sign up for a free account on Report URI to collect these reports.'\n    },\n    {\n      name: 'nel',\n      description: 'Network Error Logging is a new header that instructs the browser to send reports during various network or application errors. You can sign up for a free account on Report URI to collect these reports.'\n    },\n    {\n      name: 'server',\n      description: 'Server value has been changed. Typically you will see values like \"Microsoft-IIS/8.0\" or \"nginx 1.7.2\".'\n    }\n  ];\n  \n  let rows = '';\n  \n  for (const header of headers) {\n    const isSecurityHeader = ['content-security-policy', 'strict-transport-security', 'x-content-type-options', 'x-frame-options', 'referrer-policy', 'permissions-policy'].includes(header.name);\n    const headerColor = isSecurityHeader ? '#27AE60' : '#3498DB';\n    \n    rows += `\n      <tr>\n        <td style=\"padding: 8px; border-bottom: 1px solid #eee; color: ${headerColor}; font-weight: bold;\">${header.name}</td>\n        <td style=\"padding: 8px; border-bottom: 1px solid #eee;\">${header.description}</td>\n      </tr>\n    `;\n  }\n  \n  return `\n    <table style=\"width: 100%; border-collapse: collapse; margin-top: 10px;\">\n      <tbody>\n        ${rows}\n      </tbody>\n    </table>\n  `;\n}\n\nfunction formatSecurityGrade() {\n  const gradeColors = {\n    'A+': '#27AE60',\n    'A': '#27AE60',\n    'A-': '#27AE60',\n    'B+': '#3498DB',\n    'B': '#3498DB',\n    'B-': '#3498DB',\n    'C+': '#F39C12',\n    'C': '#F39C12',\n    'C-': '#F39C12',\n    'D+': '#E74C3C',\n    'D': '#E74C3C',\n    'D-': '#E74C3C',\n    'F': '#E74C3C'\n  };\n  \n  return `<div class=\"grade\" style=\"font-size: 64px; font-weight: bold; width: 100px; height: 100px; line-height: 100px; text-align: center; background-color: ${gradeColors[auditData.grade] || '#E74C3C'}; color: white; border-radius: 5px; margin: 0 auto;\">${auditData.grade}</div>`;\n}\n\nfunction formatCriticalVulnerabilities() {\n  if (!auditData.vulnOutput || auditData.vulnOutput.trim() === '') {\n    return '<p>No vulnerabilities detected.</p>';\n  }\n\n  try {\n    const vuln = auditData.vulnOutput.trim();\n    let html = '';\n    const renderedTitles = new Set();\n\n    // Match sections like ## Category (e.g., ## Critical Vulnerabilities)\n    const categories = vuln.split(/(?=^##\\s+)/gm).filter(Boolean);\n\n    for (const categoryBlock of categories) {\n      const categoryMatch = categoryBlock.match(/^##\\s+(.*)/);\n      const categoryTitle = categoryMatch?.[1]?.trim() || 'Uncategorized';\n\n      // Find numbered items: 1. **Title**\n      const vulns = categoryBlock.split(/(?=^\\d+\\.\\s+\\*\\*)/gm).filter(Boolean);\n\n      for (const vulnBlock of vulns) {\n        const titleMatch = vulnBlock.match(/^\\d+\\.\\s+\\*\\*(.*?)\\*\\*/);\n        const title = titleMatch?.[1]?.trim() || 'Unnamed Vulnerability';\n        const key = `${categoryTitle}::${title}`.toLowerCase();\n        if (renderedTitles.has(key)) continue;\n\n        const descriptionMatch = vulnBlock.match(/\\*\\*Description\\*\\*:?\\s*([\\s\\S]*?)(?=\\n\\*\\*|\\n$)/i);\n        const impactMatch = vulnBlock.match(/\\*\\*(?:Impact|Potential Impact)\\*\\*:?\\s*([\\s\\S]*?)(?=\\n\\*\\*|\\n$)/i);\n        const recommendationMatch = vulnBlock.match(/\\*\\*(?:Recommendation|Mitigation|Fix)\\*\\*:?\\s*([\\s\\S]*?)(?=\\n\\*\\*|\\n$)/i);\n\n        const description = descriptionMatch?.[1]?.trim() || '';\n        const impact = impactMatch?.[1]?.trim() || '';\n        const recommendation = recommendationMatch?.[1]?.trim() || '';\n\n        if (description || impact || recommendation) {\n          html += `\n            <div style=\"border-left: 4px solid #E74C3C; padding: 10px; margin-bottom: 15px;\">\n              <div style=\"font-weight: bold; color: #E74C3C;\">${title}</div>\n              ${description ? `<div style=\"margin-top: 5px;\">${description}</div>` : ''}\n              ${impact ? `<div style=\"margin-top: 5px; font-style: italic; color: #7F8C8D;\">Impact: ${impact}</div>` : ''}\n              ${recommendation ? `<div style=\"margin-top: 5px;\"><strong>Recommendation:</strong> ${recommendation}</div>` : ''}\n            </div>`;\n          renderedTitles.add(key);\n        }\n      }\n    }\n\n    return html || '<p>No vulnerabilities parsed from output.</p>';\n  } catch (e) {\n    console.error('Error in formatCriticalVulnerabilities:', e);\n    return `<p>Error processing vulnerabilities: ${e.message}</p>`;\n  }\n}\n\n\n// Generate all security header badges\nfunction generateAllHeaderBadges() {\n  // Only include the necessary security headers\n  const securityHeaders = [\n    'Content-Security-Policy',\n    'Strict-Transport-Security',\n    'X-Content-Type-Options',\n    'X-Frame-Options',\n    'Referrer-Policy',\n    'Permissions-Policy'\n  ];\n  \n  let badges = '';\n  securityHeaders.forEach(header => {\n                      \n    const isWarning = header === 'Strict-Transport-Security' &&\n                  auditData.headerStatus?.[header]?.value &&\n                  parseInt(auditData.headerStatus[header].value.match(/max-age=(\\d+)/)?.[1] || 0) < 2592000;\n    \n    badges += createHeaderBadge(header, isWarning);\n  });\n  \n  return badges;\n}\n\n<!-- Modify the HTML to directly access auditData.originalHeaders or allHeaders -->\nconst html = `<!DOCTYPE html>\n<html>\n<head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>Website Security Audit Report</title>\n    <style>\n        body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f9f9f9; }\n        .container { max-width: 950px; margin: 0 auto; }\n        .header { background-color: #2c3e50; color: white; padding: 25px 20px; text-align: center; }\n        .header h1 { color: white; font-size: 28px; margin: 0; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }\n        .content { padding: 20px; }\n        .summary-box { background-color: #EBF5FB; padding: 15px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }\n        .warning-box { background-color: #FEF5E7; padding: 15px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }\n        .headers-box { background-color: #F5F7FA; padding: 15px; margin-bottom: 20px; border-radius: 5px; }\n        .findings-box { background-color: white; padding: 15px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }\n        .raw-headers-box { background-color: #F5F7FA; padding: 15px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }\n        .additional-info-box { background-color: #F5F7FA; padding: 15px; margin-bottom: 20px; border-radius: 5px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }\n        .details-table { width: 100%; border-collapse: collapse; }\n        .details-table th { text-align: left; padding: 8px; background-color: #f2f2f2; }\n        .details-table td { padding: 8px; border-bottom: 1px solid #eee; }\n        .header-badges { margin-top: 10px; }\n        h1, h2, h3 { color: #2c3e50; }\n        .critical-item { border-left: 4px solid #E74C3C; padding: 10px; margin-bottom: 15px; }\n        .critical-title { font-weight: bold; color: #E74C3C; }\n        .config-item { border-left: 4px solid #3498DB; padding: 10px; margin-bottom: 15px; }\n        .config-title { font-weight: bold; color: #3498DB; }\n        pre { background-color: #f8f9fa; padding: 10px; border-radius: 5px; overflow-x: auto; font-family: monospace; margin-top: 5px; }\n    </style>\n</head>\n<body>\n    <div class=\"container\">\n        <!-- Report Header -->\n        <div class=\"header\">\n            <h1 style=\"color: white; text-shadow: 1px 1px 2px rgba(0,0,0,0.5);\">Website Security Audit Report</h1>\n        </div>\n        \n        <div class=\"content\">\n            <!-- Security Report Summary -->\n            <div class=\"summary-box\">\n                <h2>Security Report Summary</h2>\n                <table style=\"width: 100%;\">\n                    <tr>\n                        <td style=\"width: 120px;\" valign=\"top\">\n                            ${formatSecurityGrade()}\n                        </td>\n                        <td valign=\"top\">\n                            <table style=\"width: 100%;\">\n                                <tr>\n                                    <td><strong>Site:</strong></td>\n                                    <td><a href=\"${auditData.url}\" style=\"color: #3498db;\">${auditData.url}</a></td>\n                                </tr>\n                                <tr>\n                                    <td><strong>Report Time:</strong></td>\n                                    <td>${auditData.timestamp}</td>\n                                </tr>\n                                <tr>\n                                    <td valign=\"top\"><strong>Headers:</strong></td>\n                                    <td>\n                                        <div class=\"header-badges\">\n                                            ${generateAllHeaderBadges()}\n                                        </div>\n                                    </td>\n                                </tr>\n                                <tr>\n                                    <td><strong>Critical Issues:</strong></td>\n                                    <td>${auditData.criticalCount || 0}</td>\n                                </tr>\n                                <tr>\n                                    <td><strong>Warnings:</strong></td>\n                                    <td>${auditData.warningCount || 0}</td>\n                                </tr>\n                            </table>\n                        </td>\n                    </tr>\n                </table>\n            </div>\n\n            <!-- Warnings Section -->\n            <div class=\"warning-box\">\n                <h2>Warnings</h2>\n                ${formatWarningsSection()}\n            </div>\n\n            <!-- Raw Headers Section -->\n            <div class=\"raw-headers-box\">\n                <h2>Raw Headers</h2>\n                ${formatDetailedRawHeaders()}\n            </div>\n\n            <!-- Security Findings -->\n            <div class=\"findings-box\">\n                <h2>Security Findings</h2>\n                \n                <!-- Vulnerabilities -->\n                <h3>Vulnerabilities</h3>\n                ${formatCriticalVulnerabilities()}\n                \n                <!-- Configuration Issues -->\n                <h3>Configuration Issues</h3>\n                ${formatConfigurationIssues()}\n            </div>\n            \n            <div class=\"additional-info-box\">\n              <h2>Additional Information</h2>\n              ${formatAdditionalInfo()}\n            </div>\n            \n            <!-- Implementation Guide -->\n            <div class=\"findings-box\">\n                <h2>Implementation Guide</h2>\n                <p>This report highlights security issues detected through client-side analysis. For a comprehensive security assessment, consider engaging a professional penetration tester.</p>\n                \n                <div style=\"background-color: #eafaf1; padding: 15px; margin-top: 15px; border-left: 4px solid #2ecc71; border-radius: 3px;\">\n                    <p><strong>To implement the fixes above:</strong></p>\n                    <ol style=\"padding-left: 20px; margin-top: 10px;\">\n                        <li>Work with your development team to address each issue in order of criticality</li>\n                        <li>Retest after implementing each fix</li>\n                        <li>Consider implementing a web application firewall for additional protection</li>\n                    </ol>\n                </div>\n            </div>\n            \n            <!-- Footer -->\n            <div style=\"text-align: center; padding: 20px; font-size: 12px; color: #777;\">\n                <p>This report was automatically generated and represents an automated assessment of publicly accessible aspects of your website. For a more comprehensive security assessment, consider engaging with a professional security consultant.</p>\n                <p>&copy; 2025 Website Security Scanner | Generated on ${auditData.timestamp}</p>\n            </div>\n        </div>\n    </div>\n</body>\n</html>`;\n\nreturn [{\n  json: {\n    ...items[0].json,\n    emailHtml: html\n  }\n}];"
      },
      "typeVersion": 2
    }
  ],
  "connections": {
    "Scrape Website": {
      "main": [
        [
          {
            "node": "Security Vulnerabilities Audit",
            "type": "main",
            "index": 0
          },
          {
            "node": "Extract Headers for Debug",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "convert to HTML": {
      "main": [
        [
          {
            "node": "Send Security Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Landing Page Url": {
      "main": [
        [
          {
            "node": "Scrape Website",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Audit Results": {
      "main": [
        [
          {
            "node": "convert to HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Security Results": {
      "main": [
        [
          {
            "node": "Aggregate Audit Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Audit Results": {
      "main": [
        [
          {
            "node": "Process Audit Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Content Analysis": {
      "ai_languageModel": [
        [
          {
            "node": "Security Vulnerabilities Audit",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Headers Analysis": {
      "ai_languageModel": [
        [
          {
            "node": "Security Configuration Audit",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Extract Headers for Debug": {
      "main": [
        [
          {
            "node": "Security Configuration Audit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Security Configuration Audit": {
      "main": [
        [
          {
            "node": "Merge Security Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Security Vulnerabilities Audit": {
      "main": [
        [
          {
            "node": "Merge Security Results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}

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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

3314. Uses formTrigger, httpRequest, lmChatOpenAi, agent. Event-driven trigger; 19 nodes.

Source: https://github.com/n8nKOR/n8n-shared-workflow/blob/62a671327e906c22a40d290b339ff6d2373f8d75/workflows/n8nworkflows/ai/3314.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This workflow automates end-to-end contract and invoice management using AI intelligence. It processes proposals through intelligent contract generation, approval workflows, and automated invoicing. O

Form Trigger, Data Table, Agent +4
AI & RAG

Automates SaaS operations by consolidating user management, AI-driven support triage, analytics, and billing into one unified system. User signups flow through registration, support requests route via

Form Trigger, Data Table, Agent +7
AI & RAG

The workflow runs every hour with a randomized delay of 5–20 minutes to help distribute load. It records the exact date and time a lead is emailed so you can track outreach. Follow-ups are automatical

Google Sheets, Agent, OpenAI Chat +5
AI & RAG

This n8n workflow automates turning short user ideas into production-ready real-estate marketing assets (photorealistic images and optional 360° videos). A form submission seeds a prompt board → an LL

Form Trigger, Google Sheets, Agent +6
AI & RAG

This workflow automatically analyzes a website for UX and SEO quality. It uses Airtop for realistic web scraping, OpenAI for structured evaluation of metadata (title, description, and overall SEO sign

Airtop Tool, Form Trigger, OpenAI Chat +6