GuidesComplex Static Analysis

Complex Static Analysis

Full-coverage static-mode example: multiple tries, every filter, and jail wiring in one TOML

This guide walks through the complex static-analysis example (config_examples/complex-static.toml), a runnable configuration that exercises the entire static-mode filter surface in a single run - one command unpacks a self-contained, ready-to-run copy into a directory of your choice with flokbn generate static-demo (see below): global IP and User-Agent white-/blacklists, per-trie User-Agent/endpoint/time filters, CIDR-range analysis, three cluster parameter regimes per trie, and mixed jail wiring. It builds four differently-filtered tries from one parse pass over a 1,000,000-line log and is the best starting point for writing your own multi-trie configuration.

If you have not used static mode before, start with Static Analysis.

Generating the Demo

flokbn generate static-demo writes a complete, runnable example into a directory of your choice: a fixed 1,000,000-line synthetic access log (access.log), the matching configuration TOML whose cluster thresholds are calibrated for that log, and the four IP/UA list files it references. Every path in the generated config is rewritten to an absolute, co-located target, so the config runs from any working directory:

flokbn generate static-demo --out ./demo

--out is a directory and is optional - it defaults to the current directory (.). The synthetic log is deterministic, so the figures below are reproducible. There is no line-count flag: the demo always generates exactly 1,000,000 lines, and the config’s clusterArgSets minimum sizes are calibrated for that scale (hotspot /16s carrying ~7.5k-17.8k requests each).

The generated traffic has a known shape: ten weighted /16 hotspots over a uniform public-IP background (~13.9% of traffic, heaviest ~2%), Googlebot and bingbot User-Agents reused across many requests (~10.4%, exact matches for two entries in the UA whitelist - deliberate, see below), endpoints with Zipf-distributed popularity, and timestamps ascending over 72 hours from 2026-02-03T23:56:44Z. The full 1,000,000-line log is ~185 MB.

Using your own log instead: point logFile at your access log and adapt logFormat to its layout (see Log Formats). You will also need to adapt the per-trie useragentRegex/endpointRegex patterns, the startTime/endTime window, and - most importantly - the clusterArgSets minimum sizes to your traffic volume.

Running It

flokbn static --config ./demo/complex-static.toml --plain

Add --tui instead of --plain for the interactive terminal UI. Because flokbn generate static-demo rewrote every path to an absolute, co-located target, the generated config runs from any working directory - there is no need to be in a particular directory. Running the analysis writes heatmap.html, flokbn_jail.json, and flokbn_ban.txt into the same demo directory. The jail file persists across runs - delete it (together with flokbn_ban.txt and heatmap.html) for a clean rerun.

Global Lists

The [global] section wires all four list files:

  • whitelist / blacklist - CIDRs never/always to ban. These act only on the jail/ban pipeline - they never remove requests from the analysis, so detection, statistics, and percentages are unaffected by them. The examples hold private ranges plus public DNS, and TEST-NET-2 respectively.
  • userAgentWhitelist / userAgentBlacklist - exact User-Agent string matches. Not substrings, not regexes: the full User-Agent header must equal a line in the file.

A whitelisted User-Agent removes the request from every trie and shields its IP from jailing. A blacklisted User-Agent jails its IP immediately as a /32.

The global UA whitelist is reported under Active Filters on every trie. With the demo’s list files this shows as UA whitelist (58 patterns), in addition to whatever per-trie filters that trie carries. The IP lists are deliberately not listed there: they act only on the jail/ban pipeline and never drop requests from a trie.

The example deliberately demonstrates whitelist precedence: ua_whitelist.txt contains the exact Googlebot and bingbot strings present in the fake data (~10.4% of all requests). That is why the unfiltered baseline trie reports ~896k requests instead of 1,000,000 - the whitelisted requests are gone before any trie sees them, and no per-trie filter can bring them back.

The config does not blacklist the high-volume bot User-Agents (curl, python-requests). A UA-blacklist hit jails each matching IP as an individual /32 - with hundreds of thousands of distinct client IPs using those agents, the jail would be flooded with /32 entries. Let clustering find the dense ranges instead; reserve the UA blacklist for rare, unambiguous attack tools.

