AutomationFlowsWeb Scraping › Gate Deployments on Waf Scan Results with Waftester

Gate Deployments on Waf Scan Results with Waftester

ByQandil @qandil on n8n.io

A CI/CD quality gate that blocks deployments when WAF protection is insufficient. Your pipeline sends a webhook with the target URL, the workflow runs WAFtester scans, and returns a pass/fail HTTP response the pipeline can gate on.

Webhook trigger★★★★☆ complexity12 nodesHTTP Request
Web Scraping Trigger: Webhook Nodes: 12 Complexity: ★★★★☆ Added:

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

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
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Gate deployments on WAF security scan results with WAFtester",
  "tags": [],
  "nodes": [
    {
      "id": "793762f9-088e-4c68-b43e-9828be0fc25c",
      "name": "Deploy Webhook",
      "type": "n8n-nodes-base.webhook",
      "notes": "Receives POST from CI/CD pipeline. Body: {\"target\": \"https://...\", \"categories\": [\"sqli\", \"xss\"]}",
      "position": [
        672,
        32
      ],
      "parameters": {
        "path": "waf-security-gate",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "123e328c-f1f2-4816-a1e8-cc87719f71ae",
      "name": "Detect WAF",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Identifies the WAF vendor protecting the target.",
      "position": [
        864,
        32
      ],
      "parameters": {
        "url": "={{ $env.WAFTESTER_MCP_URL || 'http://waftester:8080/mcp' }}",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "jsonBody": "={\n  \"jsonrpc\": \"2.0\",\n  \"id\": 1,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"detect_waf\",\n    \"arguments\": {\n      \"target\": \"{{ $('Deploy Webhook').item.json.body.target }}\"\n    }\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a1348c2f-b92c-4344-ad49-ed4841760fc8",
      "name": "Start Scan",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Starts an async scan. Returns a task_id for polling.",
      "position": [
        1072,
        32
      ],
      "parameters": {
        "url": "={{ $env.WAFTESTER_MCP_URL || 'http://waftester:8080/mcp' }}",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "jsonBody": "={\n  \"jsonrpc\": \"2.0\",\n  \"id\": 2,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"scan\",\n    \"arguments\": {\n      \"target\": \"{{ $('Deploy Webhook').item.json.body.target }}\",\n      \"categories\": {{ $('Deploy Webhook').item.json.body.categories ? JSON.stringify($('Deploy Webhook').item.json.body.categories) : '[\"sqli\", \"xss\"]' }}\n    }\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a45cc213-a308-436f-b5a2-9592021f6c16",
      "name": "Wait for Scan",
      "type": "n8n-nodes-base.wait",
      "notes": "Waits 30 seconds for the scan to complete before polling.",
      "position": [
        1264,
        32
      ],
      "parameters": {
        "unit": "seconds",
        "amount": 30
      },
      "typeVersion": 1.1
    },
    {
      "id": "d595a7db-5a95-4cef-a502-0ba35bbdf01f",
      "name": "Poll Task Status",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Polls get_task_status for scan results. The regex extracts the task_id from the scan response.",
      "position": [
        1472,
        32
      ],
      "parameters": {
        "url": "={{ $env.WAFTESTER_MCP_URL || 'http://waftester:8080/mcp' }}",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "jsonBody": "={\n  \"jsonrpc\": \"2.0\",\n  \"id\": 3,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"get_task_status\",\n    \"arguments\": {\n      \"task_id\": \"{{ $('Start Scan').item.json.result.content[0].text.match(/task_id[\\\":\\\\s]+(task-[a-f0-9-]+)/)?.[1] || $('Start Scan').item.json.result.content[0].text }}\"\n    }\n  }\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "78af2954-d79f-43cc-b17e-9b67375fc3c7",
      "name": "Parse Results",
      "type": "n8n-nodes-base.code",
      "notes": "Extracts detection rate, bypass count, and compares against the WAF_PASS_THRESHOLD.",
      "position": [
        1664,
        32
      ],
      "parameters": {
        "jsCode": "// Parse the MCP JSON-RPC response and calculate pass/fail\nconst response = $input.first().json;\nconst resultText = response.result?.content?.[0]?.text || '{}';\n\nlet parsed;\ntry {\n  parsed = JSON.parse(resultText);\n} catch (e) {\n  parsed = { error: resultText };\n}\n\nconst detectionRate = parsed.detection_rate || parsed.detectionRate || 0;\nconst threshold = Number($env.WAF_PASS_THRESHOLD) || 90;\nconst passed = detectionRate >= threshold;\n\nreturn [{\n  json: {\n    passed,\n    detection_rate: detectionRate,\n    threshold,\n    total_tests: parsed.total_tests || parsed.totalTests || 0,\n    blocked: parsed.blocked || 0,\n    bypasses: parsed.bypasses || parsed.bypass_count || 0,\n    waf_vendor: parsed.waf_vendor || parsed.wafVendor || 'unknown',\n    details: parsed\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "7b655595-dc14-4e30-b5f1-13c1b314e6ab",
      "name": "Pass or Fail?",
      "type": "n8n-nodes-base.if",
      "notes": "Routes to pass (HTTP 200) or fail (HTTP 422) response based on detection rate vs threshold.",
      "position": [
        1872,
        32
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "d8246093-052a-4395-9b7b-7a23d3a36abb",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.passed }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6dce4ceb-108e-47a5-b87c-c5bf9d140835",
      "name": "Respond Pass",
      "type": "n8n-nodes-base.respondToWebhook",
      "notes": "Returns HTTP 200 \u2014 deployment can proceed.",
      "position": [
        2064,
        -64
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json",
        "responseBody": "={\n  \"status\": \"pass\",\n  \"detection_rate\": {{ $json.detection_rate }},\n  \"threshold\": {{ $json.threshold }},\n  \"total_tests\": {{ $json.total_tests }},\n  \"blocked\": {{ $json.blocked }},\n  \"waf_vendor\": \"{{ $json.waf_vendor }}\"\n}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "55ffe1fa-8a43-4aed-9f99-676333e1778c",
      "name": "Respond Fail",
      "type": "n8n-nodes-base.respondToWebhook",
      "notes": "Returns HTTP 422 \u2014 deployment should be blocked. Includes bypass details.",
      "position": [
        2064,
        128
      ],
      "parameters": {
        "options": {
          "responseCode": 422
        },
        "respondWith": "json",
        "responseBody": "={\n  \"status\": \"fail\",\n  \"detection_rate\": {{ $json.detection_rate }},\n  \"threshold\": {{ $json.threshold }},\n  \"total_tests\": {{ $json.total_tests }},\n  \"blocked\": {{ $json.blocked }},\n  \"bypasses\": {{ $json.bypasses }},\n  \"waf_vendor\": \"{{ $json.waf_vendor }}\"\n}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "d7f85de9-f0b3-4b38-8eab-ee80d5ad1181",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "notes": "",
      "position": [
        -96,
        -240
      ],
      "parameters": {
        "width": 700,
        "height": 440,
        "content": "### How it works\n\n1. CI/CD pipeline POSTs target URL and categories to the webhook\n2. Detects WAF vendor, then runs security scan\n3. Polls for results and evaluates detection rate vs threshold\n4. Returns HTTP 200 (pass) or 422 (fail) so pipeline can gate\n\n### Setup steps\n\n1. Start WAFtester MCP server via Docker\n2. Set `WAFTESTER_MCP_URL` and `WAF_PASS_THRESHOLD` env vars\n3. Copy webhook URL into your CI/CD pipeline config\n4. Send POST with `{\"target\": \"...\", \"categories\": [...]}`"
      },
      "typeVersion": 1
    },
    {
      "id": "3323c452-47a0-4e78-837a-07a6c84912e1",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "notes": "",
      "position": [
        640,
        -240
      ],
      "parameters": {
        "width": 980,
        "height": 144,
        "content": "## WAF Detection & Scan\n\nDetects WAF vendor and runs async security scan with 30s polling delay."
      },
      "typeVersion": 1
    },
    {
      "id": "7e1c0ca5-48d1-4882-b8f1-811c849f0b60",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "notes": "",
      "position": [
        1648,
        -240
      ],
      "parameters": {
        "width": 600,
        "height": 144,
        "content": "## Gate Decision\n\nThe Code node calculates detection rate. If it meets the threshold, the pipeline proceeds. Otherwise, deployment is blocked with bypass details in the response body."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Detect WAF": {
      "main": [
        [
          {
            "node": "Start Scan",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Scan": {
      "main": [
        [
          {
            "node": "Wait for Scan",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Results": {
      "main": [
        [
          {
            "node": "Pass or Fail?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pass or Fail?": {
      "main": [
        [
          {
            "node": "Respond Pass",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Fail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Scan": {
      "main": [
        [
          {
            "node": "Poll Task Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deploy Webhook": {
      "main": [
        [
          {
            "node": "Detect WAF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll Task Status": {
      "main": [
        [
          {
            "node": "Parse Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "description": "## What it does\n\nA CI/CD quality gate that blocks deployments when WAF protection is insufficient. Your pipeline sends a webhook with the target URL, the workflow runs WAFtester scans, and returns a pass/fail HTTP response the pipeline can gate on.\n\n## Who it's for\n\n- DevOps teams enforcing security gates in CI/CD\n- Platform engineers automating deployment approvals\n- Security teams requiring pre-deploy WAF validation\n\n## How it works\n\n1. Your CI/CD pipeline POSTs `{\"target\": \"https://staging.example.com\", \"categories\": [\"sqli\", \"xss\"]}` to the webhook\n2. The workflow detects the WAF vendor and starts a security scan\n3. It polls for results, then evaluates the detection rate against a configurable threshold\n4. HTTP 200 is returned if the detection rate passes (deploy allowed), or HTTP 422 with bypass details if it fails (deploy blocked)\n\n## How to set up\n\n1. Start WAFtester: `docker run -p 8080:8080 ghcr.io/waftester/waftester:latest mcp --http :8080`\n2. Set environment variables: `WAFTESTER_MCP_URL` and `WAF_PASS_THRESHOLD` (default: 90)\n3. Copy the webhook URL from the Webhook node into your CI/CD pipeline\n4. Activate the workflow\n\n## Requirements\n\n- WAFtester MCP server (Docker)\n- CI/CD pipeline that can call webhooks and read HTTP responses\n\nOnly test targets you have authorization to test."
}
Pro

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

About this workflow

A CI/CD quality gate that blocks deployments when WAF protection is insufficient. Your pipeline sends a webhook with the target URL, the workflow runs WAFtester scans, and returns a pass/fail HTTP response the pipeline can gate on.

Source: https://n8n.io/workflows/13445/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

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

Web Scraping

This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di

n8n, Execute Workflow Trigger, HTTP Request +1
Web Scraping

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh
Web Scraping

This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c

HTTP Request
Web Scraping

Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.

Execute Command, HTTP Request, Read Write File +1
Web Scraping

📡 This workflow serves as the central Alpha Vantage API fetcher for Tesla trading indicators, delivering cleaned 20-point JSON outputs for three timeframes: , , and . It is required by the following a

HTTP Request