How to Harden Cloudflare WAF Without Bot Management
Enforcement scope must not exceed evidence scope. Every controlled check passed, but the full production flow still returned 400. At the same time, suspected automation was reaching the same API, forcing us to contain it without blocking legitimate users or blaming Cloudflare for an unrelated failure.
TL;DR
Without bot scores, block only the request slice supported by reviewed evidence. Protect critical paths inside the narrow expression when possible, verify every later Cloudflare phase, treat indeterminate reviews as potential false positives, and leave ambiguous traffic in LOG.
Keep enforcement scope inside the proof boundary
The 400 and suspicious traffic collided operationally, not causally. End-to-end browser testing observed the 400. Cloudflare events observed suspicious requests on the same API surface. Neither observation proved that Cloudflare caused the response or that every similar request was automated. The investigation found a separate cause for the 400.
One noisy public network range supplied concentrated evidence suitable for separate enforcement. A wider swarm rotated addresses and user agents while sharing geography with legitimate users. Calling that swarm automated was an inference. Proving who operated it, or that its visible attributes reliably identified hostile traffic, remained outside the available evidence.
That distinction matters without Bot Management. Source network, country, URI, headers, user agent, request rate, and response status describe traffic. They do not establish identity or intent. I have learned to distrust a rule that becomes more confident as its expression becomes broader.
The named failure mode was false-positive blocking of the protected production flow. Its operating cost included log review, repeated browser journeys, propagation watches, and rollback readiness. The unresolved proof boundary was the distributed swarm. Its shared attributes did not support broad enforcement scope, so it remained in LOG.
Respect Cloudflare phase and skip semantics
Cloudflare evaluates custom rules in order. A terminating action such as Block stops later rules and phases for that request, as documented in the custom rules overview. Current phase order places custom rules in http_request_firewall_custom, followed by rate limiting in http_ratelimit, Managed Rules in http_request_firewall_managed, and Super Bot Fight Mode in http_request_sbfm. Confirm the deployed behavior against Cloudflare’s security feature interoperability documentation.
A negative condition protecting a path merely prevents one Block rule from matching. If no other terminating action fires, evaluation continues. That is narrower than a preceding Skip rule. Depending on its parameters, Skip can bypass remaining custom rules, later phases, or selected products outside the Ruleset Engine. Bot Fight Mode cannot be skipped, and account-level Skip rules do not skip zone-level rules. Cloudflare documents the available Skip options.
For this incident, exclusions inside the Block expression protected session and critical-action paths while preserving other applicable controls. This verification matrix is the reusable review contract:
| Check | Evidence required before promotion |
|---|---|
| Remaining custom rules | Expected later rule IDs appear unless explicitly skipped |
| Rate limiting | http_ratelimit evaluates, or its omission is recorded |
| Managed Rules | http_request_firewall_managed evaluates, or the exception is explicit |
| Super Bot Fight Mode | http_request_sbfm behavior matches the configured Skip contract |
| Independent products | Browser Integrity Check, Zone Lockdown, and other enabled products are checked separately |
| Production journey | Redirects, cookies, challenges, navigation, and the original 400 path work end to end |
An exclusion is also attack surface. A controlled internal caller may use a secret-valued header, following Cloudflare’s header rule example. A public browser route cannot safely rely on a shared header that users can observe and replay.
Encode only supported evidence
Test route semantics at Cloudflare and the origin, including encoded separators, duplicate slashes, case, normalization, and redirects. Then express only the supported slice:
# Existing high-confidence controls run before this rule.
(
ip.geoip.country eq $suspect_country
and ip.src in $high_confidence_networks
and starts_with(http.request.uri.path, "/api/")
and not starts_with(http.request.uri.path, "/api/session/")
and not starts_with(http.request.uri.path, "/api/critical/")
)
Action while evaluating: Log
Action after promotion: BlockThe concentrated network and distributed swarm should not inherit the same action merely because both looked suspicious. If a signal cannot distinguish the target traffic from protected users, it cannot justify equivalent enforcement scope.
Promote with conservative uncertainty
Fix the sampling method before review. Sample at a cluster level appropriate to the suspected dependence, such as actor, network, or campaign, and cover protected-path proximity, user-agent groups, status classes, and comparable traffic periods. Record the clustering key and review criteria. Classify each sampled unit as legitimate, hostile, or indeterminate.
Treat indeterminate units conservatively. For a promotion gate, count them as potential false positives rather than removing them from the denominator:
conservative_fp_rate = (legitimate + indeterminate) / all_reviewedThis is a decision rule, not an estimate of the true false-positive rate. It prevents unresolved reviews from making weak evidence appear safer. If indeterminates dominate, collect better evidence or keep the rule in LOG.
For independent, representative units with zero observed false positives, a preselected one-sided binomial upper bound is:
upper_bound = 1 - alpha^(1/n)
# alpha = 0.05 for a preselected 95% one-sided boundRequest-level use is invalid when requests cluster by actor, network, or campaign because independence no longer holds. Estimate at the appropriate cluster level, use a model that accounts for dependence, or label the request-level result as an approximation whose confidence can be overstated. The source contains no cluster design or effective sample size, so it does not support a numerical promotion claim.
Compare candidate matches and origin errors with comparable weekday and time-of-day windows. Separate launches, billing cycles, holidays, and incident periods instead of averaging them into normal traffic. No defensible baseline duration was recorded. The next test is to determine how many comparable windows are needed for those distributions to stabilize.
Logs establish matches and distributions. The browser journey tests whether the production flow still works. Neither proves that the ambiguous swarm is automated. Where network evidence is used, the earlier chapter explains how to preserve client IP provenance through proxies.
FAQ
Does a path exclusion skip later WAF phases?
Not when it is only a negative condition in one Block expression. Later phases continue unless another terminating action or configured Skip prevents them.
When is zero false positives enough?
Never by itself. Use representative cluster-level evidence, conservative treatment of indeterminates, a preselected uncertainty method, and the accepted budget.
When can a user-agent rule block?
Only when reviewed evidence supports that narrow action. A user agent is easy to copy and does not prove identity or automation.