The Four Tries

Each [static.NAME] table builds one independent trie from the same parsed log. All four use three clusterArgSets ([minSize, minDepth, maxDepth, threshold], see Clustering) spanning three regimes: a coarse sweep (large minSize, shallow depths) that only the heaviest ranges survive, a standard sweep sized to catch the dominant hotspot ranges, and a deep sweep (smaller minSize, minDepth 18) that slices hotspots into finer subranges.

t1_baseline - no per-trie filters

All traffic minus UA-whitelisted requests: ~896k requests. Hotspot /16s carry ~7.5k-17.8k requests while a background /16 carries only ~12, so the size thresholds separate them cleanly: minSize 10000 yields only the heaviest hotspot cores, 5000 the dominant hotspot ranges, and 1500 at depth 18-26 finer slices of each hotspot. cidrRanges additionally reports raw request counts for one broad /8 and two narrow /16s. Only the conservative coarse set feeds the jail (useForJail = [true, false, false]) - the finer sets are exploratory.

t2_bots - useragentRegex

"Googlebot|bingbot|python-requests|curl|Anubis" names all five bot User-Agents, but the Googlebot/bingbot requests are already gone - the exact-match UA whitelist wins over any per-trie filter. What remains is curl + python-requests + Anubis: ~196k requests, hotspot /16s carrying ~1.7k-3.9k. The minSizes scale down accordingly (2000/1000/350). The two coarser sets feed the jail.

t3_hot_endpoints - endpointRegex

"^/fake-endpoint-[1-9]$" selects the nine hottest endpoints. Endpoint popularity is Zipf-distributed, so the top nine carry ~60% of traffic and ~545k requests survive, with hotspot /16s carrying ~4.5k-10.6k (minSizes 6000/3000/1000). Only the middle set feeds the jail.

t4_targeted_window - everything combined

UA regex + endpoint regex + a 24-hour startTime/endTime window (RFC3339; the fake log spans 2026-02-03T23:56Z to 2026-02-06T23:58Z). Each filter multiplies: bots Γ— hot endpoints Γ— one third of the time range leaves ~39k requests, hotspot /16s carrying just ~310-760 - hence minSizes of 300/150/60. Even after triple filtering, the coarse sweep still recovers the hotspot /16s intact. Only the deep sweep feeds the jail (useForJail = [false, false, true]).

Jail contributions compose across tries: every cluster set marked true in any trie adds its detected ranges to the same jail, which is then filtered through the IP whitelist and published to the ban file together with the IP blacklist entries.

Why TOML Instead of CLI Flags

  • One parse, N tries. The log is read and parsed once; all four tries are built from the same pass. Reproducing this with CLI flags takes four separate runs, i.e. four full parses.
  • Per-trie filters. A CLI run applies one global filter set; named [static.NAME] tables each carry their own useragentRegex, endpointRegex, and time window. Combining differently-filtered views of the same log is not expressible as flags.
  • User-Agent lists. userAgentWhitelist/userAgentBlacklist are config-only - basic CLI usage has no equivalent flags.
  • Selective jailing. useForJail chooses per cluster set what feeds the jail; CLI cluster arg sets all count toward jailing.
  • Reviewable and reproducible. The complete analysis - filters, thresholds, list files, jail wiring - lives in one version-controlled file instead of a shell history entry.

Expected Output

Abbreviated --plain output (full run against the generated demo data). Because the synthetic generator is deterministic, the per-trie totals and hotspot /16 counts are reproducible, though the exact shape of the detected ranges varies with the cluster regime: the generator spreads hotspot traffic uniformly within each /16, so the coarse/standard sweeps tend to report whole /16s while the deep sweep slices them into /17//18//19 subranges.

πŸ“Š ANALYSIS OVERVIEW
────────────────────────────────────────────────────────────
Log File:        /path/to/demo/access.log
Analysis Type:   static

