Context
WAF-Proxy is an application-security reverse proxy built to make inspection behavior explicit and bounded. Request bodies are consumed under configured timeouts while inspection buffering is capped, then a structured request context is evaluated by Rust and Lua rules before the request is allowed, logged, or blocked according to the configured score threshold. The forwarding path preserves the original request-body bytes instead of sending normalized detector input upstream.
Architecture
The proxy separates HTTP transport, request modeling, detection, scoring, Lua execution, rate limiting, and observability into small modules. Detection receives normalized representations while the upstream request keeps its original body bytes.
HTTP boundary — request IDs, optional rate limiting, framing checks, bounded inspection buffering, WAF invocation, failure policy, upstream timeouts, and sanitized forwarding
Request model, bounded normalization, built-in SQLi/XSS/traversal/command-injection indicators, protocol findings, exclusions, anomaly scoring, and allow/log/block decisions
Embedded Lua 5.4 rule execution with a concurrency semaphore, instruction budget, and removal of file, OS, package, debug, and module-loading globals
Prometheus counters for requests, blocks, rule matches, and upstream errors plus request and WAF-evaluation latency histograms
Engineering Decisions
Preserve application input while normalizing detector input
Percent decoding is capped at two passes, selected HTML entities are decoded once, and paths are canonicalized for detection. The original Bytes body is retained separately and forwarded upstream after approval so normalization cannot rewrite application payloads.
Bound request inspection before running rules
The body stream is consumed under a request timeout, while the in-memory buffer retained for inspection is capped at the configured maximum plus one byte. The default configuration uses a 1 MiB inspection limit; an oversized request becomes an explicit finding and is blocked when the WAF runs in block mode.
Reject ambiguous framing and sanitize proxy-only headers
Requests containing both Content-Length and Transfer-Encoding are rejected before inspection. Hop-by-hop headers and stale content length are removed on both request and response forwarding boundaries.
Use explicit scoring, exclusions, and failure policy
Rules emit IDs, categories, severity, score, location, and field metadata. Scores are summed after exclusions; the result becomes allow, log, or block. Engine failures are fail-closed by default but can be configured to fail open.
Constrain extension rules instead of giving Lua host access
Each inspection gets a fresh Lua state with selected dangerous globals removed. A semaphore caps concurrent Lua work and an execution hook enforces the configured instruction budget before findings are returned to Rust.