Security
Reading a Cloudflare WAF log: five signatures of intent
A Slack ping at 22:14: 14,000 WAF events in the last hour. Half is noise, a quarter is real, the rest is your own rules misfiring. Here is how to tell them apart.

It is 22:14 on a Tuesday and your phone buzzes. Cloudflare's spike alert says 14,000 WAF events on the marketing site in the last hour, mostly Action: Block. The on-call rotation is you. Before you can do anything useful, you need to know whether this is an actual attack, a scanner having a night out, or your own WAF rules misfiring on a legitimate integration.
The fastest way through that triage is not to read the events. It is to read the shape of the events. After enough nights like this, you start to see the same five silhouettes show up in the log, and each one comes with a default answer: blackhole, throttle, or shrug.
What a Cloudflare WAF event actually contains
Every WAF event Cloudflare records carries the same skeleton. You can pull these from the dashboard under Security > Events, or query them via the GraphQL Analytics API if you want them piped into Grafana, Loki, or a Slack channel.
{
"rayName": "8b3f7e2c1a4d9000",
"action": "block",
"ruleId": "100015",
"source": "waf.managed",
"clientIP": "45.155.205.233",
"clientASN": 14061,
"clientCountry": "US",
"clientRequestHTTPHost": "abn.company",
"clientRequestPath": "/.env",
"clientRequestHTTPMethodName": "GET",
"userAgent": "Mozilla/5.0 (compatible; Nimbostratus-Bot/v1.3.2)",
"botScore": 3,
"edgeResponseStatus": 403,
"datetime": "2026-06-06T20:14:33Z"
}
That is enough to classify almost any event. The fields that matter for the triage call are ruleId, clientASN, clientRequestPath, userAgent, and botScore. The rest is for the post-mortem.
Signature 1: one IP walking the CVE shopping list
The single most common pattern in any WAF log. One clientIP hits a sequence of paths that have nothing to do with your site:
GET /.env
GET /.git/config
GET /wp-content/plugins/revslider/temp/update_extract/
GET /actuator/health
GET /phpinfo.php
GET /server-status
GET /.aws/credentials
The user agent is usually a public scanner string (Nimbostratus, zgrab, Nuclei) or a slightly stale Chrome. The ASN is often a small VPS provider on the spammier end of the spectrum. The bot score sits in single digits.
This is reconnaissance, not exploitation. They are not going to find .env on your edge, but they are also not going to stop. The right answer is to blackhole the IP at the firewall layer for a week or two and move on.
(ip.src eq 45.155.205.233) → Block, expire 14d
Signature 2: cloud ASN, valid browser UA, one endpoint
A clean Chrome user agent. A real Accept-Language. JavaScript would execute if you asked it to. The only thing wrong is that the request is coming from AS24940 (Hetzner), AS16276 (OVH) or AS14061 (DigitalOcean), and it is hitting the same product or pricing endpoint every 400ms.
This is someone scraping you. Could be a competitor, a price-tracker, an LLM training crawler that did not honour robots.txt. Blocking them outright is rude and pointless. They will rotate.
The right answer is to throttle. Rate-limit by ASN on the expensive endpoint, raise the cache TTL on the response above the scrape interval, and let them have the data at a price you can afford to serve.
If the bot has a working browser and a real ASN, it is cheaper to throttle than to block. You only fight bots that cost you money to serve.
Signature 3: distributed POSTs on /login
Hundreds of IPs, each making one or two requests, all POST against /login, /wp-login.php, /admin/auth, or your API's token endpoint. The IPs do not cluster by ASN. They cluster by residential ASN. Comcast, Vodafone, KPN, Deutsche Telekom. The user agents look fine.
This is credential stuffing through a residential proxy network (Bright Data, IPRoyal, the gray-market variants). The signal is not in any single request. It is in the path concentration plus the residential ASN mix plus the absence of any other path being requested.
Blocking by IP does nothing. The proxy pool has a million of them. The right answer is a rate-limiting rule on the auth endpoint that triggers a managed challenge after, say, three failed logins in five minutes, plus a one-line custom rule that issues a JS challenge to any POST on that path with cf.bot_management.score < 30.
Signature 4: your own managed rule misfiring
The event is blocked. The rule ID points to something in the Cloudflare Managed Ruleset, usually a SQLi or XSS pattern from the OWASP Core Rule Set. The path is yours. The user agent is a normal browser or a documented webhook (Stripe, GitHub, Mailgun). The body, if you can see it, contains something that looks dangerous but is not: a JSON field with the word SELECT in it, or a quoted apostrophe in a customer name.
This is the false positive. The frustrating one. The user logs a ticket saying "I cannot submit the form" or your webhooks queue starts backing up.
The right answer is to shrug at the attack, then write a Skip rule exception scoped narrowly to that path and that ruleset. Resist the urge to disable the whole managed group. The CRS catches real things every week. You just need to give your one quirky endpoint a pass.
If you turn off a managed rule globally, set a calendar reminder to revisit it in 30 days. We have walked into more than one site where "temporary" exceptions from 2021 are still doing nothing.
Signature 5: slow recon from clean rotating IPs
This is the one that hides in a noisy log. One request every 30 or 60 seconds. Each from a different IP, often on a residential or mobile ASN. Each hitting the same path: /admin, /backup.sql, /.env.production, /wp-config.php.bak. No user-agent pattern. No burst.
This is somebody who read about WAF rate limits and is staying under them on purpose. They will not find anything tonight. They will be back tomorrow.
Blocking IPs is useless. The right answer is to remove the target. Move the admin path behind a Cloudflare Access policy or a country geofence, or simply return a static 444 for any request to that path that does not come from your office VPN range. If they cannot reach the door, they cannot rattle the handle.
The five-minute audit
Open your Cloudflare dashboard, go to Security > Events, and add five saved filters: one per signature above. Name them cve-walk, cloud-asn-scrape, auth-burst, managed-fp, slow-recon. The next time the spike alert fires at 22:14, you will spend two minutes classifying instead of forty reading.
When we built the security-event triage agent for a Dutch e-commerce client, the thing we ran into was that managed-rule false positives were drowning every real signal in the log. We ended up piping WAF events through a classifier that bucketed them into these five patterns before anything got paged, then routed signatures 1, 3 and 5 to an on-call human and let 2 and 4 settle into a weekly digest. The same shape works as a thirty-line script against the GraphQL Analytics API if you would rather build it yourself than buy a SOC tool, or as a packaged piece of our AI agents work.
The smallest thing you can do today: open one of last week's WAF spikes and try to put each event into one of the five buckets. If something does not fit, you have found signature six.
Key takeaway
Read the shape of the WAF log, not the events. Five patterns decide whether you blackhole, throttle, or shrug. Everything else is noise.
FAQ
How do I get Cloudflare WAF events into Slack?
Query the GraphQL Analytics API on a one-minute cron, classify each event into a bucket, and post only the summary into Slack. The built-in webhook product is too coarse for real triage.
Should I ever disable a Cloudflare managed rule entirely?
Almost never. Scope an exception to the path and ruleset that misfires. If you disable a whole group, set a 30-day calendar reminder or it becomes permanent.
What's the practical difference between blocking an IP and challenging it?
Block sends a 403 and saves you the request. Challenge serves a JS check that real browsers pass. Use challenge for residential ASN traffic, block for cloud VPS scanners.
Is bot score reliable enough to write rules against?
On the Bot Management plan, yes. Below score 30 is almost always automation. Treat it as one signal, not the only signal, and always pair it with a path or method constraint.