Plugins

Firewall

Allow, block, redirect, or modify requests with ordered rules evaluated against any request attribute.

Firewall Plugin

The firewall plugin evaluates conditions against every request and can allow, block, redirect, or modify headers. Rules are evaluated in order — first match wins.

Configuration

{
  "plugins": {
    "firewall": {
      "enabled": true,
      "rules": [
        {
          "id": "rule-id",
          "name": "Human readable name",
          "enabled": true,
          "conditions": [
            {
              "left": "req.path",
              "operator": "starts_with",
              "value": "/admin/"
            }
          ],
          "action": {
            "type": "block",
            "status": 403
          }
        }
      ]
    }
  }
}

Rule structure

FieldTypeRequiredDescription
idstring✅Unique rule identifier
namestring✅Human-readable name
enabledbool—Enable/disable rule (default: true)
conditionsarray✅Matching conditions (see Conditions)
actionobject✅Action to take when conditions match
Firewall rules with empty conditions are rejected at startup (unlike rate limit rules, where empty conditions mean "always"). A firewall rule must always state when it applies.

Actions

Block

Returns an error response and stops the pipeline.

{
  "action": {
    "type": "block",
    "status": 403
  }
}

Response body:

{"error": "blocked", "status": 403, "rule_id": "rule-id"}

Allow

Continues the pipeline (skips remaining firewall rules). Useful for whitelisting.

{
  "action": {
    "type": "allow"
  }
}

Redirect

Returns a redirect response.

{
  "action": {
    "type": "redirect",
    "status": 302,
    "location": "https://example.com/blocked"
  }
}
FieldTypeDefaultDescription
statusint302HTTP redirect status code
locationstring"/"Redirect destination URL

Set request header

Injects headers into the request context for later plugins. The request continues to the upstream unchanged by this action.

{
  "action": {
    "type": "set_request_header",
    "headers": {
      "X-Custom-Header": "value",
      "X-Client-Country": "${ip.country}"
    }
  }
}

Injected headers are visible to everything downstream in the pipeline — condition resolution, rate-limit keys, and cache keys — but are not written to the upstream request. (The gateway adds its own X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-For headers upstream.)

This makes set_request_header a request-context tagging tool: annotate a request with x-risk-tier: high in the firewall, then key a rate limit or cache rule on it. The Test suite verifies this visibility with a dedicated probe.

Set response header

Reserved for future use. The rule matches and is logged, but no response header is currently set on the wire.

Rule evaluation

Rules are evaluated in order. The first matching rule wins:

Request
  │
  ├── Rule 1: conditions match?
  │   ├── YES → execute action → DONE
  │   └── NO → continue
  │
  ├── Rule 2: conditions match?
  │   ├── YES → execute action → DONE
  │   └── NO → continue
  │
  └── No rules matched → Continue to next plugin

Examples

Block Tor exit nodes

{
  "id": "block-tor",
  "name": "Block Tor Exit Nodes",
  "enabled": true,
  "conditions": [
    { "left": "ip.is_tor", "operator": "equals", "value": true }
  ],
  "action": { "type": "block", "status": 403 }
}

Block VPN on login endpoints

{
  "id": "block-vpn-login",
  "name": "Block VPN on Login",
  "enabled": true,
  "conditions": [
    { "left": "req.path", "operator": "starts_with", "value": "/auth/" },
    { "left": "ip.is_vpn", "operator": "equals", "value": true }
  ],
  "action": { "type": "block", "status": 403 }
}

Block high-risk IPs

{
  "id": "block-high-risk",
  "name": "Block High Risk IPs",
  "enabled": true,
  "conditions": [
    { "left": "ip.risk_score", "operator": "greater_than", "value": 40 }
  ],
  "action": { "type": "block", "status": 403 }
}

Geo-block countries

{
  "id": "geo-block",
  "name": "Block specific countries",
  "enabled": true,
  "conditions": [
    { "left": "ip.country", "operator": "in", "value": ["CN", "RU", "KP"] }
  ],
  "action": { "type": "block", "status": 403 }
}

Allow admin access from specific IPs

{
  "id": "allow-admin-whitelist",
  "name": "Allow Admin Whitelist",
  "enabled": true,
  "conditions": [
    { "left": "req.path", "operator": "starts_with", "value": "/admin/" },
    { "left": "req.ip", "operator": "not_in", "value": ["10.0.0.1", "10.0.0.2"] }
  ],
  "action": { "type": "block", "status": 403 }
}

Block by User-Agent

{
  "id": "block-bots",
  "name": "Block known bots",
  "enabled": true,
  "conditions": [
    { "left": "req.user_agent", "operator": "regex", "value": "(?i)(bot|crawler|spider|scraper)" }
  ],
  "action": { "type": "block", "status": 403 }
}

Redirect old URLs

{
  "id": "redirect-old-api",
  "name": "Redirect old API",
  "enabled": true,
  "conditions": [
    { "left": "req.path", "operator": "starts_with", "value": "/v1/" }
  ],
  "action": {
    "type": "redirect",
    "status": 301,
    "location": "https://api.example.com/v2/"
  }
}

Set custom headers for upstream

A firewall rule must have at least one condition — rules with empty conditions are rejected at startup. Use a catch-all condition like "req.ip" exists when a rule should apply to every request.
{
  "id": "inject-geo-headers",
  "name": "Inject Geo Headers",
  "enabled": true,
  "conditions": [
    { "left": "req.ip", "operator": "exists" }
  ],
  "action": {
    "type": "set_request_header",
    "headers": {
      "X-Client-Country": "${ip.country}",
      "X-Client-ASN": "${ip.asn}",
      "X-Client-Risk": "${ip.risk_score}"
    }
  }
}

Allow specific ASN (whitelist)

{
  "id": "allow-whitelist-asn",
  "name": "Allow Whitelist ASN",
  "enabled": true,
  "conditions": [
    { "left": "ip.asn", "operator": "in", "value": [13335, 16509, 15169] }
  ],
  "action": { "type": "allow" }
},
{
  "id": "block-everyone-else",
  "name": "Block Non-Whitelisted",
  "enabled": true,
  "conditions": [
    { "left": "req.ip", "operator": "exists" }
  ],
  "action": { "type": "block", "status": 403 }
}

Log output

{
  "firewall": {
    "matched": true,
    "rule_id": "block-tor",
    "action": "Block"
  }
}

When no rule matches:

{
  "firewall": {
    "matched": false,
    "rule_id": null,
    "action": null
  }
}
Copyright © 2026