{
  "name": "JovanOps Security Pulse",
  "nodes": [
    {
      "parameters": {},
      "id": "4b905668-e237-4d67-aea8-5990e148c803",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        0,
        112
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "49b30c3e-acfa-4d9f-8a45-432c9415cb1a",
              "name": "target_url",
              "value": "https://jovanops.com/",
              "type": "string"
            },
            {
              "id": "cf15d4bf-3118-443a-92b8-64844053fad5",
              "name": "owner",
              "value": "Jovan Ljusi\u0107",
              "type": "string"
            },
            {
              "id": "24afc7ad-58e2-47e6-80ec-f32fca866f01",
              "name": "high_risk_threshold",
              "value": 40,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "2bdef6b6-ba5c-45f9-83cb-37acf8dd3d2d",
      "name": "Target Configuration",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        224,
        112
      ]
    },
    {
      "parameters": {
        "url": "={{ $json.target_url }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "text",
              "outputPropertyName": "body"
            }
          },
          "timeout": 10000
        }
      },
      "id": "51136f2f-368f-49dc-9001-9c090a79a7d1",
      "name": "Fetch Website Response",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        448,
        112
      ]
    },
    {
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst config = $(\"Target Configuration\").first().json;\n\nconst targetUrl = config.target_url;\nconst highRiskThreshold = Number(config.high_risk_threshold ?? 40);\nconst statusCode = Number(response.statusCode ?? response.status ?? 0);\n\nconst originalHeaders = response.headers ?? {};\nconst headers = {};\n\nfor (const [key, value] of Object.entries(originalHeaders)) {\n  headers[key.toLowerCase()] = String(value);\n}\n\nconst contentSecurityPolicy =\n  headers[\"content-security-policy\"] ?? \"\";\n\nconst checks = [\n  {\n    control: \"Website availability\",\n    passed: statusCode >= 200 && statusCode < 400,\n    weight: 20,\n    recommendation:\n      \"Investigate the website hosting, DNS, deployment, or application availability.\",\n  },\n  {\n    control: \"HTTPS\",\n    passed: targetUrl.startsWith(\"https://\"),\n    weight: 15,\n    recommendation:\n      \"Redirect all unencrypted HTTP traffic to HTTPS.\",\n  },\n  {\n    control: \"Strict-Transport-Security\",\n    passed: Boolean(headers[\"strict-transport-security\"]),\n    weight: 15,\n    recommendation:\n      \"Configure the Strict-Transport-Security header after confirming HTTPS is fully enabled.\",\n  },\n  {\n    control: \"Content-Security-Policy\",\n    passed: Boolean(contentSecurityPolicy),\n    weight: 15,\n    recommendation:\n      \"Implement a restrictive Content-Security-Policy appropriate for the website.\",\n  },\n  {\n    control: \"Clickjacking protection\",\n    passed:\n      Boolean(headers[\"x-frame-options\"]) ||\n      contentSecurityPolicy.toLowerCase().includes(\"frame-ancestors\"),\n    weight: 10,\n    recommendation:\n      \"Configure X-Frame-Options or the CSP frame-ancestors directive.\",\n  },\n  {\n    control: \"X-Content-Type-Options\",\n    passed:\n      (headers[\"x-content-type-options\"] ?? \"\").toLowerCase() ===\n      \"nosniff\",\n    weight: 10,\n    recommendation:\n      \"Set X-Content-Type-Options to nosniff.\",\n  },\n  {\n    control: \"Referrer-Policy\",\n    passed: Boolean(headers[\"referrer-policy\"]),\n    weight: 5,\n    recommendation:\n      \"Configure an appropriate Referrer-Policy.\",\n  },\n  {\n    control: \"Permissions-Policy\",\n    passed: Boolean(headers[\"permissions-policy\"]),\n    weight: 5,\n    recommendation:\n      \"Restrict unnecessary browser features using Permissions-Policy.\",\n  },\n  {\n    control: \"Technology disclosure\",\n    passed: !headers[\"x-powered-by\"],\n    weight: 5,\n    recommendation:\n      \"Remove the X-Powered-By header to reduce unnecessary technology disclosure.\",\n  },\n];\n\nconst securityScore = checks.reduce(\n  (total, check) => total + (check.passed ? check.weight : 0),\n  0\n);\n\nconst riskScore = 100 - securityScore;\n\nlet severity = \"LOW\";\n\nif (riskScore >= 60) {\n  severity = \"CRITICAL\";\n} else if (riskScore >= highRiskThreshold) {\n  severity = \"HIGH\";\n} else if (riskScore >= 20) {\n  severity = \"MEDIUM\";\n}\n\nconst findings = checks\n  .filter((check) => !check.passed)\n  .map((check) => ({\n    control: check.control,\n    risk_points: check.weight,\n    recommendation: check.recommendation,\n  }));\n\nreturn [\n  {\n    json: {\n      target_url: targetUrl,\n      owner: config.owner,\n      scanned_at: new Date().toISOString(),\n      status_code: statusCode,\n      security_score: securityScore,\n      risk_score: riskScore,\n      severity,\n      high_risk_threshold: highRiskThreshold,\n      passed_controls: checks.filter((check) => check.passed).length,\n      failed_controls: findings.length,\n      findings,\n    },\n  },\n];"
      },
      "id": "65a2eec9-0e60-40f1-a456-ecfe9779d6e9",
      "name": "Analyze Security Controls",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        672,
        112
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=You are a defensive cybersecurity reporting assistant.\n\nAnalyze the following results from a passive website configuration review.\n\nImportant rules:\n- This is not a penetration test.\n- Do not invent vulnerabilities.\n- Do not recommend exploitation, brute force, bypassing controls, or intrusive testing.\n- Base the report only on the supplied findings.\n- Treat the risk score as an internal workflow score, not as an industry-standard severity rating.\n- Provide clear and practical defensive recommendations.\n- Do not claim that a confirmed vulnerability exists solely because a security header is missing. Describe missing headers as configuration or security-hardening gaps.\nWebsite: {{ $('Analyze Security Controls').first().json.target_url }}\nHTTP status: {{ $('Analyze Security Controls').first().json.status_code }}\nSecurity score: {{ $('Analyze Security Controls').first().json.security_score }}/100\nRisk score: {{ $('Analyze Security Controls').first().json.risk_score }}/100\nWorkflow severity: {{ $('Analyze Security Controls').first().json.severity }}\nPassed controls: {{ $('Analyze Security Controls').first().json.passed_controls }}\nFailed controls: {{ $('Analyze Security Controls').first().json.failed_controls }}\n\nFailed security controls:\n{{ JSON.stringify($('Analyze Security Controls').first().json.findings) }}\n\nCreate a concise report with these sections:\n\n1. Executive Summary\n2. Detected Configuration Gaps\n3. Prioritized Remediation Actions\n4. Assessment Limitations\n- Use plain text only.\n- Do not use Markdown symbols such as #, *, backticks, or tables.",
        "batching": {}
      },
      "id": "ce26661c-c4ab-462f-95f9-10c1c40264a3",
      "name": "Generate Remediation Report",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        896,
        112
      ]
    },
    {
      "parameters": {
        "modelName": "models/gemini-3.1-flash-lite",
        "options": {}
      },
      "id": "a9d7dbd9-4fab-40cf-a0fe-fbc033890376",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1.1,
      "position": [
        976,
        336
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 3
          },
          "conditions": [
            {
              "id": "ca0142ae-6124-4999-831f-af0e0b563f56",
              "leftValue": "={{ Number($('Analyze Security Controls').first().json.risk_score) >= Number($('Analyze Security Controls').first().json.high_risk_threshold) }}",
              "rightValue": "={{ $('Analyze Security Controls').first().json.high_risk_threshold }}",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.3,
      "position": [
        1248,
        112
      ],
      "id": "efe50254-e521-428b-be4a-8ade1471daae",
      "name": "Is Risk High?"
    },
    {
      "parameters": {
        "sendTo": "your-email@example.com",
        "subject": "={{ '[JovanOps Security Alert] ' + $('Analyze Security Controls').first().json.severity + ' risk detected' }}",
        "emailType": "text",
        "message": "=JovanOps Security Pulse detected a high-risk configuration result.  Website: {{ $('Analyze Security Controls').first().json.target_url }} Scan time: {{ $('Analyze Security Controls').first().json.scanned_at }} HTTP status: {{ $('Analyze Security Controls').first().json.status_code }} Security score: {{ $('Analyze Security Controls').first().json.security_score }}/100 Risk score: {{ $('Analyze Security Controls').first().json.risk_score }}/100 Workflow severity: {{ $('Analyze Security Controls').first().json.severity }}  AI-generated remediation report:  {{ $('Generate Remediation Report').first().json.text }}  Important: This was a passive security-configuration review, not a penetration test.",
        "options": {}
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        1456,
        16
      ],
      "id": "8fbb2313-42b8-4401-86b6-1565afa8b5d4",
      "name": "Send Urgent Security Alert"
    },
    {
      "parameters": {
        "sendTo": "your-email@example.com",
        "subject": "={{ '[JovanOps Security Report] Scan completed - Security score ' + $('Analyze Security Controls').first().json.security_score + '/100' }}",
        "emailType": "text",
        "message": "=JovanOps Security Pulse completed a routine website security review.  Website: {{ $('Analyze Security Controls').first().json.target_url }} Scan time: {{ $('Analyze Security Controls').first().json.scanned_at }} HTTP status: {{ $('Analyze Security Controls').first().json.status_code }} Security score: {{ $('Analyze Security Controls').first().json.security_score }}/100 Risk score: {{ $('Analyze Security Controls').first().json.risk_score }}/100 Workflow severity: {{ $('Analyze Security Controls').first().json.severity }}  AI-generated remediation report:  {{ $('Generate Remediation Report').first().json.text }}  Important: This was a passive security-configuration review, not a penetration test.",
        "options": {}
      },
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        1456,
        208
      ],
      "id": "a67d9ab6-46b8-4a96-a8b9-23b42d0b7dce",
      "name": "Send Routine Security Report"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.3,
      "position": [
        192,
        272
      ],
      "id": "77e89c6a-11ce-49a9-8495-3493fb7b73b5",
      "name": "Daily Security Scan"
    }
  ],
  "connections": {
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Target Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Target Configuration": {
      "main": [
        [
          {
            "node": "Fetch Website Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Website Response": {
      "main": [
        [
          {
            "node": "Analyze Security Controls",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Security Controls": {
      "main": [
        [
          {
            "node": "Generate Remediation Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Remediation Report",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Generate Remediation Report": {
      "main": [
        [
          {
            "node": "Is Risk High?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Risk High?": {
      "main": [
        [
          {
            "node": "Send Urgent Security Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Routine Security Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Security Scan": {
      "main": [
        [
          {
            "node": "Target Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  },
  "nodeGroups": [],
  "tags": []
}