AutomationFlowsAI & RAG › Security Headers Audit

Security Headers Audit

Security-Headers-Audit. Uses httpRequest, googleSheets, gmail, agent. Event-driven trigger; 18 nodes.

Event trigger★★★★☆ complexityAI-powered18 nodesHTTP RequestGoogle SheetsGmailAgentOpenRouter ChatForm Trigger
AI & RAG Trigger: Event Nodes: 18 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": [
    {
      "parameters": {
        "content": "## Security Headers Verification Audit\n\nThis workflow audits websites for critical security headers:\n\n\ud83d\udd12 **Headers Checked:**\n- Strict-Transport-Security (HSTS)\n- Content-Security-Policy (CSP)\n- X-Frame-Options (Clickjacking)\n- X-Content-Type-Options (MIME sniffing)\n- Referrer-Policy (Info leakage)\n- Permissions-Policy (Feature control)\n- Cache-Control (Auth page caching)\n\n\ud83d\udcca **Process:**\n1. Input URL to audit\n2. HTTP HEAD request to each URL\n3. Evaluate header presence & strength\n4. Generate security score & report\n5. Export findings to Google Sheets\n6. Send alert email for critical issues",
        "height": 476,
        "width": 520,
        "color": 6
      },
      "id": "0a98c5df-6157-4783-a612-56534fb61881",
      "name": "Workflow Documentation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        96,
        -16
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "\u2699\ufe0f **Configuration Required:**\n\n1. **Input URLs**: Modify 'URL List' node with target domains\n2. **Google Sheets**: \n   - Create a new spreadsheet for audit results\n   - Add Google Sheets credentials\n   - Update spreadsheet ID in 'Export to Sheets' node\n3. **Email Alerts**: \n   - Add Gmail/SMTP credentials for notifications\n   - Configure recipient emails in 'Send Alert' node\n4. **Scheduling**: \n   - Enable 'Security Audit Trigger' for automated runs\n   - Set desired frequency (daily/weekly recommended)\n\n\ud83d\udd0d **Customization:**\n- Adjust security scoring thresholds in 'Evaluate Headers' node\n- Modify alert criteria in 'Check Critical Issues' node\n- Add custom headers to check in HTTP request evaluation",
        "height": 448,
        "width": 580,
        "color": 3
      },
      "id": "e12bb523-3fbe-4ebe-90e1-81adcf9a2c82",
      "name": "Setup Instructions",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        0
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "urls-to-audit",
              "name": "urlsToAudit",
              "type": "string",
              "value": "={{ $json.Site }}"
            },
            {
              "id": "audit-timestamp",
              "name": "auditTimestamp",
              "type": "string",
              "value": "={{ $now.format('yyyy-MM-dd HH:mm:ss') }}"
            }
          ]
        },
        "options": {}
      },
      "id": "ebbf79f6-2dbb-4d3a-bcdf-108f533266b8",
      "name": "URL List",
      "type": "n8n-nodes-base.set",
      "position": [
        416,
        512
      ],
      "typeVersion": 3.4
    },
    {
      "parameters": {
        "url": "=https://{{ $json.urlsToAudit }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {}
          ]
        },
        "options": {
          "redirect": {
            "redirect": {}
          },
          "response": {
            "response": {
              "fullResponse": true
            }
          },
          "timeout": 10000
        }
      },
      "id": "008e3214-538e-40eb-b8c2-c92dceefc4d1",
      "name": "Fetch Headers",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        624,
        512
      ],
      "typeVersion": 4.2
    },
    {
      "parameters": {
        "jsCode": "const {urlsToAudit: url, headers = {}, auditTimestamp: timestamp} = $input.first().json; const h = Object.fromEntries(Object.entries(headers).map(([k,v]) => [k.toLowerCase(), v])); const checks = ['strict-transport-security','content-security-policy','x-frame-options','x-content-type-options','referrer-policy','permissions-policy','cache-control']; const securityChecks = Object.fromEntries(checks.map(name => [name, {present: !!h[name], value: h[name] || null, score: 0, issues: []}])); return [{json: {url, timestamp, headers: h, securityChecks, isAuthPage: url && (url.includes('login') || url.includes('auth') || url.includes('admin'))}}];"
      },
      "id": "f708ad1f-ad51-4caf-a135-8d9c6ac6ad69",
      "name": "Parse Headers",
      "type": "n8n-nodes-base.code",
      "position": [
        816,
        512
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "const { headers: h, securityChecks, isAuthPage } = $input.first().json; const evaluateHSTS = (hsts, val) => { if (hsts.present) { const maxAge = val.match(/max-age=(\\d+)/); hsts.score = maxAge && parseInt(maxAge[1]) >= 31536000 ? 10 : 7; if (hsts.score === 7) hsts.issues.push('max-age less than 1 year'); if (val.includes('includeSubDomains')) hsts.score += 2; if (val.includes('preload')) hsts.score += 1; } else hsts.issues.push('HSTS header missing - vulnerable to downgrade attacks'); return hsts; }; const evaluateCSP = (csp, val) => { if (csp.present) { csp.score = 8; if (val.includes(\"'unsafe-eval'\")) { csp.score -= 3; csp.issues.push('unsafe-eval allows dangerous script execution'); } if (val.includes(\"'unsafe-inline'\")) { csp.score -= 2; csp.issues.push('unsafe-inline reduces XSS protection'); } if (val.includes('*') && !val.includes('data:')) { csp.score -= 2; csp.issues.push('wildcard (*) directive too permissive'); } } else csp.issues.push('CSP header missing - vulnerable to XSS attacks'); return csp; }; const evaluateXFO = (xfo, val) => { if (xfo.present) { const lowerVal = val.toLowerCase(); xfo.score = lowerVal === 'deny' ? 10 : lowerVal === 'sameorigin' ? 8 : 5; if (xfo.score === 5) xfo.issues.push('ALLOW-FROM directive is deprecated'); } else xfo.issues.push('X-Frame-Options missing - vulnerable to clickjacking'); return xfo; }; const evaluateXCTO = (xcto, val) => { if (xcto.present) { xcto.score = val.toLowerCase() === 'nosniff' ? 10 : 5; if (xcto.score === 5) xcto.issues.push('Invalid value - should be \"nosniff\"'); } else xcto.issues.push('X-Content-Type-Options missing - vulnerable to MIME sniffing'); return xcto; }; const evaluateRP = (rp, val) => { if (rp.present) { const lowerVal = val.toLowerCase(); const strongPolicies = ['no-referrer', 'strict-origin', 'strict-origin-when-cross-origin']; rp.score = strongPolicies.includes(lowerVal) ? 10 : ['same-origin', 'origin'].includes(lowerVal) ? 7 : 4; if (rp.score === 4) rp.issues.push('Weak referrer policy - potential information leakage'); } else rp.issues.push('Referrer-Policy missing - may leak sensitive URLs'); return rp; }; const evaluatePP = (pp, val) => { if (pp.present) { pp.score = 8; if (!val.includes('camera') || !val.includes('microphone')) pp.issues.push('Consider restricting camera/microphone access'); } else pp.issues.push('Permissions-Policy missing - no feature access control'); return pp; }; const evaluateCC = (cc, val, isAuthPage) => { if (cc.present) { const lowerVal = val.toLowerCase(); if (isAuthPage) { cc.score = lowerVal.includes('no-cache') || lowerVal.includes('no-store') ? 10 : 3; if (cc.score === 3) cc.issues.push('Authentication pages should not be cached'); } else cc.score = 7; } else { cc.issues.push(isAuthPage ? 'CRITICAL: Authentication page missing Cache-Control' : 'Cache-Control header missing'); } return cc; }; const evaluated = { 'strict-transport-security': evaluateHSTS(securityChecks['strict-transport-security'], h['strict-transport-security']), 'content-security-policy': evaluateCSP(securityChecks['content-security-policy'], h['content-security-policy']), 'x-frame-options': evaluateXFO(securityChecks['x-frame-options'], h['x-frame-options']), 'x-content-type-options': evaluateXCTO(securityChecks['x-content-type-options'], h['x-content-type-options']), 'referrer-policy': evaluateRP(securityChecks['referrer-policy'], h['referrer-policy']), 'permissions-policy': evaluatePP(securityChecks['permissions-policy'], h['permissions-policy']), 'cache-control': evaluateCC(securityChecks['cache-control'], h['cache-control'], isAuthPage) }; return [{ json: evaluated }];"
      },
      "id": "cf80e5e4-a521-4b20-afb6-c8485979c144",
      "name": "Security Scorer",
      "type": "n8n-nodes-base.code",
      "position": [
        1008,
        512
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "const { url, timestamp } = $('Parse Headers').first().json; const securityChecks = $input.first().json; const actualScore = Object.values(securityChecks).reduce((sum, check) => sum + check.score, 0); const securityScorePercentage = Math.round((actualScore / 70) * 100); const grades = [85, 65, 50, 35]; let securityGrade = ['A', 'B', 'C', 'D', 'F'][grades.findIndex(g => securityScorePercentage >= g) + 1] || 'A'; const hasCSP = securityChecks['content-security-policy'].present; const hasPermissions = securityChecks['permissions-policy'].present; const hasHSTS = securityChecks['strict-transport-security'].present; const hasFrameOptions = securityChecks['x-frame-options'].present; const hasNoSniff = securityChecks['x-content-type-options'].present; const hasReferrer = securityChecks['referrer-policy'].present; const coreHeadersCount = [hasHSTS, hasFrameOptions, hasNoSniff, hasReferrer].filter(Boolean).length; if (coreHeadersCount >= 4 && !hasCSP && !hasPermissions && securityScorePercentage >= 60) { if (securityGrade === 'C' || securityGrade === 'D') { securityGrade = 'B'; } } if (!hasHSTS && !hasCSP && securityGrade !== 'F') { if (securityGrade === 'A' || securityGrade === 'B') { securityGrade = 'C'; } } const allIssues = Object.entries(securityChecks).flatMap(([header, check]) => check.issues.map(issue => ({ header, severity: ['strict-transport-security', 'content-security-policy'].includes(header) ? 'HIGH' : ['x-frame-options', 'x-content-type-options'].includes(header) ? 'MEDIUM' : 'LOW', issue }))); const criticalIssues = allIssues.filter(issue => issue.severity === 'HIGH' || (issue.header === 'cache-control' && issue.issue.includes('CRITICAL'))); return [{json: {url, timestamp, securityScore: securityScorePercentage, securityGrade, actualScore, maxPossibleScore: 70, headers: securityChecks, issues: allIssues, criticalIssues, hasCriticalIssues: criticalIssues.length > 0}}];"
      },
      "id": "3f8aa7e8-dbe4-4c40-a30d-cf165f804618",
      "name": "Grade Calculator",
      "type": "n8n-nodes-base.code",
      "position": [
        1008,
        704
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "timestamp",
              "name": "Timestamp",
              "type": "string",
              "value": "={{ $('URL List').item.json.auditTimestamp }}"
            },
            {
              "id": "url",
              "name": "URL",
              "type": "string",
              "value": "={{ $('URL List').item.json.urlsToAudit }}"
            },
            {
              "id": "security-grade",
              "name": "Security Grade",
              "type": "string",
              "value": "={{ $json.securityGrade }}"
            },
            {
              "id": "security-score",
              "name": "Security Score (%)",
              "type": "number",
              "value": "={{ $json.securityScore }}"
            },
            {
              "id": "raw-score",
              "name": "Raw Score",
              "type": "string",
              "value": "={{ $json.actualScore + '/' + $json.maxPossibleScore }}"
            },
            {
              "id": "hsts",
              "name": "HSTS",
              "type": "string",
              "value": "={{ $json.headers['strict-transport-security'].present ? '\u2713' : '\u2717' }}"
            },
            {
              "id": "csp",
              "name": "CSP",
              "type": "string",
              "value": "={{ $json.headers['content-security-policy'].present ? '\u2713' : '\u2717' }}"
            },
            {
              "id": "xfo",
              "name": "X-Frame-Options",
              "type": "string",
              "value": "={{ $json.headers['x-frame-options'].present ? '\u2713' : '\u2717' }}"
            },
            {
              "id": "xcto",
              "name": "X-Content-Type-Options",
              "type": "string",
              "value": "={{ $json.headers['x-content-type-options'].present ? '\u2713' : '\u2717' }}"
            },
            {
              "id": "rp",
              "name": "Referrer-Policy",
              "type": "string",
              "value": "={{ $json.headers['referrer-policy'].present ? '\u2713' : '\u2717' }}"
            },
            {
              "id": "pp",
              "name": "Permissions-Policy",
              "type": "string",
              "value": "={{ $json.headers['permissions-policy'].present ? '\u2713' : '\u2717' }}"
            },
            {
              "id": "cc",
              "name": "Cache-Control",
              "type": "string",
              "value": "={{ $json.headers['cache-control'].present ? '\u2713' : '\u2717' }}"
            },
            {
              "id": "total-issues",
              "name": "Total Issues",
              "type": "number",
              "value": "={{ $json.issues.length }}"
            },
            {
              "id": "critical-issues",
              "name": "Critical Issues",
              "type": "number",
              "value": "={{ $json.criticalIssues.length }}"
            },
            {
              "id": "issue-summary",
              "name": "Issue Summary",
              "type": "string",
              "value": "={{ $json.issues.length > 0 ? $json.issues.map(issue => '[' + issue.severity + '] ' + issue.header.toUpperCase() + ': ' + issue.issue).join(' | ') : 'No issues found' }}"
            }
          ]
        },
        "options": {}
      },
      "id": "2a126807-e887-462e-a69e-30a7cb8e2dde",
      "name": "Format Report",
      "type": "n8n-nodes-base.set",
      "position": [
        1312,
        528
      ],
      "typeVersion": 3.4
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "1m6C7qa-zGFSo6sFDj8KEgEMO-Ydi8fUv_EJqgSQDfOs",
          "mode": "list",
          "cachedResultName": "Security",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1m6C7qa-zGFSo6sFDj8KEgEMO-Ydi8fUv_EJqgSQDfOs/edit?usp=drivesdk"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Security Headers Verification",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1m6C7qa-zGFSo6sFDj8KEgEMO-Ydi8fUv_EJqgSQDfOs/edit#gid=0"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "Timestamp"
          ],
          "schema": [
            {
              "id": "Timestamp",
              "displayName": "Timestamp",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "URL",
              "displayName": "URL",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Security Grade",
              "displayName": "Security Grade",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Security Score (%)",
              "displayName": "Security Score (%)",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Raw Score",
              "displayName": "Raw Score",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "HSTS",
              "displayName": "HSTS",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "CSP",
              "displayName": "CSP",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "X-Frame-Options",
              "displayName": "X-Frame-Options",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "X-Content-Type-Options",
              "displayName": "X-Content-Type-Options",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Referrer-Policy",
              "displayName": "Referrer-Policy",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Permissions-Policy",
              "displayName": "Permissions-Policy",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Cache-Control",
              "displayName": "Cache-Control",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Total Issues",
              "displayName": "Total Issues",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Critical Issues",
              "displayName": "Critical Issues",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Issue Summary",
              "displayName": "Issue Summary",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "useAppend": true
        }
      },
      "id": "2ca24326-d335-4215-85fc-8b191c50185d",
      "name": "Export to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1472,
        528
      ],
      "typeVersion": 4.4,
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Make sure these node names match your actual workflow node names exactly.\n// For example, if your node is named 'EvaluateHeaders', change accordingly:\n// const evaluationData = $('EvaluateHeaders').first()?.json || {};\nconst evaluationData = $('Grade Calculator').first()?.json || {};\nconst urlData = $('URL List').first()?.json || {};\nconst aiRemediation = $('AI Remediation Agent').first()?.json?.output || $('AI Remediation Agent').first()?.json?.text || $('AI Remediation Agent').first()?.json?.response || $('AI Remediation Agent').first()?.json || 'AI remediation instructions not available'; const url = urlData.urlsToAudit || 'Unknown URL'; const timestamp = urlData.auditTimestamp || 'Unknown'; const grade = evaluationData.securityGrade || 'F'; const score = Math.round(evaluationData.securityScore || 0); const actualScore = evaluationData.actualScore || 0; const headersData = evaluationData.headers || {}; const detailedIssues = evaluationData.issues || []; return [{ json: { url, timestamp, grade, score, actualScore, headersData, detailedIssues, aiRemediation } }];"
      },
      "id": "b2eaf987-acc1-4e83-9a72-771d59b10538",
      "name": "Data Aggregator",
      "type": "n8n-nodes-base.code",
      "position": [
        1328,
        736
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "const TAILWIND_CSS = `\n  body { font-family: system-ui, -apple-system, sans-serif; margin: 0; padding: 0; }\n  .bg-gray-100 { background-color: #f3f4f6; }\n  .bg-white { background-color: #ffffff; }\n  .bg-slate-900 { background-color: #0f172a; }\n  .bg-slate-300 { background-color: #cbd5e1; }\n  .bg-gray-50 { background-color: #f9fafb; }\n  .bg-green-50 { background-color: #f0fdf4; }\n  .bg-blue-50 { background-color: #eff6ff; }\n  .bg-red-50 { background-color: #fef2f2; }\n  .bg-yellow-50 { background-color: #fefce8; }\n  .border-green-200 { border-color: #bbf7d0; }\n  .border-blue-200 { border-color: #bfdbfe; }\n  .border-red-200 { border-color: #fecaca; }\n  .border-yellow-200 { border-color: #fef08a; }\n  .border-gray-200 { border-color: #e5e7eb; }\n  .border { border-width: 1px; }\n  .border-b { border-bottom-width: 1px; }\n  .rounded-lg { border-radius: 8px; }\n  .rounded-sm { border-radius: 4px; }\n  .text-white { color: #ffffff; }\n  .text-slate-300 { color: #cbd5e1; }\n  .text-gray-900 { color: #111827; }\n  .text-gray-700 { color: #374151; }\n  .text-gray-600 { color: #4b5563; }\n  .text-gray-500 { color: #6b7280; }\n  .text-gray-400 { color: #9ca3af; }\n  .text-green-800 { color: #166534; }\n  .text-green-700 { color: #15803d; }\n  .text-green-600 { color: #16a34a; }\n  .text-blue-800 { color: #1e40af; }\n  .text-blue-700 { color: #1d4ed8; }\n  .text-blue-600 { color: #2563eb; }\n  .text-red-800 { color: #991b1b; }\n  .text-red-700 { color: #b91c1c; }\n  .text-yellow-800 { color: #92400e; }\n  .text-yellow-700 { color: #a16207; }\n  .text-xs { font-size: 12px; line-height: 16px; }\n  .text-sm { font-size: 14px; line-height: 20px; }\n  .text-base { font-size: 16px; line-height: 24px; }\n  .text-lg { font-size: 18px; line-height: 28px; }\n  .text-xl { font-size: 20px; line-height: 28px; }\n  .text-2xl { font-size: 24px; line-height: 32px; }\n  .text-3xl { font-size: 30px; line-height: 36px; }\n  .font-medium { font-weight: 500; }\n  .font-semibold { font-weight: 600; }\n  .font-bold { font-weight: 700; }\n  .p-4 { padding: 16px; }\n  .p-6 { padding: 24px; }\n  .px-4 { padding-left: 16px; padding-right: 16px; }\n  .px-6 { padding-left: 24px; padding-right: 24px; }\n  .py-3 { padding-top: 12px; padding-bottom: 12px; }\n  .py-4 { padding-top: 16px; padding-bottom: 16px; }\n  .py-6 { padding-top: 24px; padding-bottom: 24px; }\n  .py-10 { padding-top: 40px; padding-bottom: 40px; }\n  .mx-auto { margin-left: auto; margin-right: auto; }\n  .mt-2 { margin-top: 8px; }\n  .mt-4 { margin-top: 16px; }\n  .mt-6 { margin-top: 24px; }\n  .mb-2 { margin-bottom: 8px; }\n  .mb-4 { margin-bottom: 16px; }\n  .mb-6 { margin-bottom: 24px; }\n  .max-w-2xl { max-width: 672px; }\n  .overflow-hidden { overflow: hidden; }\n  .shadow-sm { box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); }\n  .leading-relaxed { line-height: 1.625; }\n  .leading-tight { line-height: 1.25; }\n  .space-y-3 > :not(:first-child) { margin-top: 12px; }\n  .space-y-4 > :not(:first-child) { margin-top: 16px; }\n  .space-y-6 > :not(:first-child) { margin-top: 24px; }\n  .flex { display: flex; }\n  .justify-between { justify-content: space-between; }\n  .items-center { align-items: center; }\n  .underline { text-decoration: underline; }\n  .bg-slate-800 { background-color: #1e293b; }\n  .bg-blue-100 { background-color: #dbeafe; }\n  .bg-purple-50 { background-color: #faf5ff; }\n  .border-blue-400 { border-color: #60a5fa; }\n  .border-purple-200 { border-color: #e9d5ff; }\n  .border-l-4 { border-left-width: 4px; }\n  .rounded { border-radius: 4px; }\n  .text-green-400 { color: #4ade80; }\n  .text-purple-800 { color: #6b21a8; }\n  .text-purple-700 { color: #7c2d12; }\n  .font-mono { font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Consolas, \"Liberation Mono\", Menlo, monospace; }\n  .p-2 { padding: 8px; }\n  .px-2 { padding-left: 8px; padding-right: 8px; }\n  .px-3 { padding-left: 12px; padding-right: 12px; }\n  .py-1 { padding-top: 4px; padding-bottom: 4px; }\n  .py-2 { padding-top: 8px; padding-bottom: 8px; }\n  .mt-3 { margin-top: 12px; }\n  .mb-1 { margin-bottom: 4px; }\n  .mb-3 { margin-bottom: 12px; }\n  .ml-4 { margin-left: 16px; }\n  .my-1 { margin-top: 4px; margin-bottom: 4px; }\n  .my-2 { margin-top: 8px; margin-bottom: 8px; }\n  .overflow-x-auto { overflow-x: auto; }\n  .space-y-1 > :not(:first-child) { margin-top: 4px; }\n  .space-y-2 > :not(:first-child) { margin-top: 8px; }\n  .break-all { word-break: break-all; }\n  .whitespace-pre-wrap { white-space: pre-wrap; }\n  ul { list-style: none; }\n  ul li { position: relative; }\n  a { color: inherit; text-decoration: inherit; }\n  a:hover { text-decoration: underline; }`; const headerDescriptions = { 'strict-transport-security': 'HTTP Strict Transport Security strengthens your implementation of TLS by enforcing HTTPS connections.', 'content-security-policy': 'Content Security Policy is an effective measure to protect your site from XSS attacks by controlling resource loading.', 'x-frame-options': 'X-Frame-Options tells the browser whether you want to allow your site to be framed, preventing clickjacking attacks.', 'x-content-type-options': 'X-Content-Type-Options stops a browser from trying to MIME-sniff the content type, preventing certain attacks.', 'referrer-policy': 'Referrer Policy controls how much information the browser includes with navigations away from your site.', 'permissions-policy': 'Permissions Policy allows a site to control which features and APIs can be used in the browser.', 'cache-control': 'Cache-Control header helps prevent caching of sensitive pages, especially important for authentication pages.' }; const securityHeaders = [ { key: 'strict-transport-security', name: 'Strict-Transport-Security' }, { key: 'content-security-policy', name: 'Content-Security-Policy' }, { key: 'x-frame-options', name: 'X-Frame-Options' }, { key: 'x-content-type-options', name: 'X-Content-Type-Options' }, { key: 'referrer-policy', name: 'Referrer-Policy' }, { key: 'permissions-policy', name: 'Permissions-Policy' }, { key: 'cache-control', name: 'Cache-Control' } ]; return [{ json: { css: TAILWIND_CSS, headerDescriptions, securityHeaders } }];"
      },
      "id": "3cb19a8d-243e-457d-8ce0-5f75386e3a6c",
      "name": "Style Constants",
      "type": "n8n-nodes-base.code",
      "position": [
        1488,
        736
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "const { url, timestamp, grade, score, actualScore, headersData, detailedIssues, aiRemediation } = $('Data Aggregator').first().json; const { css, headerDescriptions, securityHeaders } = $('Style Constants').first().json; const createStatusBadge = (headerKey, headerName, isPresent) => { const badgeClass = isPresent ? 'bg-green-50 border-green-200 text-green-800' : 'bg-red-50 border-red-200 text-red-800'; const symbol = isPresent ? '\u2713' : '\u2717'; return `<span style=\"display: inline-block; padding: 8px 12px; margin: 4px 2px; border-radius: 6px; font-size: 12px; font-weight: 600;\" class=\"${badgeClass} border\">${symbol} ${headerName}</span>`; }; const headerBadges = securityHeaders.map(({ key, name }) => { const isPresent = headersData[key] && headersData[key].present; return createStatusBadge(key, name, isPresent); }).join(' '); const missingHeaders = securityHeaders.filter(({ key }) => !headersData[key] || !headersData[key].present); const getGradeColor = (grade) => { switch(grade) { case 'A': return 'text-green-600'; case 'B': return 'text-blue-600'; case 'C': return 'text-yellow-600'; case 'D': return 'text-orange-600'; case 'F': return 'text-red-600'; default: return 'text-gray-600'; } }; const getGradeBgColor = (grade) => { switch(grade) { case 'A': return 'bg-green-50 border-green-200'; case 'B': return 'bg-blue-50 border-blue-200'; case 'C': return 'bg-yellow-50 border-yellow-200'; case 'D': return 'bg-orange-50 border-orange-200'; case 'F': return 'bg-red-50 border-red-200'; default: return 'bg-gray-50 border-gray-200'; } }; const presentHeadersWithIssues = Object.entries(headersData).filter(([key, data]) => data.present && data.issues.length > 0); const missingHeadersSection = missingHeaders.length > 0 ? `\n  <div class=\"px-6 py-6 border-b border-gray-200\">\n    <h2 class=\"text-lg font-bold text-gray-900 mb-4\">Missing Headers</h2>\n    <div class=\"space-y-4\">\n      ${missingHeaders.map(({ key, name }) => `\n        <div class=\"bg-red-50 border border-red-200 rounded-lg px-4 py-3\">\n          <div class=\"text-red-800 font-semibold mb-2\">${name}</div>\n          <div class=\"text-red-700 text-sm leading-relaxed\">${headerDescriptions[key]}</div>\n        </div>\n      `).join('')}\n    </div>\n  </div>` : ''; const warningsSection = presentHeadersWithIssues.length > 0 ? `\n  <div class=\"px-6 py-6 border-b border-gray-200\">\n    <h2 class=\"text-lg font-bold text-gray-900 mb-4\">Security Warnings</h2>\n    <div class=\"space-y-4\">\n      ${presentHeadersWithIssues.map(([key, data]) => { const headerName = securityHeaders.find(h => h.key === key)?.name || key; return `\n        <div class=\"bg-yellow-50 border border-yellow-200 rounded-lg px-4 py-3\">\n          <div class=\"text-yellow-800 font-semibold mb-2\">${headerName}</div>\n          <div class=\"text-yellow-700 text-sm leading-relaxed\">${data.issues.join('. ')}</div>\n        </div>\n      `; }).join('')}\n    </div>\n  </div>` : ''; const rawHeadersSection = `\n  <div class=\"px-6 py-6 border-b border-gray-200\">\n    <h2 class=\"text-lg font-bold text-gray-900 mb-4\">Header Details</h2>\n    <div class=\"bg-gray-50 rounded-lg p-4 space-y-3\">\n      ${Object.entries(headersData).filter(([key, data]) => data.present).map(([key, data]) => { const hasIssues = data.issues.length > 0; const headerColor = hasIssues ? 'text-yellow-600' : 'text-green-600'; const headerValue = (data.value || 'present').length > 100 ? (data.value || 'present').substring(0, 97) + '...' : (data.value || 'present'); return `\n        <div class=\"bg-white border rounded px-3 py-2\">\n          <div class=\"${headerColor} font-semibold text-sm mb-1\">${key}</div>\n          <div class=\"text-gray-600 text-xs font-mono break-all\">${headerValue}</div>\n        </div>\n      `; }).join('')}\n    </div>\n  </div>`; const formatAIRemediation = (text) => { if (!text) return 'No AI remediation available.'; if (typeof text === 'object') { text = text.output || text.instructions || text.text || text.content || text.response || JSON.stringify(text); } if (typeof text === 'string' && (text.startsWith('{') || text.startsWith('```json'))) { try { if (text.startsWith('```json')) { text = text.replace(/```json\\s*/, '').replace(/\\s*```$/, ''); } const parsed = JSON.parse(text); text = parsed.output || parsed.instructions || parsed.text || parsed.content || parsed.response || text; } catch (e) { } } text = text.replace(/</g, '&lt;').replace(/>/g, '&gt;'); const sections = text.split(/(?=\\*\\*[A-Z])/); if (sections.length <= 1) { const lines = text.split('\\n'); let formatted = ''; let inCodeBlock = false; let inList = false; for (let line of lines) { line = line.trim(); if (line.startsWith('```')) { if (inCodeBlock) { formatted += '</div></div>'; } else { formatted += '<div class=\"bg-slate-800 text-green-400 rounded px-3 py-2 mt-2 mb-2\"><div class=\"font-mono text-xs\">'; } inCodeBlock = !inCodeBlock; } else if (line.match(/^\\d+\\.|^\u2022|^-/)) { if (!inList) { formatted += '<ul class=\"space-y-1 ml-4 my-2\">'; inList = true; } formatted += `<li class=\"text-blue-700 text-sm\">\u2022 ${line.replace(/^\\d+\\.|^\u2022|^-\\s*/, '')}</li>`; } else if (line.startsWith('\u274c') || line.startsWith('\u2713') || line.startsWith('\u26a0\ufe0f')) { if (inList) { formatted += '</ul>'; inList = false; } const color = line.startsWith('\u274c') ? 'text-red-700 bg-red-50' : line.startsWith('\u2713') ? 'text-green-700 bg-green-50' : 'text-yellow-700 bg-yellow-50'; formatted += `<div class=\"${color} rounded px-2 py-1 my-1 text-sm font-medium\">${line}</div>`; } else if (line.length > 0 && !inCodeBlock) { if (inList) { formatted += '</ul>'; inList = false; } formatted += `<p class=\"text-gray-700 text-sm leading-relaxed my-2\">${line}</p>`; } else if (inCodeBlock && line.length > 0) { formatted += `${line}<br>`; } } if (inList) formatted += '</ul>'; if (inCodeBlock) formatted += '</div></div>'; return formatted; } let formatted = ''; for (let section of sections) { section = section.trim(); if (section.length === 0) continue; if (section.startsWith('**') && section.includes(':**')) { const [header, ...contentParts] = section.split(':**'); const content = contentParts.join(':**'); const headerText = header.replace(/\\*\\*/g, '').trim(); const iconMap = { 'COPY THIS MESSAGE': '\ud83d\udccb', 'IMPLEMENT': '\u26a1', 'MY WEBSITE': '\ud83c\udf10', 'CURRENT': '\ud83d\udcca', 'TARGET': '\ud83c\udfaf', 'BEFORE': '\u26a0\ufe0f', 'REQUIRED': '\ud83d\udd27', 'CRITICAL': '\u26d4', 'PLEASE': '\ud83d\udcdd', 'WORDPRESS': '\ud83d\udcbb' }; const icon = iconMap[headerText] || '\u2022'; formatted += `<div class=\"mb-4\"><div class=\"bg-blue-100 border-l-4 border-blue-400 px-3 py-2 mb-2\"><div class=\"font-semibold text-blue-800 text-sm\">${icon} ${headerText}</div></div>`; if (content.trim()) { const lines = content.trim().split('\\n'); let inList = false; for (let line of lines) { line = line.trim(); if (line.match(/^\\d+\\.|^\u2022|^-/)) { if (!inList) { formatted += '<ul class=\"space-y-1 ml-4 mb-2\">'; inList = true; } formatted += `<li class=\"text-blue-700 text-sm\">\u2022 ${line.replace(/^\\d+\\.|^\u2022|^-\\s*/, '')}</li>`; } else if (line.startsWith('\u274c') || line.startsWith('\u2713') || line.startsWith('\u26a0\ufe0f')) { if (inList) { formatted += '</ul>'; inList = false; } const color = line.startsWith('\u274c') ? 'text-red-700 bg-red-50' : line.startsWith('\u2713') ? 'text-green-700 bg-green-50' : 'text-yellow-700 bg-yellow-50'; formatted += `<div class=\"${color} rounded px-2 py-1 my-1 text-sm font-medium\">${line}</div>`; } else if (line.length > 0) { if (inList) { formatted += '</ul>'; inList = false; } formatted += `<p class=\"text-gray-700 text-sm leading-relaxed my-1\">${line}</p>`; } } if (inList) formatted += '</ul>'; } formatted += '</div>'; } else { formatted += `<p class=\"text-gray-700 text-sm leading-relaxed my-2\">${section}</p>`; } } return formatted; }; const aiRemediationSection = `\n  <div class=\"px-6 py-6 border-b border-gray-200\">\n    <h2 class=\"text-lg font-bold text-gray-900 mb-4\">\ud83d\udccb AI-Generated Remediation Instructions</h2>\n    <div class=\"bg-blue-50 border border-blue-200 rounded-lg p-4\">\n      <div class=\"bg-white rounded-lg p-4 border shadow-sm overflow-hidden\">\n        <div class=\"text-blue-800 font-semibold mb-3 text-sm\">\ud83d\udca1 Copy-Paste Instructions for Your AI Code Editor:</div>\n        <div class=\"space-y-2\">\n          ${formatAIRemediation(aiRemediation)}\n        </div>\n      </div>\n      <div class=\"mt-3 p-2 bg-blue-100 rounded text-blue-700 text-xs\">\n        <strong>\ud83d\udca1 Pro Tip:</strong> Copy the entire message above and paste it into your AI code editor (GitHub Copilot Chat, Cursor, Codeium, etc.) for instant security header implementation.\n      </div>\n    </div>\n  </div>`; const nextStepsSection = `\n  <div class=\"px-6 py-6\">\n    <h2 class=\"text-lg font-bold text-gray-900 mb-4\">\ud83d\ude80 Quick Implementation Guide</h2>\n    <div class=\"space-y-3\">\n      <div class=\"bg-green-50 border border-green-200 rounded-lg px-4 py-3\">\n        <div class=\"text-green-800 font-semibold mb-2 text-sm\">\u2705 Step 1: Copy AI Instructions</div>\n        <div class=\"text-green-700 text-sm\">Select all text in the blue box above and copy it</div>\n      </div>\n      <div class=\"bg-blue-50 border border-blue-200 rounded-lg px-4 py-3\">\n        <div class=\"text-blue-800 font-semibold mb-2 text-sm\">\ud83e\udd16 Step 2: Open Your AI Assistant</div>\n        <div class=\"text-blue-700 text-sm\">GitHub Copilot Chat, Cursor, Codeium, Claude, ChatGPT, or any AI coding assistant</div>\n      </div>\n      <div class=\"bg-yellow-50 border border-yellow-200 rounded-lg px-4 py-3\">\n        <div class=\"text-yellow-800 font-semibold mb-2 text-sm\">\u26a0\ufe0f Step 3: Test in Staging First</div>\n        <div class=\"text-yellow-700 text-sm\">Always test security header changes in a staging environment before production</div>\n      </div>\n      <div class=\"bg-purple-50 border border-purple-200 rounded-lg px-4 py-3\">\n        <div class=\"text-purple-800 font-semibold mb-2 text-sm\">\ud83d\udd04 Step 4: Verify Implementation</div>\n        <div class=\"text-purple-700 text-sm\">Re-run this audit or check <a href=\"https://securityheaders.com\" class=\"underline\">securityheaders.com</a> to confirm improvements</div>\n      </div>\n    </div>\n  </div>`; return [{ json: { headerBadges, missingHeadersSection, warningsSection, rawHeadersSection, aiRemediationSection, nextStepsSection, gradeColor: getGradeColor(grade), gradeBgColor: getGradeBgColor(grade) } }];"
      },
      "id": "bb21085e-08a1-468d-b396-de174a044b2f",
      "name": "Content Builder",
      "type": "n8n-nodes-base.code",
      "position": [
        1648,
        736
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "jsCode": "const { url, timestamp, grade, score, actualScore } = $('Data Aggregator').first().json; const { css } = $('Style Constants').first().json; const { headerBadges, missingHeadersSection, warningsSection, rawHeadersSection, aiRemediationSection, nextStepsSection, gradeColor, gradeBgColor } = $('Content Builder').first().json; const emailSubject = `Security Report: Grade ${grade} - ${url}`; const emailHtml = `<!DOCTYPE html>\n<html lang=\"en\" dir=\"ltr\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Security Headers Audit Report</title>\n  <style>${css}</style>\n</head>\n<body class=\"bg-gray-100 py-10\">\n  <div class=\"bg-white max-w-2xl mx-auto rounded-lg overflow-hidden shadow-sm\">\n    \n    <!-- Header -->\n    <div class=\"bg-slate-900 px-6 py-6\">\n      <h1 class=\"text-white text-2xl font-bold mb-2 leading-tight\">Security Headers Report</h1>\n      <div class=\"text-slate-300 text-sm\">\n        Comprehensive security analysis for your website\n      </div>\n    </div>\n\n    <!-- Report Summary -->\n    <div class=\"px-6 py-6 border-b border-gray-200\">\n      <div class=\"text-gray-600 text-sm font-medium mb-1\">WEBSITE ANALYZED</div>\n      <div class=\"text-gray-900 text-base font-semibold mb-4\">${url}</div>\n      \n      <div class=\"text-gray-600 text-sm font-medium mb-1\">REPORT GENERATED</div>\n      <div class=\"text-gray-900 text-base font-semibold mb-4\">${timestamp}</div>\n    </div>\n\n    <!-- Security Status -->\n    <div class=\"px-6 py-6 border-b border-gray-200\">\n      <h2 class=\"text-lg font-bold text-gray-900 mb-4\">Overall Security Status</h2>\n      \n      <div class=\"${gradeBgColor} border rounded-lg px-4 py-3 mb-6\">\n        <div class=\"${gradeColor} text-xl font-bold mb-1\">Grade: ${grade}</div>\n        <div class=\"${gradeColor} text-sm\">\n          Security Score: ${score}% (${actualScore}/70 points)\n        </div>\n      </div>\n\n      <!-- Header Status -->\n      <h3 class=\"text-base font-bold text-gray-900 mb-3\">Security Headers Status</h3>\n      <div class=\"leading-relaxed\">\n        ${headerBadges}\n      </div>\n    </div>\n\n    ${missingHeadersSection}\n    ${warningsSection}\n    ${rawHeadersSection}\n    ${aiRemediationSection}\n    ${nextStepsSection}\n\n    <!-- Footer -->\n    <div class=\"px-6 py-4 bg-gray-50 border-t border-gray-200\">\n      <div class=\"text-gray-500 text-xs leading-relaxed\">\n        This security report was generated automatically for ${url}.\n        For questions, contact <a href=\"mailto:ari@theauspiciouscompany.com\" class=\"text-blue-600 underline\">support</a>.\n      </div>\n      \n      <div class=\"text-gray-400 text-xs mt-2\">\n        Made by <a href=\"https://theauspiciouscompany.com\" class=\"text-gray-400 underline\">Ari Nakos</a> \n      </div>\n    </div>\n\n  </div>\n</body>\n</html>`; return [{ json: { email_html: emailHtml, email_subject: emailSubject } }];"
      },
      "id": "5b1bee4d-f49b-4e14-935a-8a3f81ca595a",
      "name": "Email Formatter",
      "type": "n8n-nodes-base.code",
      "position": [
        1824,
        736
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "sendTo": "ari@llanai.com",
        "subject": "={{ $json.email_subject }}",
        "message": "={{ $json.email_html }}",
        "options": {}
      },
      "id": "b7c9eb09-2a80-4aa1-8ca0-5ee94f4881df",
      "name": "Send Alert",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2000,
        736
      ],
      "typeVersion": 2.1,
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "69be5ca0-6baf-4365-bd60-bcef7c40124e",
              "leftValue": "={{ $json.actualScore }}",
              "rightValue": 70,
              "operator": {
                "type": "number",
                "operation": "lt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        768,
        944
      ],
      "id": "3299e83a-d8bb-4fd4-8d36-8ba7293f2652",
      "name": "If"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=You are an expert security consultant creating instructions for non-technical users to give to their AI code editor. Based on the security audit below, create simple, copy-paste instructions that anyone can use.\n\n**Security Audit Results:**\n- Website: {{ $('URL List').item.json.urlsToAudit }}\n- Security Grade: {{ $('Grade Calculator').item.json.securityGrade }}\n- Current Score: {{ $('Grade Calculator').item.json.securityScore }}%\n- Issues Found: {{ $('Grade Calculator').item.json.criticalIssues.length }} critical issues\n\n**Missing/Problematic Headers:**\n{{ $('Grade Calculator').item.json.issues.map(issue => `\u2022 ${issue.header}: ${issue.issue}`).join('\\n') }}\n\nCreate instructions using this EXACT format:\n\n---\n\n**COPY THIS MESSAGE TO YOUR AI CODE EDITOR:**\n\nIMPLEMENT SECURITY HEADERS\n\nMY WEBSITE: {{ $('URL List').item.json.urlsToAudit }}\nCURRENT SECURITY GRADE: {{ $('Grade Calculator').item.json.securityGrade }}\nTARGET: A+ security grade\n\nBEFORE MAKING CHANGES:\n1. Test current grade at securityheaders.com\n2. Identify my platform (Vercel/Netlify/Apache/Nginx/Node.js)\n3. Locate security config file\n\nREQUIRED HEADERS TO IMPLEMENT:\n[List each missing/problematic header with brief explanation]\n\nCRITICAL SAFETY RULES:\n\u274c NEVER add unsafe-eval to CSP\n\u274c NEVER use wildcards (*) in CSP directives\n\u274c NEVER remove existing security headers\n\u274c NEVER weaken existing CSP directives\n\nPLEASE:\n1. Show exactly where to add headers in my config\n2. Provide copy-paste header configurations\n3. Explain how to test the changes\n4. Ensure no functionality breaks\n5. Ask about my tech stack if unsure\n\nProvide step-by-step instructions for non-developers.\n\n---\n\n**WORDPRESS SITES:** If this is a WordPress website, contact Ari Nakos at https://theauspiciouscompany.com who can handle the security header implementation for you.\n\n---\n\nMake this message clear and actionable so anyone can copy-paste it to their AI assistant and get exactly what they need.",
        "options": {
          "systemMessage": "You are a security consultant writing for non-technical users. Create simple, clear instructions that anyone can copy-paste to their AI code editor. Use plain language and focus on what the user needs to tell their AI assistant to get the security headers implemented correctly. Don't include technical implementation details - just create the perfect prompt for them to use.\n\nReturn only the plain text instructions that users should copy-paste. Do NOT wrap in JSON or any other format - return the raw instructions as they should appear."
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.9,
      "position": [
        1040,
        944
      ],
      "id": "e18fa16d-4b85-4840-bc0f-1bba38e34885",
      "name": "AI Remediation Agent"
    },
    {
      "parameters": {
        "model": "z-ai/glm-4.6",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "typeVersion": 1,
      "position": [
        976,
        1152
      ],
      "id": "19997b9e-5399-4d4c-a2c6-b61e98ae4c18",
      "name": "AI Remediation Model",
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "formTitle": "Site",
        "formDescription": "Site to examine",
        "formFields": {
          "values": [
            {
              "fieldLabel": "Site",
              "placeholder": "theauspiciouscompany.com",
              "requiredField": true
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.formTrigger",
      "typeVersion": 2.3,
      "position": [
        224,
        512
      ],
      "id": "c609fcc2-c351-439b-8cfd-af7ddc7525af",
      "name": "Form Input"
    }
  ],
  "connections": {
    "URL List": {
      "main": [
        [
          {
            "node": "Fetch Headers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Headers": {
      "main": [
        [
          {
            "node": "Parse Headers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Headers": {
      "main": [
        [
          {
            "node": "Security Scorer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Security Scorer": {
      "main": [
        [
          {
            "node": "Grade Calculator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Grade Calculator": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          },
          {
            "node": "Format Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Report": {
      "main": [
        [
          {
            "node": "Export to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Aggregator": {
      "main": [
        [
          {
            "node": "Style Constants",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Style Constants": {
      "main": [
        [
          {
            "node": "Content Builder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Content Builder": {
      "main": [
        [
          {
            "node": "Email Formatter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Formatter": {
      "main": [
        [
          {
            "node": "Send Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If": {
      "main": [
        [
          {
            "node": "AI Remediation Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Remediation Agent": {
      "main": [
        [
          {
            "node": "Data Aggregator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Remediation Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Remediation Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Form Input": {
      "main": [
        [
          {
            "node": "URL List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "meta": {
    "templateCredsSetupCompleted": true
  }
}

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

Security-Headers-Audit. Uses httpRequest, googleSheets, gmail, agent. Event-driven trigger; 18 nodes.

Source: https://github.com/aristidesnakos/automations/blob/main/n8n/security/security-headers-audit.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

An automated workflow for auditing website security headers and generating comprehensive security reports.

HTTP Request, Google Sheets, Gmail +3
AI & RAG

Arvifund - Supabase. Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.

HTTP Request, Telegram, Google Sheets +8
AI & RAG

Arvifund - Supabase (Fixed v2). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.

HTTP Request, Telegram, Google Sheets +9
AI & RAG

Arvifund - Supabase (Fixed v4). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.

HTTP Request, Telegram, Google Sheets +9
AI & RAG

Arvifund - Supabase (Fixed v3). Uses httpRequest, telegram, googleSheets, telegramTrigger. Event-driven trigger; 90 nodes.

HTTP Request, Telegram, Google Sheets +9