AutomationFlowsAI & RAG › Triage AWS Security Misconfigurations with Gpt-4.1 Mini and Send Alerts to Gmail

Triage AWS Security Misconfigurations with Gpt-4.1 Mini and Send Alerts to Gmail

ByCalistus Christian @ca7ai on n8n.io

Automatically triages risky AWS misconfigurations and alerts your team.

Webhook trigger★★★★☆ complexityAI-powered12 nodesGmailOpenAIAirtableHTTP Request
AI & RAG Trigger: Webhook Nodes: 12 Complexity: ★★★★☆ AI nodes: yes Added:
Triage AWS Security Misconfigurations with Gpt-4.1 Mini and Send Alerts to Gmail — n8n workflow card showing Gmail, OpenAI, Airtable integration

This workflow corresponds to n8n.io template #7202 — we link there as the canonical source.

This workflow follows the Airtable → Gmail 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
{
  "id": "bBRp9TmumfujcoHs",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Security Hub Alerts Triaged by AI",
  "tags": [],
  "nodes": [
    {
      "id": "7be0eeba-8700-4b51-a40f-db84c7c533b1",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        0,
        -272
      ],
      "parameters": {
        "path": "aws-misconfig",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 2
    },
    {
      "id": "ac0b1a8d-14a0-4e6d-be3a-779a57603869",
      "name": "Normalize Finding",
      "type": "n8n-nodes-base.code",
      "position": [
        672,
        -176
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Code created by ca7ai\n// n8n Code node (Run Once for Each Item)\n// Normalizes Security Hub / AWS Config events whether they arrive at root or under body\nconst evt = $json.body ?? $json;\n\n// Security Hub finding (EventBridge or raw)\nconst sh = evt?.detail?.findings?.[0] || (Array.isArray(evt.findings) ? evt.findings[0] : null);\n// AWS Config notification\nconst cfg = evt?.detail?.configRuleName ? evt.detail : null;\n// Raw Security Hub finding at root\nconst rawSh = (!sh && evt?.ProductArn) ? evt : null;\n\nconst f = sh || rawSh || {};\n\nconst sev   = f?.Severity?.Label || (cfg ? \"MEDIUM\" : \"UNKNOWN\");\nconst title = f?.Title || cfg?.configRuleName || \"Finding\";\nconst desc  = f?.Description || cfg?.newEvaluationResult?.annotation || \"\u2014\";\nconst id    = f?.Id\n  || cfg?.newEvaluationResult?.evaluationResultIdentifier?.evaluationResultQualifier?.configRuleName\n  || String(Date.now());\nconst res   = f?.Resources?.[0]?.Id || cfg?.resourceId || \"unknown\";\nconst types = f?.Types || [];\nconst account = evt?.account || f?.AwsAccountId || \"unknown\";\nconst region  = evt?.region  || f?.Region       || \"unknown\";\n\n// Derive service + hints\nlet service = \"UNKNOWN\";\nif (/^arn:aws:s3:::/.test(res) || types.some(t => t.includes(\"S3\"))) service = \"S3\";\nelse if (types.some(t => /SecurityGroup/i.test(t)) || /sg-/.test(res)) service = \"EC2-SG\";\nelse if (types.some(t => /IAM/i.test(t))) service = \"IAM\";\nelse if (types.some(t => /RDS|SQL|DB/i.test(t))) service = \"RDS\";\nelse if (f?.ProductArn) service = (f.ProductArn.split(\":\")[5] || \"UNKNOWN\");\n\nconst misconfig_hints = [];\nif (service === \"S3\") misconfig_hints.push(\"s3\");\nif (/0\\.0\\.0\\.0\\/0|Public|Open|world/i.test(desc)) misconfig_hints.push(\"public\");\nif (service === \"EC2-SG\") misconfig_hints.push(\"sg\");\nif (service === \"IAM\")    misconfig_hints.push(\"iam\");\nif (service === \"RDS\")    misconfig_hints.push(\"db\");\n\n// IMPORTANT: return a SINGLE object (not an array) in this mode\nreturn {\n  finding_id: id,\n  title,\n  description: desc,\n  severity: sev,\n  resource_id: res,\n  service,\n  account,\n  region,\n  product_types: types,\n  misconfig_hints,\n  raw: evt,\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "be7fa618-9c8c-4418-a405-a4f2787faaf5",
      "name": "Send a message",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1520,
        -176
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "=={{ (() => {  const nf = $node[\"Normalize Finding\"].json;  const ai = typeof $node[\"AI Prioritizer\"].json.message.content === 'string'    ? JSON.parse($node[\"AI Prioritizer\"].json.message.content)    : $node[\"AI Prioritizer\"].json.message.content;  const steps = (ai.remediation || []).map(s => `<li>${s}</li>`).join('');  const tags  = (ai.tags || []).join(', ');  const airtableId = $node[\"Airtable - Create Record\"].json?.id || '';  const airtableLine = airtableId ? `<p><b>Airtable Record ID:</b> ${airtableId}</p>` : '';  return `    <h2>AWS Misconfig Alert</h2>    <p><b>Priority:</b> ${ai.priority} &nbsp; <b>Severity:</b> ${nf.severity}</p>    <p><b>Title:</b> ${nf.title}</p>    <p><b>Service:</b> ${nf.service} &nbsp; <b>Resource:</b> ${nf.resource_id}</p>    <p><b>Account:</b> ${nf.account} &nbsp; <b>Region:</b> ${nf.region}</p>    <p><b>Why:</b> ${ai.rationale}</p>    <p><b>Remediation:</b></p>    <ol>${steps}</ol>    <p><b>Tags:</b> ${tags || '\u2014'}</p>    ${airtableLine}    <details><summary>Raw finding</summary>      <pre style=\"background:#f6f8fa;padding:12px;border-radius:6px;white-space:pre-wrap\">${JSON.stringify(nf.raw || nf, null, 2)}</pre>    </details>  `;})() }}",
        "options": {},
        "subject": "=={{ `[${JSON.parse($node[\"AI Prioritizer\"].json.message.content).priority}] ${$node[\"Normalize Finding\"].json.title} \u2014 ${$node[\"Normalize Finding\"].json.resource_id} (${ $node[\"Normalize Finding\"].json.service })` }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "c0fb42e3-e670-44f9-a3fa-0b2c3dec2ae1",
      "name": "AI Prioritizer",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        896,
        -176
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini",
          "cachedResultName": "GPT-4.1-MINI"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=You are a cloud SecOps triage assistant. Given a normalized AWS finding JSON, return a STRICT JSON object:\n\n{\n  \"priority\": \"P0|P1|P2|P3\",\n  \"rationale\": \"one-paragraph reason referencing severity, resource, and exposure\",\n  \"remediation\": [\"step 1\", \"step 2\", \"...\"],\n  \"tags\": [\"s3\",\"iam\",\"public\", \"...\"]\n}\n\nMapping guidance:\n- Treat publicly accessible data (e.g., public S3 buckets, 0.0.0.0/0 on admin ports, open RDS) as P0 or P1 depending on blast radius.\n- Internal-only or low impact \u2192 P2/P3.\n- If severity label is CRITICAL/HIGH, bias to P0/P1.\n\nFinding:\n{{ JSON.stringify($json, null, 2) }}\n"
            }
          ]
        },
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "42721c67-8c6d-4063-826b-b59e01a3d8ca",
      "name": "Airtable - Create Record",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1296,
        -176
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appzIE2wRRYUbvl50",
          "cachedResultUrl": "https://airtable.com/uuuu",
          "cachedResultName": "misconfigs"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblPDewIrVYYYYNyi",
          "cachedResultUrl": "https://airtable.com/uuuu/uuuu",
          "cachedResultName": "finding_table"
        },
        "columns": {
          "value": {
            "id": "={{ $('Normalize Finding').item.json.finding_id }}",
            "Tags": "={{ $json.message.content.tags[0] }}{{ $json.message.content.tags[1] }}{{ $json.message.content.tags[2] }}{{ $json.message.content.tags[3] }}{{ $json.message.content.tags[4] }}",
            "Title": "={{ $('Normalize Finding').item.json.title }}",
            "Region": "={{ $('Normalize Finding').item.json.region }}",
            "Account": "={{ $('Normalize Finding').item.json.account }}",
            "Service": "={{ $('Normalize Finding').item.json.service }}",
            "Priority": "={{ $json.message.content.priority }}",
            "Resource": "={{ $('Normalize Finding').item.json.raw.detail.findings[0].Resources[0].Id }}",
            "Severity": "={{ $('Normalize Finding').item.json.severity }}",
            "Rationale": "={{ $json.message.content.rationale }}",
            "Finding ID": "={{ $('Normalize Finding').item.json.raw.detail.findings[0].Id }}",
            "Remediation": "={{ $json.message.content.remediation[0] }}{{ $json.message.content.remediation[1] }}{{ $json.message.content.remediation[2] }}{{ $json.message.content.remediation[3] }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true
            },
            {
              "id": "Finding ID",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Finding ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Severity",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Severity",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Priority",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Priority",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Resource",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Resource",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Service",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Service",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Account",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Account",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Region",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Region",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Tags",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Tags",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Rationale",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Rationale",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Remediation",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Remediation",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "upsert"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "6f9bf9c6-61ee-4b25-844c-5f03c9979f9e",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        1744,
        -176
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "={{\n  {\n    resp: {\n      status: \"processed\",\n      priority: $node[\"Airtable - Create Record\"].json.fields.Priority,\n      finding_id: $node[\"Normalize Finding\"].json.finding_id\n    }\n  }\n}}\n"
      },
      "typeVersion": 3.4
    },
    {
      "id": "fbb5d2af-7eb2-414f-b79e-2a51ddb9b21f",
      "name": "SNS Handler",
      "type": "n8n-nodes-base.code",
      "position": [
        224,
        -272
      ],
      "parameters": {
        "jsCode": "const b = $json.body ?? $json;\nconst token = $json.query?.token ?? b.token;\nif (token !== 'MY_SUPER_TOKEN') throw new Error('unauthorized');\n\nif (b.Type === 'SubscriptionConfirmation' && b.SubscribeURL) {\n  return { mode: 'confirm', subscribeUrl: b.SubscribeURL };\n}\n\nlet event = b;\nif (b.Type === 'Notification' && b.Message) {\n  try { event = JSON.parse(b.Message); } catch {}\n}\nreturn { mode: 'notify', event };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "b61e5e70-697d-4fe4-9897-9a116aa5aff1",
      "name": "If",
      "type": "n8n-nodes-base.if",
      "position": [
        448,
        -272
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d192995f-8809-4b60-8f6a-b7bd2e3e47b0",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "=={{ $json.mode === 'confirm' }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "5c1145c7-6dd6-47e3-9fee-e1fb15318a22",
      "name": "SNS Confirm",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        672,
        -368
      ],
      "parameters": {
        "url": "=={{ $json.subscribeUrl }}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "fullResponse": true,
              "responseFormat": "json"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "831507c3-b00f-44ec-8506-c0310904eb6e",
      "name": "Edit Fields1",
      "type": "n8n-nodes-base.set",
      "position": [
        984,
        -368
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "346708e7-629e-4d8a-8a98-994b9526b55d",
              "name": "resp",
              "type": "object",
              "value": "=resp = { status: \"subscribed\", statusCode: $json.statusCode || 200 }"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "b39a925c-911d-4e61-a0e6-5aadc4ccfd38",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -464,
        -32
      ],
      "parameters": {
        "width": 336,
        "content": "## Note\n\n: You must have the AWS Side pre-configured before testing / starting this workflow\n"
      },
      "typeVersion": 1
    },
    {
      "id": "1bea05cd-6468-4c74-aeec-8c60d69411c4",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -464,
        -368
      ],
      "parameters": {
        "color": 4,
        "width": 336,
        "height": 304,
        "content": "## Title: How it works (wiring)\n\n-   **Flow**: Webhook \u2192 SNS Handler \u2192 IF \u2192 (true) SNS Confirm \u2192 Done | (false) Normalize \u2192 AI \u2192 Airtable \u2192 Gmail \u2192 Respond\n    \n-   **Purpose**: triage AWS misconfigs and alert the team\n    \n-   Responds when last node finishes (returns small JSON)\n"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "044dc52b-81c7-43ab-a1c2-5be287a0d970",
  "connections": {
    "If": {
      "main": [
        [
          {
            "node": "SNS Confirm",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Normalize Finding",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook": {
      "main": [
        [
          {
            "node": "SNS Handler",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SNS Confirm": {
      "main": [
        [
          {
            "node": "Edit Fields1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SNS Handler": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields1": {
      "main": [
        []
      ]
    },
    "AI Prioritizer": {
      "main": [
        [
          {
            "node": "Airtable - Create Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send a message": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Finding": {
      "main": [
        [
          {
            "node": "AI Prioritizer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Airtable - Create Record": {
      "main": [
        [
          {
            "node": "Send a message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

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

Automatically triages risky AWS misconfigurations and alerts your team.

Source: https://n8n.io/workflows/7202/ — 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 powerful n8n automation workflow is designed to execute advanced B2B lead enrichment and hyper-personalization for cold email outreach. By orchestrating a complex chain of data scraping, AI analy

OpenAI, HTTP Request, Airtable
AI & RAG

User Signup & Verification: The workflow starts when a user signs up. It generates a verification code and sends it via SMS using Twilio. Code Validation: The user replies with the code. The workflow

Postgres, HTTP Request, OpenAI +2
AI & RAG

This template is perfect for e-commerce entrepreneurs, marketers, agencies, and creative teams who want to turn simple product photos and short descriptions into professional flyers or product videos—

Airtable, OpenAI, HTTP Request +1
AI & RAG

Instantly map all internal URLs, perform AI-powered (ChatGPT) analysis, and deliver results in HTML via webhook, Google Sheets, or email. All from your own n8n instance!

OpenAI, HTTP Request, XML +3
AI & RAG

Watch on Youtube▶️

HTTP Request, Email Send, Google Sheets +3