[ fwscope: A Firewall, WAF and IDS Log Visualizer ]

[ 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 fwscope dashboard on its sample data: summary tiles, events over time split into firewall and WAF charts, a world map of sources with a top-networks list, and an If blocking were on table comparing anomaly thresholds.
The dashboard on its built-in sample data, which uses made-up documentation addresses.
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.
fwscope's Since your last visit card: 526 events newer than last time with a Show them button, 31 new sources, newly targeted ports 5900, 8080, 445, 3389 and 1900, and two new rules, sshd and wan forward.
After a made-up OpenWrt log one visit, then the next day's log plus a fail2ban log: new sources, new ports, and fail2ban's sshd jail as a new rule.

[ 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
fwscope's start screen: a drop zone for firewall, WAF or IDS logs with a Try it on sample data button, Optional extras folded away, and Get your logs help with a tab for each format, showing how to turn on UFW logging, copy the log, or pull it with fwscope-pull.py.
The start screen. Each tab under Get your logs shows how to turn logging on for that format and grab the file, plus the fwscope-pull command that fetches it for you.
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.
The fwscope-pull helper page: tabs for Linux server, OPNsense / pfSense, OpenWrt, This computer, Cloudflare, AWS VPC and Azure, with the Linux server form filled in: SSH login admin@web1, port 2222, a key file, Read the logs with sudo ticked, a sudo password field and 7 days back.
The helper page with the Linux server form filled in. The login, port and key are examples.
After a pull: six files ready, the output listing each file fetched from web1 with its size (UFW, fail2ban, Suricata eve.json and ModSecurity logs, 24.6 MB in all), the folder they went to, and the Open in fwscope button.
After the pull: what it fetched and where it went. Open in fwscope loads the folder straight in.
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.
fwscope's Block suggestions card on the sample data: ticked addresses and /24 ranges with their networks, and reasons like port scan, WAF attack hits, requests that reached the web app, firewall + WAF, Suricata alerts too, banned or not banned by fail2ban, and via Cloudflare.
Block suggestions on the sample data, made-up addresses again. The reasons pull in the firewall, Suricata, fail2ban and Cloudflare.
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.

[ See Also ]