Phase 8: WAF & Advanced Security

Goal

Request-level security filtering built into bext — IP filtering, geo-blocking, request inspection, bot detection, and DDoS mitigation — so production deployments don't need Cloudflare or a separate WAF.

Current State

  • Rate limiting: per-IP token bucket (600 req/min default, middleware/rate_limit.rs)
  • Security headers: CSP, HSTS, X-Frame-Options (builtin plugin)
  • SSRF prevention: private IP blocking in plugin host functions
  • CORS: origin allowlisting
  • No IP allow/deny lists
  • No geo-blocking
  • No request body inspection
  • No bot detection
  • No DDoS mitigation beyond basic rate limits

Design

Security Middleware Stack

Insert before all other middleware (after TLS termination):

Request → IP Filter → Geo Block → Rate Limit → Request Inspect → Bot Detect → ... → App

1. IP Filtering

Allow/deny lists with CIDR support:

[security.ip_filter]
mode = "deny"                     # allow | deny | off

# Deny list (blocked IPs/ranges)
deny = [
    "192.168.1.0/24",
    "10.0.0.5",
    "2001:db8::/32",
]

# Allow list (always permitted, bypasses deny)
allow = [
    "10.0.0.1",                   # Monitoring
    "192.168.1.100",              # Admin
]

# Action when blocked
deny_status = 403
deny_body = "Forbidden"

# Optional: load from file (hot-reloaded)
deny_file = "/etc/bext/blocklist.txt"

Implementation: Patricia trie for O(prefix_length) CIDR lookup.

2. Geo-Blocking

Block or allow by country code using MaxMind GeoLite2 database:

[security.geo]
enabled = true
database = "/etc/bext/GeoLite2-Country.mmdb"  # MaxMind DB path

# Block mode: deny listed countries
mode = "deny"
countries = ["CN", "RU", "KP"]

# Or allow mode: only allow listed countries
# mode = "allow"
# countries = ["FR", "DE", "US", "GB"]

# Bypass for specific paths (e.g., health checks)
bypass_paths = ["/health", "/.well-known/*"]

# Header to check for real IP (behind proxy)
real_ip_header = "X-Forwarded-For"

Implementation: maxminddb crate for mmdb parsing, cached in memory.

3. Request Inspection (Mini-WAF)

Rule-based request filtering inspired by OWASP ModSecurity Core Rule Set, but lightweight:

[security.waf]
enabled = true
mode = "block"                    # block | log-only | off

# Built-in rule sets
[security.waf.rules]
sql_injection = true              # Detect SQLi patterns in query/body
xss = true                        # Detect XSS patterns in query/body
path_traversal = true             # Block ../ in URLs
shell_injection = true            # Detect shell metacharacters
protocol_violation = true         # Malformed HTTP requests
scanner_detection = true          # Block known scanner UAs

# Custom rules
[[security.waf.custom_rules]]
name = "block-wp-scans"
match = { path = "/wp-admin/*" }
action = "block"
status = 404

[[security.waf.custom_rules]]
name = "rate-limit-login"
match = { path = "/api/auth/login", method = "POST" }
action = "rate-limit"
rate = "5/minute"

[[security.waf.custom_rules]]
name = "require-api-key"
match = { path = "/api/v1/**" }
condition = { header_missing = "X-API-Key" }
action = "block"
status = 401

Rule patterns (regex-based, compiled at startup):

lazy_static! {
    static ref SQL_INJECTION_PATTERNS: Vec = vec![
        Regex::new(r"(?i)(union\s+select|or\s+1\s*=\s*1|drop\s+table|;\s*delete)").unwrap(),
        Regex::new(r"(?i)('|\"|;|--)\s*(or|and|union|select|drop|delete|insert|update)").unwrap(),
    ];
    static ref XSS_PATTERNS: Vec = vec![
        Regex::new(r"(?i)<script[^>]*>").unwrap(),
        Regex::new(r"(?i)(javascript|vbscript|data):").unwrap(),
        Regex::new(r"(?i)on(load|error|click|mouse|focus)\s*=").unwrap(),
    ];
    static ref PATH_TRAVERSAL: Regex = Regex::new(r"(\.\./|\.\.\\|%2e%2e)").unwrap();
}

4. Bot Detection

Distinguish humans from bots using behavioral signals:

[security.bot]
enabled = true
mode = "challenge"                # block | challenge | log-only

# Known good bots (skip detection)
allow = [
    "Googlebot",
    "Bingbot",
    "Slurp",
    "DuckDuckBot",
    "facebookexternalhit",
]

# Detection signals
[security.bot.signals]
missing_accept_header = true      # Bots often omit Accept
tls_fingerprint = true            # JA3/JA4 fingerprinting
request_rate_anomaly = true       # Burst detection
known_scanner_ua = true           # nikto, sqlmap, etc.

Challenge mode serves a lightweight JS challenge (< 1KB) that browsers execute automatically but headless bots often fail.

5. DDoS Mitigation

Beyond per-IP rate limiting — connection-level and pattern-based protection:

[security.ddos]
enabled = true

# Connection limiting
max_connections_per_ip = 50       # Concurrent connections per IP
max_new_connections_per_second = 20  # Connection rate per IP

# Request limiting (layered on top of rate_limit)
max_request_body_size = "10mb"    # Reject oversized bodies
slowloris_timeout_ms = 10000      # Close slow-reading connections
header_count_limit = 100          # Max headers per request
header_size_limit = "8kb"         # Max total header size

