Skip to main content
joão

2025 — Case Study

WAF-Proxy

Defensive Rust reverse proxy with bounded HTTP inspection, anomaly scoring, constrained Lua rules, configurable failure behavior, and Prometheus telemetry.

RustHyper 1TokioLua 5.4Prometheustracing
01

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.

02

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.

proxy.rs

HTTP boundary — request IDs, optional rate limiting, framing checks, bounded inspection buffering, WAF invocation, failure policy, upstream timeouts, and sanitized forwarding

/waf

Request model, bounded normalization, built-in SQLi/XSS/traversal/command-injection indicators, protocol findings, exclusions, anomaly scoring, and allow/log/block decisions

/lua

Embedded Lua 5.4 rule execution with a concurrency semaphore, instruction budget, and removal of file, OS, package, debug, and module-loading globals

Metrics

Prometheus counters for requests, blocks, rule matches, and upstream errors plus request and WAF-evaluation latency histograms

03

Engineering Decisions

01

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.

02

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.

03

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.

04

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.

05

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.