Configuration

The proxy reads one TOML file (--config). This is the reference for the open-core sections. Certificate management at scale (ACME, mTLS) and the enterprise modules are covered on the boundary page.

Proxy & backend

[proxy]
listen  = "0.0.0.0:8080"          # where the WAF accepts client traffic
backend = "http://127.0.0.1:3000" # your origin; allowed requests are forwarded here

TLS

Termination is off by default (the listener serves cleartext h1 + h2c). When enabled, the listener serves only TLS; an unreadable certificate at boot is fatal (fail-closed, no downgrade).

[tls]
enabled   = true
cert_path = "/etc/waf/tls/cert.pem"   # PEM chain, leaf first
key_path  = "/etc/waf/tls/key.pem"    # PKCS#8 / PKCS#1 / SEC1
alpn      = ["h2", "http/1.1"]

Automatic ACME issuance/rotation and mTLS with managed PKI are enterprise features that plug into the same TLS seam.

Detection & the block threshold

No single rule blocks a request — matched rules add points to a cumulative anomaly score, and the request is denied at the threshold.

[waf]
mode            = "blocking"   # "blocking" enforces | "detection" observes only
block_threshold = 5            # deny when the score reaches this
paranoia_level  = 1            # 1..=4 — higher activates more (noisier) rules

Severity maps to points here — this table, with the threshold, is your main tuning surface:

[waf.severity_scores]
critical = 6   # a single critical (6) blocks on its own at threshold 5
error    = 4
warning  = 3   # two warnings (3+3) also block
notice   = 2   # a lone notice never blocks

Resilience

Explicit policy for when the WAF itself is degraded. Security failures fail closed; infrastructure failures fail open.

[resilience]
on_upstream_error   = "fail_closed"   # origin down/timeout → 502 (closed) | 503 (open)
on_internal_error   = "fail_open"     # a module panic must not take down the site
on_config_error     = "fail_open"     # a bad reload → keep last-good config
on_parser_limit     = "fail_closed"   # normalization failed → 400 (closed)
upstream_timeout_ms = 30000

Rate limiting

A per-client L7 token bucket. In the open core this is single-node; the enterprise tier shares one budget across a cluster via the same policy.

[rate_limit]
enabled        = true
key            = "client_ip"
requests       = 100        # tokens per window
window_seconds = 60
burst          = 100        # bucket capacity (default = requests)
action         = "block"    # "block" → 429 | "score" → add points

Client-IP resolution

The client IP is derived once and reused. Fail-safe: with trusted_proxies empty, X-Forwarded-For is always ignored and the peer address is used — an unconfigured deploy can't be spoofed.

[network]
trusted_proxies  = ["10.0.0.0/8", "::1"]   # CIDRs of YOUR proxies/LBs/CDNs
client_ip_header = "X-Forwarded-For"
trusted_hops     = 1                        # hops to trust from the RIGHT

Request limits

Structural caps; an oversized or malformed request is rejected before content inspection.

[limits]
max_body_size   = 1048576   # 1 MiB
max_header_size = 8192
max_headers     = 100
max_params      = 100
max_cookies     = 50
max_json_depth  = 20

Detection modules

Each native module is toggled on its own table. See the module catalogue for what they catch.

[modules.sqli]
enabled = true

[modules.xss]
enabled = true
# …rce, ssrf, path_traversal, lfi_rfi, ldap, nosql, ssti,
#   ssi, xxe, mail, scanner, header_injection, request_smuggling

Structural GraphQL and gRPC caps (depth / aliases / fields / batch) are opt-in and endpoint-specific:

[modules.graphql]
enabled   = false
paths     = ["/graphql"]
max_depth = 15
max_batch = 10

[modules.grpc]
enabled           = false
max_message_bytes = 4194304
on_compressed     = "reject"   # opaque payload: "reject" | "passthrough"

The CRS engine loads SecRule .conf files, and the WASM runtime loads Proxy-Wasm plugins:

[modules.crs]
enabled = true
files   = ["/etc/waf/rules.conf"]

[modules.wasm]
enabled = false
# plugins = [ ... ]  — signature verification is an enterprise feature