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 = 20Detection 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