⚑ PARSING PERFORMANCE
────────────────────────────────────────────────────────────
Total Requests:  1,000,000
Parse Time:      360 ms
Parse Rate:      2,773,168 requests/sec

🎯 TRIE: t1_baseline
────────────────────────────────────────────────────────────
Requests After Filtering: 895,978
Excluded (UA whitelist): 104,022
Unique IPs:              884,202
Trie Build Time:         1364 ms
Active Filters:          UA whitelist (58 patterns)

πŸ“ CIDR RANGE ANALYSIS
  23.0.0.0/8                21,316 requests  (  2.38%)
  23.253.0.0/16             17,838 requests  (  1.99%)
  87.26.0.0/16              15,018 requests  (  1.68%)

πŸ” CLUSTERING RESULTS (3 sets)
  Set 1: min_size=10000, depth=12-18, threshold=0.20
  Execution Time: 37 ΞΌs
  Detected Threat Ranges:
    23.253.0.0/16             17,838 requests  (  1.99%)
    35.217.0.0/16             11,220 requests  (  1.25%)
    50.231.0.0/16             11,938 requests  (  1.33%)
    87.26.0.0/16              15,018 requests  (  1.68%)
    143.173.0.0/16            15,955 requests  (  1.78%)
    166.94.0.0/16             12,563 requests  (  1.40%)
    183.77.0.0/16             15,046 requests  (  1.68%)
    ───────────────────       99,578 requests  ( 11.11%) [TOTAL]
  ...

🎯 TRIE: t2_bots
────────────────────────────────────────────────────────────
Requests After Filtering: 195,509
Active Filters:          User-Agent: Googlebot|bingbot|python-requests|curl|Anubis,
                         UA whitelist (58 patterns)
  ...

🎯 TRIE: t3_hot_endpoints
────────────────────────────────────────────────────────────
Requests After Filtering: 545,070
Active Filters:          Endpoint: ^/fake-endpoint-[1-9]$,
                         UA whitelist (58 patterns)
  ...

🎯 TRIE: t4_targeted_window
────────────────────────────────────────────────────────────
Requests After Filtering: 39,399
Active Filters:          User-Agent: python-requests|curl|Anubis,
                         Endpoint: ^/fake-endpoint-[1-9]$,
                         Time: 2026-02-04 12:00 β†’ 2026-02-05 12:00,
                         UA whitelist (58 patterns)

πŸ” CLUSTERING RESULTS (3 sets)
  Set 1: min_size=300, depth=12-16, threshold=0.20
  Execution Time: 49 ΞΌs
  Detected Threat Ranges:
    23.253.0.0/16                757 requests  (  1.92%)
    35.217.0.0/16                519 requests  (  1.32%)
    ...
    ───────────────────        5,516 requests  ( 14.00%) [TOTAL]
  ...

Every cluster set detects the genuine hotspot /16s; the four tries report ~896k / ~196k / ~545k / ~39k requests after filtering. After the run, flokbn_ban.txt contains the jailed ranges plus the IP blacklist entries, flokbn_jail.json holds the persistent jail state, and heatmap.html shows the first-octet/second-octet request distribution.

Performance

Measured on the generated 1,000,000-line / ~185 MB demo log (single run, plain output, jail enabled):

StageTime
Parse (1,000,000 requests)360 ms (2,773,168 requests/sec)
t1_baseline build (884k unique IPs)1364 ms
t2_bots build (195k unique IPs)589 ms
t3_hot_endpoints build (541k unique IPs)1093 ms
t4_targeted_window build (39k unique IPs)78 ms
Each clustering set28-153 ΞΌs
Total (four-trie analysis)~2.5 s

Four differently-filtered tries plus twelve clustering passes add little on top of the parse: clustering itself runs in microseconds per set - the dominant costs are parsing and trie construction. See Performance for how the parser and tries achieve this.

Note on the synthetic data: flokbn generate static-demo scatters IPs almost uniformly at random, so the vast majority of requests carry a distinct address. Real logs reuse client IPs heavily, which builds a far smaller, cache-resident trie - so on real traffic of the same volume the build times are substantially lower. Read the figures above as a deliberate worst case.