Decision records
ADR-0013: Deterministic security engine (first stage)
- Status: Accepted (2026-10-05)
Context
Handoff §11: deterministic protocol/application rules first, modular so a mature WAF can be evaluated later; rate limits with tenant/app/route/source dimensions without unbounded in-memory key growth; slowloris and exhaustion protection.
Decision
- Path rules:
deny_pathsfrom the application’s profile plus configuration, matched case-insensitively on the normalised (decoded, dot-segment-free) path. A pattern without*matches the path or anything below it (segment-aligned);*matches any sequence of characters, including/. Denied requests get404(no confirmation that the resource exists) and are counted. - Per-source limits (
server.security.per_ip): concurrent connections (checked at accept, connection closed immediately) and a request token bucket (429). IPv4 addresses are tracked individually, IPv6 per /64. State lives in 64 shards with a fixed total capacity; when a shard is full the least recently seen idle entry is evicted, and if every entry is in use the new source is admitted untracked (fail open, counted) — the global connection limit still applies. Memory is therefore bounded regardless of the number of attacking addresses. - Slow readers: a write timeout on the connection (
limits.write_timeout) closes a connection whose writes make no progress. - Strict SNI (
listeners[].strict_sni): a request whose Host differs from the TLS SNI is answered421 Misdirected Request(clients then open a new connection), preventing domain fronting across tenants. - Rules are evaluated before routing to handlers and before the cache.
Consequences
- No trusted-proxy handling yet: behind a load balancer, per-IP limits see the balancer’s address. Configuring trusted proxies is follow-up work.
- A rule engine for request content (OWASP CRS-class) is out of scope for this stage.