Architecture
Single binary, zero CGO, bounded memory (LRU + histogram). For package signatures and import examples, see API / Packages. To add a feature, see Contributing.
Data flow
Sources Reader Parser
────────── ────── ──────
file / glob ─┐
stdin - ├─▶ reader.FromSource(follow) ─▶ parser.Parse(line) ─┐
docker:// ───┤ fan-in + rotation │
k8s:// ──────┤ stdout+stderr merge ▼
journalctl ──┘ ┌─────────────────┐
│ analysis.Engine │
│ ├─ MatchEntry() │◀── types.Filters (--from, --ip, --country, --grep, --level…)
│ ├─ Enrich ───────▶ enrich.GeoIP.Lookup(ip) → Country/ASN (LRU 50K, TTL 24h)
│ ├─ Detect ───────▶ analysis.Detector (26 cats, dual-pass, MITRE)
│ └─ Stats (TopN, histogram 1K buckets, human/bot) │
└────────┬────────┘
│
┌────────────────────────┼────────────────────────┐
│ │ │
output.Report tui.Model (watch) guard.Guard
table/json/csv/html 9 tabs, 2s tick ├─ Evaluate(entry) ──▶ blocklist.Manager.Contains(ip) → immediate
├─ enrich.GeoIP ──▶ country-block → immediate
├─ sliding window (per-sec buckets) → Tick()
├─ firewall.Backend (auto → hybrid/iptables/docker/nftables)
└─ audit + state (JSONL + blocked.json)
Packages — where concepts land
| Concept you read elsewhere | Package | Link |
|---|---|---|
| “Caddy JSON → LogEntry” | pkg/parser | API → parser |
| “Top country/asn, GeoIP cache” | pkg/enrich | API → enrich |
| “26 categories, MITRE, Sigma” | pkg/analysis | API → analysis, Detection |
| “8 feeds, CIDR trie, 7d cache” | pkg/blocklist | API → blocklist |
| “sliding window, audit, state” | pkg/guard + guard/firewall | API → guard |
| “file/docker/k8s/journalctl” | pkg/reader | API → reader |
| “table/html/json” | pkg/output | API → output |
| “live dashboard” | pkg/tui | TUI & Reports |
Memory bounds
MaxCardinality 100k per counter, IPs LRU 100k, histogram 1000 buckets (log10, 1µs–10s), GeoIP LRU 50k. No unbounded maps.
Guard self-IP flow
cmd/guard.go calls guard.DetectSelfIPs(ctx) before constructing guard, unless --protect-self=false. Interface addresses and direct HTTPS egress discovery seed Config.SelfIPs; Config.SelfIPLookup enables refresh every 30 seconds in Run and before new bans in the shared block path.
Self-IP matching also rejects subnet candidates containing an own address. State recovery never recreates a protected ban; cleanup removes tracked rules/state and retries failed removals. Lookup failure preserves the previous address set but prevents new bans. Discovery is synchronous and bounded; it never derives trusted server addresses from access logs or forwarded headers.
Config flow
caddy-analyzer.json (local → global) → PersistentPreRunE applyTuning() → CLI flag overrides. Guard adds whitelist + --never-block union. See Configuration.
pkg/analysis/detect.go), a feed (pkg/blocklist), or a firewall backend (pkg/guard/firewall).