[ fwscope: A Firewall, WAF and IDS Log Visualizer ]
[ Overview |
What's new |
What it shows |
What it reads |
Pulling the logs with fwscope-pull |
What it found in my own logs |
Behind Cloudflare: who's really knocking |
Fake crawlers |
fail2ban, Suricata and blocklists |
From detection-only to blocking |
Blocking the attackers themselves |
What stays on your machine |
FAQ ]
[ Overview ]
fwscope is a firewall and WAF log viewer I built. It's one HTML file: open it in a browser, drop your logs on it, and you get a dashboard. No install, no server, no account. Everything is parsed in the browser tab and nothing gets uploaded, so it works on an air-gapped box too. It reads UFW, nftables, iptables and firewalld, OPNsense and pfSense, OpenWrt, Windows Firewall, AWS and Azure flow logs, fail2ban, Suricata, ModSecurity or Coraza, and Cloudflare's firewall events. A companion script, fwscope-pull.py, gathers those logs from your servers, routers and cloud accounts. It's free software, GPL-3.0, and the current release is 1.4.0. Open it runs in your browser, on your own logs Try the demo a week of made-up sample data Download index.html from the latest release, to run offline GitHub source, README and changelog Below: what it shows, what it turned up in my own logs, how I used it to work out whether my WAF was ready to start blocking, and how it turns the attackers it finds into firewall rules.
[ What's new ]
fwscope has grown since I first wrote this up. New in 1.4.0:
- fwscope-pull.py. A script that gathers logs from this machine,
Linux servers, OPNsense, pfSense and OpenWrt over SSH, Cloudflare, AWS
and Azure, plus a helper page that opens them in fwscope for you. More
on it below.
- Host names from file names. A file like web1-modsec_audit.log shows
up as host web1, and fail2ban logs take their host the same way.
1.3.0 added:
- A shorter start screen. A few-line drop zone, setup help for one
log format at a time, and the optional extras folded away.
- Routers and the cloud. OpenWrt's firewall log (fw4 and fw3), AWS
VPC Flow Logs, and Azure NSG and VNet flow logs.
- Reopen your last load. The parsed events stay in the browser, so a
day of OPNsense logs comes back in under half a second instead of being
read all over again.
- Since your last visit. A card with the new sources, ports, attack
types, rules and countries compared with every earlier visit.
- fail2ban repeats. An address that comes back while it's banned
shows as F2B REPEAT, the sign of a ban that isn't keeping it out.
Before that, 1.0 through 1.2 added:
- More logs. nftables, iptables and firewalld lines with any log
prefix, fail2ban's own log, Suricata alerts, and Cloudflare's firewall
events from its API or Logpush.
- Block suggestions. The attackers that got past the WAF, with a
script to block them on your firewall, OPNsense or Cloudflare.
- Caught or missed. With fail2ban's log or Suricata's alerts loaded,
you see which attackers they flagged and which they let slide.
- Blocklists. Drop Spamhaus DROP, a FireHOL set or a list of your own
to mark the sources on it.
- Adding files. Add logs to what's already loaded, drop a whole
folder, and take a file back out from the Loaded files card.
- Compare. The last hour, day or week against the one before, which
is how you see whether a block worked.
- Background parsing. Big loads run in a worker now, so the page
stays responsive and Stop works right away.
Each release is in the changelog.
[ What it shows ]
Summary events by action, unique sources, ports, the busiest
source and the time span
Timeline events over time, stacked by action; drag to zoom.
With firewall and WAF logs loaded together it splits
into two charts on one time axis
Top lists sources, subnets, ports, protocols, interfaces,
firewall rules, networks and countries
New sources, networks, countries and ports first seen in
the last hour, day or week
Compare the last hour, day or week against the one before,
biggest changes first
Since last visit new sources, ports, attack types, rules and
countries since your earlier visits
Scans fast sweeps, slow scans, host sweeps and hammering,
each labeled; on WAF logs, web scans and crawlers too
World map sources by country and network owner, from a free
GeoIP database you add once
Event table click any cell to filter on it, shift-click to
exclude it
Block suggestions attackers that got past the WAF, and a script to
block them
Loaded files each file's events and duplicates, with Remove
Exports CSV, a source list, a standalone HTML report, and a
drafted abuse report
The filter box takes terms like dpt:22, cc:CN, org:digitalocean, attack:sqli,
seen:both or listed:any, and everything you set lives in the page address, so
a bookmark brings back the same view on next week's logs.
The Since your last visit card is for coming back to the same logs every few days. Each load of real logs leaves a small fingerprint in the browser, and the next load is compared with every earlier one: events newer than what you last saw, with a button to show just those, then new sources, newly targeted ports, new attack types, new rules and new countries, busiest first. Click one to filter on it.
[ What it reads ]
UFW / iptables ufw.log, kern.log or syslog (gzipped too),
journalctl and dmesg
nftables / firewalld any kernel log line with SRC=, DST= and PROTO=,
whatever its log prefix
OPNsense / pfSense filterlog files, remote syslog, or the Live
View API's JSON
OpenWrt logread or a syslog server; fw4 and fw3 prefixes
Windows Firewall pfirewall.log once logging is on, plus the
Security log for program names
AWS VPC Flow Logs from S3 or CloudWatch, default or custom format
Azure flow logs NSG and VNet flow logs, the hourly PT1H.json
fail2ban fail2ban.log: finds, bans, repeats and unbans
Suricata eve.json alerts (forwarded through syslog too)
or fast.log
ModSecurity / Coraza JSON or native audit logs, or the error-log
lines
Cloudflare firewall events from the GraphQL API or Logpush
Blocklists Spamhaus DROP, FireHOL sets or your own list of
addresses and ranges
Drop several files at once and they're merged, or drop a whole folder. Once something is loaded the button reads Add logs, and whatever you open next lands on the same dashboard, with a note on how many new events it brought and how many duplicates it skipped. That matters more than it sounds: UFW's default setup writes every line to ufw.log, kern.log and syslog. The Loaded files card lists what's in, and Remove takes a file back out as if it had never been there. Getting them off the machine is usually one line: sudo cp /var/log/ufw.log* ~/ && sudo chown $USER ~/ufw.log* journalctl -k -g 'SRC=' -o short-iso --since '-7d' > netfilter.log scp 'root@opnsense:/var/log/filter/filter_*.log' . scp 'root@opnsense:/var/log/suricata/eve.json*' . ssh root@openwrt 'logread' > router1.log scp 'you@web:/var/log/modsec_audit.log*' . aws s3 sync s3://BUCKET/AWSLogs/ACCOUNT/vpcflowlogs/REGION/ ./vpc/ curl -sO https://www.spamhaus.org/drop/drop.txt Or skip the copying: fwscope-pull.py, in the next section, does it for you. On Windows, logging is off by default. In an admin PowerShell: Set-NetFirewallProfile -All -LogBlocked True -LogMaxSizeKilobytes 32767 OpenWrt only logs a zone once you turn on "Enable logging on this zone" under the zone's advanced settings. logread keeps a small buffer and no hostname, so name the file after the router, or send the log to a syslog server for a longer history. The cloud flow logs have no log prefix at all, just ACCEPT or REJECT per flow. On AWS, fwscope uses pkt-srcaddr when your format has it, so the real client behind a NAT gateway or load balancer shows up as the source. Azure writes several records for one flow, and each flow is counted once. Big logs are fine. One real OPNsense box here logs about 850,000 events a day, and a full day loads in two or three seconds. Parsing runs in a background worker, so the page stays responsive during a big load and Stop works right away. The parsed events of your last load are kept in the browser too, so the next time you open the page, that day of logs reopens in under half a second. The README covers every format and option.
[ Pulling the logs with fwscope-pull ]
Copying logs off every box by hand gets old, so fwscope now comes with fwscope-pull.py, a script that gathers them onto the computer you run fwscope on. It's one Python 3.10+ file with no dependencies, and it runs on Windows, macOS and Linux. Remote machines are reached with the ssh client your system already has (Windows 10 and later ship one), so nothing gets installed on your servers or routers, and they don't need Python. local this machine: UFW / netfilter, fail2ban, Suricata and ModSecurity on Linux; on Windows, pfirewall.log, and with --programs the Security-log events with program names ssh the same from a Linux server; --sudo asks for the password once and hands it to sudo, never to the command line opnsense OPNsense or pfSense filter logs, and --suricata for eve.json openwrt logread, saved under the router's hostname cloudflare firewall events, paged past the API's limit of 10,000 per request; the token comes from CF_API_TOKEN aws VPC Flow Logs from S3, through the aws tool azure VNet or NSG flow logs, through the az tool A pull looks like this: python3 fwscope-pull.py ssh you@web1 --sudo python3 fwscope-pull.py opnsense [email protected] --days 2 --suricata python3 fwscope-pull.py cloudflare --zone ZONE_ID --since 2026-11-01 Every command takes --days and -o for the output folder, and finishes with what it collected and anything it couldn't read. Files are named after the host, like web1-modsec_audit.log, and fwscope reads the host from that name, so the dashboard shows web1 rather than the whole file name. When it's done, drop the folder on fwscope. Each format's tab on the start screen shows its fwscope-pull command too. The helper page. If you'd rather fill in a form than remember flags, run python3 fwscope-pull.py gui. It opens a page in your browser with a tab for each source. Fill in the login (or the zone, bucket or storage account), press Pull logs and watch the output, then press Open in fwscope: fwscope opens with the logs already loaded, no folder to drag.
A page on your own computer that runs commands is something I'd want to see the guts of before trusting, so here they are. It listens on 127.0.0.1 only. The address it opens carries a random key, which becomes a strict same-site cookie, and every request needs it. Requests naming any other host are refused, so no website you visit can drive it. Each pull runs the same script with a fixed argument list and no shell, so the form can't do anything the command line can't. Passwords and tokens go to that one run through its environment and aren't saved. The other fields are remembered in the browser for next time. It can't answer prompts, so SSH needs key login, and each host has to be known already: connect once with ssh in a terminal first. A key with a passphrase has to be loaded into ssh-agent. To read root-only logs on this computer, start the helper with sudo, or from an administrator prompt on Windows. Open in fwscope needs fwscope's index.html next to the scripts folder, as in a clone of the repo. Without it, drop the folder on fwscope as usual. The script only reads. It never changes a firewall, server or cloud setting. Download it and read it before you run it, rather than piping it from the internet into a shell.
[ What it found in my own logs ]
I pointed it at two weeks of my web server's WAF logs (ModSecurity with the
OWASP Core Rule Set, running detection-only at the time) together with my
firewall logs. Between October 24 and November 7, 43,640 requests got
flagged. Some of what came out:
- One address, 7,925 requests for credentials. A single source on an
RDP-hosting network went after files like /.aws/credentials,
/.azure/credentials and gcloud keys. It had hit my firewall too.
- 324 addresses showed up in both logs. The scanner that sweeps your
ports usually comes back for the web server, and seen:both pulls
exactly those out.
- Fast scanners. One probed 754 paths in 3 minutes; another did 535
paths in 5 seconds.
- The rules doing the work: a bare IP address as the Host header (35.9K
hits), restricted file access (16K) and restricted file extensions
(3.6K).
- Different rhythms. Firewall blocks ran a steady ~600 every three
hours, while WAF hits came in bursts and dropped off after November 4.
That bare-IP Host count says something on its own. My site sits behind
Cloudflare, which routes by hostname, so a request arriving with a bare IP as
the Host never went through Cloudflare at all. It went straight at the
server. If your origin only needs to talk to Cloudflare, its firewall can
drop everything else.
[ Behind Cloudflare: who's really knocking ]
Cloudflare hid the attackers at first. A WAF behind Cloudflare sees Cloudflare's edge servers as the client, so about 6,600 of my requests looked like they came from Cloudflare itself, and some "web scans" got pinned on Cloudflare's addresses. The real visitor's address rides along in the CF-Connecting-IP header, and fwscope uses it, with one catch: it only trusts that header when the request really came from one of Cloudflare's published address ranges. Anyone can send a CF-Connecting-IP header, and trusting it from anywhere would let a scanner pick its own address. Behind your own reverse proxy (a private source address), it uses the first X-Forwarded-For address instead. The footer says how many requests it unwrapped. Unwrapped, some of those "Cloudflare" scans turned out to be machines on Google Cloud. This needs the audit logs. Error-log lines carry no headers, so there's nothing to unwrap.
[ Fake crawlers ]
Real search engines crawl everything, my exploit archive included, and at
first Huawei's PetalBot crawling /exploits/ got labeled a web scan. Now
sources with a known crawler's user agent are labeled crawler and listed
last, since they're usually harmless.
Usually. A user agent is just a string anyone can send. With an ASN database
loaded, fwscope checks each big-crawler claim against the networks that
crawler really uses: Googlebot on Google's, bingbot on Microsoft's, GPTBot on
Microsoft's, CCBot on Amazon's, PetalBot on Huawei's and so on. A claim from
anywhere else, or a source that switches between three or more bot names, is
a fake crawler: a scanner in disguise. My logs had a few:
- A "Googlebot" on a Bulgarian hosting network. It probed 319 paths,
/.git/index among them, in 15 seconds.
- A bot of many names. One source rotated between GPTBot, Bytespider
and others.
- Google and Common Crawl at once. Another claimed to be both, from the
same network as my firewall's busiest attacker.
The real PetalBot passed, because its addresses really are on Huawei's
network. Turn on Hostnames and fwscope also forward-confirms reverse DNS, so
a made-up googlebot.com name shows up as unverified.
[ fail2ban, Suricata and blocklists ]
The firewall + WAF check (seen:both) was the first cross-check. There are three more now, one for each kind of tool that already has an opinion about a source. fail2ban. Load fail2ban's log next to the firewall or WAF logs, and the Scans card marks every scanner and hammerer as banned or not banned. The banned ones are fail2ban doing its job. The not-banned ones are the interesting part: a hammerer it never banned usually means no jail covers that service. The jail shows as the rule, so rule:sshd works, and seen:f2b filters to the sources it banned (-seen:f2b to the ones it didn't). Then there are the repeats. When a banned address turns up again, fail2ban logs "already banned", and fwscope shows that as F2B REPEAT. Lots of them on one jail mean the ban isn't keeping anybody out. Usually a rule that allows the port comes before the ban, or the visitors arrive through a proxy or CDN, so the address fail2ban bans isn't the one connecting. Suricata. Alerts from eve.json or fast.log load as events of their own, with the signature as the rule. IDS ALERT means it only alerted; IDS DROP means IPS mode dropped the packet. seen:ids filters to the sources Suricata alerted on, and the Sources list says "IDS too" when an address also shows up in the firewall or WAF logs. Blocklists. Drop Spamhaus DROP, a FireHOL .netset or a list of your own, one address or range per line. fwscope recognizes it as a list rather than a log and marks the sources on it, and listed:any (or listed:drop, by name) filters to them. It holds up to eight lists, kept in the browser and checked locally. Nothing gets looked up online. All three show up in the block suggestions' reasons. Suricata alerts and blocklist hits count as supporting evidence there, but neither one gets a source suggested on its own.
[ From detection-only to blocking ]
My WAF started in detection-only mode (SecRuleEngine DetectionOnly). Every
rule runs and logs, nothing gets blocked, and in fwscope every request shows
as WAF DETECT. It's the safe way to start, and it leaves you with one
question: if I turn blocking on, who gets caught?
The If blocking were on card answers that from your own traffic. CRS 4
logs every request's inbound and outbound anomaly score in rule 980170, so
fwscope can replay each threshold with real scores instead of guesses. On my
two weeks:
Threshold Would block Possibly legitimate
5 (CRS default) 20,427 (47%), 1,395 sources 3,536 from 886 sources
7 15,033 (34%) 1,535
10 5,759 (13%) 951
15 1,332 (3%) 671
The percentages are of flagged requests, not of all traffic. "Possibly
legitimate" is deliberately cautious. Most of mine were requests that only
broke HTTP protocol rules, like a bare IP as the Host header with headers
missing, which real visitors coming through Cloudflare never do. Most of the
rest were real crawlers, mainly PetalBot.
Some blocks come from outbound rules, which judge the response rather than
the request. 950140 (CGI source code leakage) fires on /pubCarHacking/, where
I serve script files on purpose. CRS's outbound threshold is 4, so a single
match there blocks the response, whatever the inbound threshold.
The Tuning suggestions card is a false-positive finder. It looks for rules
that fire mostly on verified crawlers or your own addresses on one path, and
works out what excluding each would change at the threshold you picked. At
5, two exclusions on my site would change what gets blocked:
Rule Where Unblocks Verdict
921190 HTTP splitting /exploits/ 42 legit, 0 other clean
950140 CGI source leakage /pubCarHacking/ 597 legit, 250 other trade-off
The first is a clean win. The second is a real false positive, but excluding
it for the whole path would also let 250 likely-hostile requests through, so
I'd narrow it to the file types I serve on purpose. The other candidates were
920350 (bare IP as Host) on various paths. It only scores 3, so it never
blocks anything on its own, and excluding it would just quiet the log.
Draft exclusions writes the candidates as ctl:ruleRemoveById rules scoped by
path, with each one's numbers in a comment, for CRS's "before" exclusions
file (REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf). Roughly this shape:
# 921190 on /exploits/: unblocks 42 possibly legitimate, 0 other
SecRule REQUEST_FILENAME "@beginsWith /exploits/" \
"id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=921190"
They're drafts. Nothing touches your server, and you should read what each
one lets through before you use it.
The switch, step by step. This follows the method CRS's own docs describe: turn blocking on early with a very high threshold, then lower it as you tune. 1. Load two weeks or more of audit logs (JSON is easiest), plus your firewall log and a free ASN database, so the crawler checks and seen:both work. 2. Read the what-if card at your configured threshold and a few above it. Click a row to see who'd be caught and which rules would do it. 3. Take the clean tuning suggestions, weigh the trade-offs, and put the exclusions in the "before CRS" file. 4. Turn blocking on (SecRuleEngine On) with a high threshold. It's tx.inbound_anomaly_score_threshold, set by rule 900110 in crs-setup.conf. The what-if table shows where "high" stops catching legitimate traffic. 5. Every week or so, reload the logs, tune the highest-scoring false positives first, and lower the threshold a step. 6. End at CRS's defaults: 5 inbound, 4 outbound. CRS warns that staying above them lets a substantial number of attacks bypass it, so a high number is a stage, not a setting. Christian Folini's tutorial on handling false positives is the long version of steps 3 to 5, and it's where this method comes from.
[ Blocking the attackers themselves ]
The what-if card is about the WAF's own blocking. The Block suggestions card is about the attackers: which sources are worth blocking outright, and a script to do it. It starts from the WAF logs. A source is suggested when the WAF caught it attacking (a web scan, web hammering, attack-rule hits or a fake crawler) and its requests still reached the web app, which in detection-only mode is all of them. Traffic the firewall already drops isn't a reason to block anything, so firewall logs only add supporting evidence, like a port scan or "firewall + WAF". When three or more strong attackers share a /24 (a /64 on IPv6), the whole range is offered as well, unticked. Some things are never suggested: your own and LAN addresses, the servers your WAF and allow rules protect, verified crawlers, anything on the Never block list, and Cloudflare's ranges. Blocking those would take your own proxied site down.
Tick what to block, pick a format, and write the script: Format What the script does UFW ufw insert 1 deny, above your allow rules iptables + ipset two ipsets with a 7-day timeout, one DROP rule each nftables its own inet fwscope table; entries expire in 7 days OPNsense alias a list to paste into an alias a WAN block rule uses OPNsense API adds the entries to that alias with curl Windows Firewall one inbound Block rule, via New-NetFirewallRule ModSecurity one @ipMatch deny rule Cloudflare IP access rules at the edge, through its API Every script starts with the date and the number of entries, and carries its undo script at the bottom, commented out. Nothing is applied from the page. Behind Cloudflare, a firewall rule on the server can't stop anybody, since proxied requests arrive from Cloudflare's addresses. Sources that came through Cloudflare are marked "via Cloudflare", the firewall scripts warn when they include any, and the Cloudflare format blocks them at the edge with IP access rules instead. The ModSecurity rule works too, because it checks CF-Connecting-IP. The format defaults to Cloudflare when most of the WAF's requests came through it. Draft fail2ban goes the other way: rather than a list of today's attackers, jails that ban the next ones on their own. It only drafts what the logs call for (ModSecurity's anomaly-score rule, WordPress logins, SSH if connections to port 22 got through, and recidive for repeat offenders) and replays your logs through each jail to say what it would have banned. Behind Cloudflare the jails use fail2ban's cloudflare-token action, because a local ban wouldn't stop proxied requests. Read the list before you run anything. Blocking a shared address, like a VPN exit, a carrier NAT or a cloud range, can shut real people out.
[ What stays on your machine ]
Your logs never leave the browser. There's no server behind the page and no CDN calls. GeoIP databases you add (DB-IP Lite, IPinfo Lite or GeoLite2) are kept in the browser and looked up locally, and so are any OPNsense rule names and blocklists you load. The rest of a config backup, password hashes and keys included, is never stored. The one exception is Hostnames (reverse DNS), off by default. A browser can't send DNS queries itself, so turning it on sends the public source addresses on screen to the DNS-over-HTTPS service you pick, Cloudflare or Google, after asking you first. Destinations, private and LAN addresses are never sent. Two things stay in the browser between visits, on this computer only. Remember last load keeps the parsed events of your last load (the events, not the files) so they can be reopened. On a computer you share, untick it under the header or use Forget it on the start screen. Since your last visit keeps a fingerprint of what you've seen: hashed source addresses rather than the addresses themselves, plus ports, attack types, rules, countries and the newest event time. Forget history on the card clears it. The sample data doesn't count as a visit. The OPNsense API and Cloudflare block scripts need keys, but the page never asks for them. The scripts read them from environment variables when you run them (OPN_KEY and OPN_SECRET, or CF_API_TOKEN and CF_ZONE_ID). Saved reports have no individual log lines, so you can share one without sharing your logs. They do include addresses, ports and any rule names you loaded, so read one before you send it.
[ FAQ ]
Does fwscope upload my logs? No. It's one HTML file, and everything is parsed in the browser tab. There's no server and no CDN, and GeoIP lookups happen locally. The only feature that sends anything out is the optional Hostnames (reverse DNS) setting, which is off by default and asks before it sends public source addresses to the DNS-over-HTTPS service you pick. Which logs can fwscope read? UFW, nftables, iptables and firewalld (ufw.log, kern.log, syslog, journalctl, dmesg, gzipped or not), OPNsense and pfSense filterlog, OpenWrt (fw4 and fw3), Windows Firewall's pfirewall.log and Security log, AWS VPC Flow Logs, Azure NSG and VNet flow logs, fail2ban's log, Suricata's eve.json or fast.log, ModSecurity or Coraza audit logs (JSON or native) and error-log lines, and Cloudflare firewall events. It also reads blocklists like Spamhaus DROP. How do I get logs off my servers and routers for fwscope? Copy them over with scp, or use fwscope-pull.py from the fwscope repo. It pulls them over SSH from Linux servers, OPNsense, pfSense and OpenWrt, and from Cloudflare, AWS and Azure through their API or command-line tools, into one folder you drop on fwscope. Its gui command opens a helper page on your own computer that does the same from a form and opens the results in fwscope. It only reads; it never changes a setting. Can fwscope read AWS VPC Flow Logs or Azure flow logs? Yes. AWS VPC Flow Logs as delivered to S3 or exported from CloudWatch Logs, in the default format or a custom one, and Azure NSG and VNet flow logs from the hourly PT1H.json blobs. Download them and drop them on the page; they're parsed in the browser like everything else. Can fwscope show which attackers fail2ban missed? Yes. Load fail2ban's log along with your firewall or WAF logs, and the Scans card marks each scanner and hammerer as banned or not banned by fail2ban. A hammerer it never banned usually means no jail covers that service. The seen:f2b filter shows the sources it banned, and -seen:f2b the rest. Addresses that came back while banned show as F2B REPEAT, which means the ban isn't keeping them out. Does fwscope block anything itself? No. Block suggestions write a script for UFW, iptables with ipset, nftables, OPNsense, Windows Firewall, ModSecurity or Cloudflare, with an undo script, and it can draft fail2ban jails too. You read them and run them yourself. Nothing is applied from the page. How do I see real visitor addresses in ModSecurity logs behind Cloudflare? Use the CF-Connecting-IP header, but only for requests that really came from Cloudflare's published address ranges, since anyone can send that header. fwscope does this automatically with audit logs, and uses the first X-Forwarded-For address behind your own reverse proxy. How can I tell a fake Googlebot from the real one? Check where it comes from. Real Googlebot crawls from Google's network, and its reverse DNS name resolves back to the same address. fwscope checks crawler claims against the right networks when an ASN database is loaded, and flags sources that rotate between bot names. Should I switch ModSecurity from detection-only to blocking? Eventually, yes; detection-only stops nothing. CRS recommends turning blocking on with a very high anomaly threshold and lowering it step by step as you tune false positives, ending at the defaults of 5 inbound and 4 outbound. fwscope's what-if card shows what each threshold would block on your own traffic before you change anything. Is fwscope free? Yes. It's free software under the GPL-3.0, and the source is on GitHub.
