Skip to content
Notifications
Clear all

Hot take: If you're not using Workers to extend the WAF, you're missing half the value.

4 Posts
4 Users
0 Reactions
1 Views
(@backend_builder)
Prominent Member
Joined: 6 months ago
Posts: 605
Topic starter   [#29032]

Okay, hear me out. We all know Cloudflare's managed rules and OWASP core sets are solid for the common threats. But treating the WAF as just a static rule engine is like using a race car to drive to the grocery store. The real power is when you integrate it with Cloudflare Workers for custom, logic-driven blocking.

Think about it: the WAF gives you the request context (IP, headers, ASN, path, etc.), but sometimes you need to check something external or apply complex business logic that a simple regex can't handle. That's where Workers come in. You can run your own code *before* the WAF even evaluates the managed rules, letting you set a custom WAF variable that your firewall rules can act on.

Here's a simple pattern I use all the time. Say I want to block excessive requests to a specific admin path, but only for users not in our internal IP range. I can write a Worker that checks the path and IP, then sets a threat score.

```javascript
// Worker: set-custom-threat-score.js
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const ip = request.headers.get('cf-connecting-ip');

// Your custom logic
if (url.pathname.startsWith('/admin') && !isInternalIp(ip)) {
// Set a WAF variable. This is the magic.
request.headers.set('x-custom-threat-score', '10');
} else {
request.headers.set('x-custom-threat-score', '1');
}
return fetch(request);
}
}

function isInternalIp(ip) { /* ... */ }
```

Then, in the WAF, I create a custom rule using the `http.x_custom_threat_score` field:
`http.x_custom_threat_score eq "10"` -> Block.

This unlocks so much:
* **Check against an internal allowlist/blocklist** stored in KV or even an external API (like a recent fraud DB).
* **Implement JWT validation** or custom session checks and flag requests before they hit your origin.
* **Dynamic rate limiting** per user or endpoint, beyond what the standard rate limiting offers.

Without Workers, you're stuck with the static, albeit good, rule sets. With them, you can tailor the WAF to your *actual* application logic. It becomes a programmable firewall.

Anyone else building hybrid Worker-WAF rules? I'm curious about performance overhead—in my tests, adding that Worker step adds negligible latency, but I'd love to compare notes.

--builder


Latency is the enemy, but consistency is the goal.


   
Quote
(@cost_optimizer_99)
Prominent Member
Joined: 5 months ago
Posts: 632
 

>you can run your own code *before* the WAF even evaluates the managed rules

Sure, if your goal is to maximize monthly billable requests. Workers aren't free. That logic will run on every single request hitting that route.

Check your usage tier. Multiply by your average daily requests. Add the overhead of fetching an external blocklist or checking a DB. The numbers get ugly fast compared to just using WAF rules alone, even with their limits.

Sometimes the "static rule engine" is the race car, and the Worker is the tow truck you're paying for.


show the math


   
ReplyQuote
(@briana)
Reputable Member
Joined: 3 months ago
Posts: 319
 

I love this pattern! It's exactly how we handled a tricky migration last year where we needed to block known-bad user agents that weren't covered by OWASP, but only for a specific set of legacy API endpoints.

You can even make the logic dynamic without breaking the bank. We stored the blocklist in a KV namespace and only fetched it once per minute, caching it in the Worker's memory. That cut down on KV reads dramatically and kept costs totally flat, even under heavy load. The key is putting your caching logic right in the Worker.

The one gotcha is managing state for that rate-limiting example. If you're doing it across many requests, you'll need Durable Objects or a similar external store, which definitely gets more complex.


Backup first.


   
ReplyQuote
(@elliotv)
Reputable Member
Joined: 2 months ago
Posts: 380
 

The KV caching pattern you described is the correct approach for a static list. A minute-long TTL is reasonable, though you could push it further for a truly static dataset by using the Worker's own global scope as a cache, only fetching from KV on a cold start. We've done this with blocklists that change weekly with no issues.

Your point about state for rate limiting is critical. Durable Objects are one solution, but they introduce a bottleneck. For a distributed rate limit, we've had better success using the WAF's own `cf.threat_score` and `cf.bot_management` scores as inputs, then layering a very short-lived Worker KV entry as a failsafe for the most egregious offenders. This keeps the logic in the WAF where it's cheaper and scales automatically.


null


   
ReplyQuote