{ "meta": { "instanceId": "c2589fa234defe76e8a1321c3a7d0a73579d0120d64d927e88f5e3be584ae8d4" }, "nodes": [ { "id": "634f2fc5-0ba7-42ad-bdf5-ade3415dd288", "name": "Landing Page Url", "type": "n8n-nodes-base.formTrigger", "position": [ -200, 580 ], "webhookId": "afe067a5-4878-4c9d-b746-691f77190f54", "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": { "id": "yZ0AIg9abV8HJadB", "name": "OpenAi account" } }, "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": { "id": "yZ0AIg9abV8HJadB", "name": "OpenAi account" } }, "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 – Issues that could lead to immediate compromise\n- Information Leakage – Sensitive data exposed in page source\n- Client-Side Weaknesses – 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 – Missing or misconfigured security headers\n- Cookie Security – Insecure cookie configurations\n- Content Security – 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": "// ✅ 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 // ⛏️ 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 ], "webhookId": "2979e4dc-1689-447e-8cd4-eb907b4eedf4", "parameters": { "sendTo": "=example@here.com", "message": "={{ $json.emailHtml }}", "options": {}, "subject": "=Website Security Audit - {{ $json.auditData.url }}" }, "credentials": { "gmailOAuth2": { "id": "9CEpbF4jIWb2OETv", "name": "Gmail account" } }, "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": "## Quick Setup Guide\n\n1. **Add OpenAI API Credentials**\n - Go to Settings → Credentials → New → OpenAI API\n - Enter your API key from platform.openai.com\n\n2. **Add Gmail Credentials**\n - Go to Settings → Credentials → New → Gmail OAuth2 API\n - Complete the OAuth setup process\n\n3. **Update Email Configuration**\n - Open the 'Send Security Report' node\n - Change the recipient email address from the default\n\n4. **Activate and Deploy Workflow**\n - Click 'Active' toggle in the top right\n - Copy the form URL to share with others or use yourself" }, "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 Security Analysis\n\n- Add your OpenAI credentials (required)\n- Using GPT-4o models provides more detailed security analysis\n- Analyzes for XSS, information disclosure, CSRF, and more\n- Each agent scans different aspects of website security\n- Consider upgrading to GPT-4o (not mini) for production use" }, "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": "## Send Security Report\n\n- Connects securely to Gmail for sending detailed reports\n- Report is sent as HTML formatted email\n- Subject line includes the scanned URL\n- Requires Gmail OAuth credentials to be set up" }, "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": "## Security Audit Process\n\n- This workflow performs two parallel security analyses\n- Top path: Checks headers, cookies, and security configurations\n- Bottom path: Analyzes HTML/JavaScript for client-side vulnerabilities\n- Results are merged and formatted into a comprehensive report\n- Analysis is non-invasive and only examines client-side content" }, "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": "## How To Use This Workflow\n\n1. **Deploy the workflow** and activate it\n2. **Access the form** via the provided URL\n3. **Enter any website URL** to scan (must include http:// or https://)\n4. **Submit the form** to trigger the analysis\n5. **Check your email** for the detailed security report\n6. **Share the results** with your development team to implement fixes" }, "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": "## Report Formatting\n\n- Creates beautiful, professional HTML email report\n- Visual grade indicator (A-F) based on findings\n- Includes count of critical issues and warnings\n- Color-coded sections for easy readability\n- Mobile-friendly responsive design" }, "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": "## Results Processing\n\n- Analyzes AI output to determine security grade\n- Counts critical issues and warnings\n- Extracts present security headers\n- Prepares data for the email report template\n- Generates timestamp for the report" }, "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 '
No specific configuration issues detected.
';\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${recommendation}` : ''}\n
No configuration issues detected.
';\n } catch (e) {\n console.error('Error in formatConfigurationIssues:', e);\n return `Error processing configuration issues: ${e.message}
`;\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 ? \"✓\" : \"✗\";\n \n return `${icon} ${headerName}`;\n}\n\n// Format warnings section\nfunction formatWarningsSection() {\n if (!auditData.warningCount || auditData.warningCount === 0 || !auditData.headerStatus) {\n return 'No warnings detected.
';\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 += `\nThe use of 'unsafe-inline' allows potentially malicious scripts to execute.
\nmax-age is too low (${age}). Should be at least 2592000 (30 days).
\nThis header enables the browser's XSS filter. Lack of it increases XSS risks.
\nSee the Configuration Issues section below for more info.
\nHeader | \nStatus | \nValue | \n
---|
No vulnerabilities detected.
';\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 += `\nNo vulnerabilities parsed from output.
';\n } catch (e) {\n console.error('Error in formatCriticalVulnerabilities:', e);\n return `Error processing vulnerabilities: ${e.message}
`;\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\nconst html = `\n\n\n \n \n\n ${formatSecurityGrade()}\n | \n\n
| \n
This report highlights security issues detected through client-side analysis. For a comprehensive security assessment, consider engaging a professional penetration tester.
\n \nTo implement the fixes above:
\nThis 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.
\n© 2025 Website Security Scanner | Generated on ${auditData.timestamp}
\n