# Automatic throttling under load
[security.ddos.auto_throttle]
enabled = true
cpu_threshold = 80                # Start throttling at 80% CPU
response_time_threshold_ms = 500  # Start throttling when avg response > 500ms
throttle_ratio = 0.5              # Reject 50% of new connections when throttling

6. Rate Limit Enhancements

Extend existing rate limiter with features from nginx and Cloudflare:

[security.rate_limit]
enabled = true
default_rpm = 600                 # Global default

# Per-route rate limits
[[security.rate_limit.rules]]
pattern = "/api/auth/**"
rpm = 30
burst = 10                       # Allow burst above limit
delay = "nodelay"                # nodelay | delay (queue excess)

[[security.rate_limit.rules]]
pattern = "/api/v1/**"
rpm = 100
key = "header:X-API-Key"        # Rate limit by API key, not IP
burst = 20

# Distributed rate limiting (Redis-backed)
[security.rate_limit.distributed]
enabled = true                   # Use Redis for cross-instance limits

Response headers:

RateLimit-Limit: 100
RateLimit-Remaining: 42
RateLimit-Reset: 1711843200
Retry-After: 30                  # Only on 429 responses

Implementation

New Crate: bext-waf

bext-waf/
  src/
    lib.rs              # Public API, middleware factory
    ip_filter.rs        # CIDR allow/deny with Patricia trie
    geo.rs              # GeoIP lookup (maxminddb)
    rules/
      mod.rs            # Rule engine
      sqli.rs           # SQL injection patterns
      xss.rs            # XSS patterns
      traversal.rs      # Path traversal patterns
      scanner.rs        # Scanner UA detection
      custom.rs         # User-defined rules
    bot.rs              # Bot detection signals
    ddos.rs             # Connection limiting, slowloris, auto-throttle
    rate_limit.rs       # Enhanced rate limiter (burst, per-key, distributed)
    challenge.rs        # JS challenge for bot detection

Dependencies

Crate Purpose
maxminddb GeoIP database reader
ip_network_table Patricia trie for CIDR lookup
regex Rule pattern matching (already in deps)

actix-web Middleware Integration

pub struct WafMiddleware {
    ip_filter: Arc,
    geo_blocker: Option<Arc>,
    rule_engine: Arc,
    bot_detector: Option<Arc>,
    ddos_guard: Arc,
    rate_limiter: Arc,
}

impl WafMiddleware {
    fn check_request(&self, req: &HttpRequest) -> WafDecision {
        // 1. IP filter (fastest check)
        if let Some(block) = self.ip_filter.check(req.peer_addr()) {
            return WafDecision::Block(block);
        }

        // 2. Geo check
        if let Some(geo) = &self.geo_blocker {
            if let Some(block) = geo.check(req.peer_addr()) {
                return WafDecision::Block(block);
            }
        }

        // 3. DDoS checks (connection limits)
        if let Some(block) = self.ddos_guard.check(req) {
            return WafDecision::Block(block);
        }

        // 4. Rate limit
        if let Some(limited) = self.rate_limiter.check(req) {
            return WafDecision::RateLimit(limited);
        }

        // 5. Rule engine (SQLi, XSS, etc.)
        if let Some(violation) = self.rule_engine.inspect(req) {
            return WafDecision::Block(violation);
        }

        // 6. Bot detection
        if let Some(bot) = &self.bot_detector {
            if let Some(challenge) = bot.check(req) {
                return WafDecision::Challenge(challenge);
            }
        }

        WafDecision::Allow
    }
}

Audit Logging

All WAF decisions are logged for analysis:

struct WafEvent {
    timestamp: DateTime,
    client_ip: IpAddr,
    path: String,
    rule: String,           // "sqli", "xss", "ip-deny", "geo-block", "rate-limit"
    action: String,         // "block", "challenge", "log"
    details: String,        // Matched pattern or reason
}

Accessible via:

  • GET /__bext/waf/events — recent WAF events (bounded buffer)
  • GET /metrics — Prometheus counters per rule type
  • bext waf stats — CLI summary

Testing Plan

Test Type What it validates
CIDR allow/deny Unit IP ranges matched correctly
Patricia trie lookup Unit O(prefix_length) performance
GeoIP country lookup Unit IP → country code mapping
Geo mode allow/deny Unit Correct countries blocked/allowed
SQLi detection Unit Known injection patterns caught
XSS detection Unit Script tags, event handlers caught
Path traversal Unit ../ variants blocked
Custom rules Unit User-defined rules match and act
Bot UA detection Unit Known scanners flagged
Good bot bypass Unit Googlebot etc. allowed
DDoS connection limit Unit Excess connections rejected
Slowloris detection Unit Slow clients disconnected
Rate limit burst Unit Burst above limit allowed, excess blocked
Rate limit by key Unit Per-API-key limits independent
Rate limit headers Unit RateLimit-* headers in response
Distributed rate limit Integration Redis-backed limits work across instances
JS challenge Integration Browser passes, simple bot fails
Auto-throttle Unit Throttling activates at CPU threshold
Audit logging Unit WAF events recorded with details
Log-only mode Unit Violations logged but not blocked

Done Criteria

  • IP allow/deny with CIDR support
  • GeoIP blocking with MaxMind database
  • SQLi, XSS, path traversal, scanner detection rules
  • Custom WAF rules in config
  • Bot detection with JS challenge
  • DDoS mitigation (connection limits, slowloris, auto-throttle)
  • Enhanced rate limiting (burst, per-key, distributed)
  • Rate limit response headers (RateLimit-*, Retry-After)
  • WAF audit log endpoint
  • log-only mode for testing rules without blocking
  • Hot-reload for IP blocklists
  • All tests passing