Bassam Ismail
How to Harden Cloudflare WAF Without Bot Management
Engineering

How to Harden Cloudflare WAF Without Bot Management

6 min read

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:

CheckEvidence required before promotion
Remaining custom rulesExpected later rule IDs appear unless explicitly skipped
Rate limitinghttp_ratelimit evaluates, or its omission is recorded
Managed Ruleshttp_request_firewall_managed evaluates, or the exception is explicit
Super Bot Fight Modehttp_request_sbfm behavior matches the configured Skip contract
Independent productsBrowser Integrity Check, Zone Lockdown, and other enabled products are checked separately
Production journeyRedirects, 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: Block

The 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_reviewed

This 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 bound

Request-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.

PROMOTION STOPS AT THE PROOF BOUNDARYLOGsample candidate clustersEvidence supports this slice?include indeterminate reviews conservativelyVerify exclusionscheck later phases and the full journeyNarrow enforcementblock only the supported sliceWithin budget?compare with seasonal baselineMonitored enforcementsuccess terminates hereRollback or LOGdisable, restore, or observe[ Matches are observations. Classification is inference. Causal proof requires a controlled test. ]

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.

More to read