diap.dev
Back to all articles
•
#Security#WAF#Testing#k6

Testing WAF Rules Before Production: Rate Limits, OWASP CRS, and Bot Mitigation

A practitioner's guide to testing WAF rules, OWASP CRS anomaly scoring, and rate limits in CI: attack matrices, false-positive safety, and k6 verification.

In enterprise edge infrastructure, the single most dangerous configuration change is an untested firewall rule deployed directly to production.

In practice, the failure pattern is almost always symmetric. On one side is the false positive catastrophe: an infrastructure team enables an aggressive OWASP Core Rule Set (CRS) rule to mitigate an emerging SQL injection vector. Within minutes, legitimate client market orders fail with HTTP 403 Forbidden. A valid trading payload containing technical terms in comment fields—such as "Order update: select execution route"—triggered an uncalibrated regex rule, dropping transactions during peak market hours.

On the other side is the false negative illusion: assuming perimeter defenses protect client portals against credential stuffing, distributed Layer 7 floods, or parameter tampering, only to discover during an audit that rule evaluation ordering or payload truncation allowed malicious requests to slip through undetected.

Perimeter security—specifically Cloudflare WAF rules, OWASP CRS anomaly scoring, and edge rate limiting—safeguards critical trading flows, wallet APIs, and client portals. But firewall rules that lack automated regression tests in a pre-production pipeline represent unquantified production risk.

Treating Firewall Rules as Code means applying software testing standards to edge security: automated attack matrices, false-positive verification suites, deterministic rate-limit testing with k6, and disciplined staged rollout pipelines.


1. Mental Model: Anomaly Scoring vs. Destructive Regex Traps

To test a Web Application Firewall effectively, we must first understand how modern edge inspection engines evaluate incoming HTTP traffic.

A. Traditional Regex Matching vs. Collaborative Anomaly Scoring

Early WAF implementations operated in destructive mode: matching a single signature (such as UNION SELECT) immediately triggered an HTTP 403 block. This created unacceptably high false-positive rates on complex JSON payloads and multi-lingual inputs. Modern enterprise WAFs—such as the OWASP ModSecurity Core Rule Set (CRS v3 and v4) and Cloudflare Managed Rulesets—rely instead on collaborative anomaly scoring:

In anomaly scoring mode, individual rules do not block requests directly. Instead, each triggered rule increments an inbound anomaly score based on severity:

| Severity Level | Score Contribution | Example Finding | |---|---|---| | CRITICAL | +5 points | Confirmed SQLi, RCE, or path traversal syntax | | ERROR | +4 points | Protocol violation or known scanner signature | | WARNING | +3 points | Suspicious user agent, missing standard headers | | NOTICE | +2 points | Minor protocol anomaly or non-standard encoding |

The edge firewall compares cumulative anomaly scores against a configurable threshold (typically 5). A request with a minor encoding quirk (Score: 2) passes cleanly. A request exhibiting an SQL fragment (+5) alongside an obfuscated encoding pattern (+3) reaches a score of 8, exceeding the threshold and triggering an immediate block or managed challenge.

B. Understanding Paranoia Levels (PL1 Through PL4)

The OWASP Core Rule Set organizes rules into four hierarchical Paranoia Levels (PL):

  • PL1 (Baseline): High-fidelity rules with near-zero false positives. Suitable for all public APIs.
  • PL2 (Enhanced): Stricter regex patterns, sensitive to complex nested JSON structures.
  • PL3 (High Sensitivity): Aggressively inspects query parameters and headers; flags SQL words in user text.
  • PL4 (Extreme Hardening): Strict ASCII enforcement and severe length limits. Reserved for sensitive banking portals.

Validating rules against your targeted Paranoia Level in CI ensures edge hardening does not introduce regressions into legitimate business journeys.


2. Edge Request Lifecycle & Architecture

A production-grade edge architecture processes traffic through multiple inspection stages before requests reach origin microservices:

EDGE FIREWALL INGRESS INSPECTION & ANOMALY EVALUATION
Loading diagram…
ETHICAL SCOPE & AUTHORIZATION BOUNDARY

Security testing against perimeter defenses must strictly be performed against dedicated staging environments, sandbox endpoints, or pre-production origins that you own or have explicit written authorization to test. Never run automated attack matrices or high-rate traffic against third-party endpoints or production infrastructure without a formal rules-of-engagement agreement.


3. Technique 1: Automated Attack & Legitimate-Traffic Matrix in Pytest

The core of WAF verification is a two-sided test matrix:

  1. Attack Matrix: Verify that known public attack vectors (SQL injection, XSS, path traversal) trigger edge blocking (HTTP 403).
  2. Legitimate Journeys: Verify that genuine business transactions—especially those containing punctuation, symbols, and technical vocabulary—pass through without false positives (HTTP 200 or HTTP 201).

Below is an automated verification suite in Python using pytest and requests, testing against standard public OWASP test set samples (never weaponized or novel exploits):

import os, requests, pytest

TARGET = os.getenv("WAF_TEST_TARGET", "https://staging-api.example.com")
TOKEN = os.getenv("WAF_TEST_TOKEN", "test-token")
HEADERS = {
    "User-Agent": "WAF-Harness/2.4",
    "Authorization": f"Bearer {TOKEN}",
    "Content-Type": "application/json",
}

ATTACK_VECTORS = [
    ("sqli_tautology", "/api/v1/account", "GET", {"id": "1' OR '1'='1"}, None, 403),
    ("xss_script", "/api/v1/profile", "POST", None, {"bio": "<script>alert(1)</script>"}, 403),
    ("path_traversal", "/api/v1/docs", "GET", {"f": "../../../../etc/passwd"}, None, 403),
]

LEGITIMATE_JOURNEYS = [
    ("order_placement", "/api/v1/orders", "POST", None, {"symbol": "EURUSD", "volume": 0.5, "side": "BUY"}, 201),
    ("search_faq", "/api/v1/faq", "GET", {"query": "how to select a different currency account"}, None, 200),
]

@pytest.mark.parametrize("name, path, method, params, body, expected", ATTACK_VECTORS)
def test_waf_blocks_attack_payloads(name, path, method, params, body, expected):
    res = requests.request(method, f"{TARGET}{path}", headers=HEADERS, params=params, json=body, timeout=10)
    assert res.status_code == expected, f"Expected {expected}, got {res.status_code} on {name}"

@pytest.mark.parametrize("name, path, method, params, body, expected", LEGITIMATE_JOURNEYS)
def test_waf_allows_legitimate_traffic(name, path, method, params, body, expected):
    res = requests.request(method, f"{TARGET}{path}", headers=HEADERS, params=params, json=body, timeout=10)
    assert res.status_code == expected, f"False positive on {name}: got {res.status_code}"

Key Engineering Heuristics:

  • Zero-Tolerance Gate: A failure in test_waf_allows_legitimate_traffic is an immediate deployment blocker.
  • Realistic Request Profiles: Test requests must specify realistic User-Agent and authentication headers. Default curl agents trigger generic bot mitigations rather than targeted CRS rules under test.

4. Technique 2: Verifying Edge Rate Limits with k6 at Controlled Rates

Rate limiting rules cannot be verified through single-threaded requests. Validating a token bucket or sliding window counter requires multi-threaded arrival rate generation.

When configuring rate limits for financial APIs, we test three operational properties:

  1. Pass-Through Capacity: Traffic below the rate limit executes with HTTP 200 responses and minimal edge latency overhead.
  2. Threshold Enforcement: Traffic exceeding the burst threshold receives HTTP 429 Too Many Requests with a valid Retry-After header.
  3. Recovery Determinism: Once arrival rates return to baseline, clients immediately regain access without sticky lockout penalties.

Here is a k6 test script validating edge rate limits under controlled arrival rates:

import http from 'k6/http';
import { check } from 'k6';
import { Rate } from 'k6/metrics';

export const rateLimitBlocks = new Rate('waf_blocks');
export const legitimatePasses = new Rate('waf_passes');

export const options = {
  scenarios: {
    below_limit: {
      executor: 'constant-arrival-rate',
      rate: 30, timeUnit: '1s', duration: '30s',
      preAllocatedVUs: 10, maxVUs: 30, exec: 'testUnderLimit',
    },
    exceed_limit: {
      executor: 'constant-arrival-rate',
      rate: 120, timeUnit: '1s', duration: '30s', startTime: '35s',
      preAllocatedVUs: 20, maxVUs: 50, exec: 'testOverLimit',
    },
  },
  thresholds: {
    waf_passes: ['rate>0.99'],
    http_req_duration: ['p(95)<150'],
  },
};

const BASE_URL = __ENV.WAF_TARGET_URL || 'https://staging-api.example.com';
const headers = { 'Authorization': `Bearer ${__ENV.WAF_CLIENT_TOKEN || 'test-token'}` };

export function testUnderLimit() {
  const res = http.get(`${BASE_URL}/api/v1/quotes/EURUSD`, { headers });
  legitimatePasses.add(check(res, { 'status is 200': (r) => r.status === 200 }));
}

export function testOverLimit() {
  const res = http.get(`${BASE_URL}/api/v1/quotes/EURUSD`, { headers });
  rateLimitBlocks.add(check(res, {
    'status is 429': (r) => r.status === 429,
    'retry-after present': (r) => r.headers['Retry-After'] !== undefined,
  }));
}
RATE LIMITING BY COMPOSITE KEYS INSTEAD OF NAKED IP

In enterprise trading environments, multiple institutional clients or office branches route through shared corporate proxy gateways or NAT pools. If edge rate limiting is configured strictly on client.ip, an entire corporate office can be blocked due to one aggressive script. Always configure rate limits on composite keys: client.ip combined with authenticated session tokens or API keys (hash(ip + token)).


5. The Staged Rollout Pipeline: Log/Simulate -> Challenge -> Block

Testing in staging is necessary, but production traffic always presents unanticipated edge cases. To eliminate the risk of production outages, we enforce a strict 3-stage rollout protocol:

Stage 1: Log / Simulate (Observation Mode)

When a new custom firewall rule or CRS threshold is introduced, its action is initially set to log (or simulate in Cloudflare). The rule inspects every request and logs matched rule IDs and anomaly scores, but the request passes to origin unblocked.

We maintain observation mode for at least 48 to 72 hours to capture:

  • Peak daily trading volume surges.
  • Market open and market close volatility windows.
  • Scheduled batch processing jobs and third-party partner integrations.

We query edge log pipelines to identify unintended rule matches:

SELECT ClientRequestPath, SecurityRuleID, count() AS TotalHits
FROM edge_security_logs
WHERE SecurityAction = 'log' AND SecurityRuleID = 'custom-rule-10492'
GROUP BY ClientRequestPath, SecurityRuleID
ORDER BY TotalHits DESC LIMIT 50;

If legitimate endpoints appear in the log output, URI exclusions or rule parameter exceptions must be authored before advancing.

Stage 2: Managed Challenge (Soft Enforcement)

For suspicious traffic patterns, automated scrapers, or borderline bot scores, jumping directly from log to block risks rejecting legitimate users with non-standard browsers.

In Stage 2, the action is updated to managed_challenge (or Turnstile). Legitimate interactive users receive an imperceptible JavaScript challenge that resolves in milliseconds. Headless attack scripts, scrapers, and automated bots fail the challenge and are terminated at the edge.

Stage 3: Block (Hard Enforcement)

Once observation telemetry demonstrates zero legitimate traffic disruption and challenge success metrics validate cleanly, the rule action is upgraded to block (HTTP 403). Malicious traffic is terminated at the perimeter before consuming backend compute or database capacity.


6. Edge Failure Modes and Production Pitfalls

Over years of managing high-throughput perimeter firewalls, these are the most common architectural blind spots:

A. Payload Truncation Limits

Edge WAF engines do not buffer arbitrarily large request bodies. Cloudflare WAF inspects up to the first 128 KB (or 500 KB on Enterprise tiers) of an incoming HTTP request body.

PAYLOAD TRUNCATION INSPECTION BLIND SPOTS

If an attacker pads an HTTP POST request with 130 KB of harmless whitespace or benign JSON keys before injecting a malicious payload, the exploit sits beyond the edge inspection buffer and reaches your backend unnoticed. Perimeter security reduces attack volume, but backend input validation and parameterized SQL queries remain strictly mandatory.

B. IPv6 Subnet Clustering Pitfall

Unlike IPv4 where clients occupy single /32 addresses, IPv6 ISPs allocate entire /64 prefixes to individual connections. A single client can rotate across millions of distinct /128 host addresses within that prefix. Edge rate limits must aggregate IPv6 traffic on /64 subnets rather than single /128 host addresses.

C. Header Spoofing and Proxy Trust

A common vulnerability occurs when rate limiting or custom rules evaluate client identity based on unverified headers such as X-Forwarded-For. If your origin or edge does not strip incoming client-supplied headers, an attacker can bypass rate limits simply by rotating the header value.

Always configure edge rules to evaluate verified edge properties—such as cf.connecting.ip—which are asserted cryptographically by the edge proxy and cannot be spoofed by client headers.


7. When Not to Rely on WAF Alone

A Web Application Firewall is an edge perimeter filter; it is not an architectural panacea. Recognizing its operational boundaries prevents dangerous security complacency.

| Scenario | WAF Suitability | Recommended Architectural Solution | |---|---|---| | SQL Injection & XSS | First-line perimeter filter | Parameterized SQL queries, typed ORMs, contextual HTML escaping | | Business Logic Flaws | Low (WAF cannot parse domain state) | Domain-driven validations, state machine checks in backend code | | Internal Microservices | Anti-pattern (Adds edge hop latency) | Mutual TLS (mTLS), Kubernetes network policies, VPC isolation |

Relying on WAF "virtual patching" as a permanent fix for vulnerable backend code creates technical debt. Virtual patching is an emergency tactical measure to buy time while engineering squads author, test, and deploy the structural code fix.


8. The Production WAF Verification Checklist

Before deploying or modifying perimeter firewall rules in production, verify every item on this pre-flight engineering checklist:

  • [ ] Dual-Matrix Staging Pass: Automated Pytest suite executes clean against staging endpoints, verifying that attack payloads are blocked (HTTP 403) and legitimate business journeys pass (HTTP 200/201).
  • [ ] Rate Limit Calibration: k6 load scenarios verify that normal user volume stays below the rate threshold, while synthetic bursts cleanly trigger HTTP 429 with Retry-After.
  • [ ] Composite Rate Keys: Rate limits keyed on composite identifiers (IP + Session Token or IP + API Key) to prevent corporate NAT office lockouts.
  • [ ] IPv6 Subnet Masking: Edge rate limits configured to aggregate IPv6 clients on /64 subnets rather than /128 host IPs.
  • [ ] Payload Size Safety: Backend applications validate and enforce strict maximum payload sizes, eliminating edge truncation blind spots.
  • [ ] Header Authenticity: Edge rules rely strictly on verified edge metadata (cf.connecting.ip) rather than spoofable client request headers.
  • [ ] Log/Simulate Staged Promotion: New rules promoted through observation mode for 48–72 hours across peak production cycles before enabling active blocking.
  • [ ] Real-Time Edge Observability: Log streams active, with automated alerts configured for unexpected spikes in perimeter HTTP 403 or HTTP 429 error rates.