Changelog

Everything that shipped,
in the order it shipped.

First public release was v0.1.0 (2026-04-24). Latest is v0.1.1 (2026-05-21). Each entry lists scope, behavior change, and the commit. Benchmark deltas where the change touched a hot path.

git · github.com/ul0gic/fail2zig latest release · v0.1.1 on GitHub format · keep a changelog

0.1.1

2026-05-21 stable sha · 10a640d

Patch release. Two correctness fixes for behavior the v0.1.0 schema documented but the daemon didn't actually deliver. Drop-in binary replacement — state file v1 auto-migrates to v2 on first checkpoint, no data loss.

Fixes

  • state Per-jail maxretry · findtime · bantime · bantime_increment are now actually applied. In v0.1.0 they were parsed and accepted but silently ignored — every jail received [defaults]. Per-jail StateTracker instances now route matches through the right thresholds. State file format bumped to v2; v1 files migrate on first checkpoint. 7f4b486
  • config TOML parser now accepts fractional values for bantime_increment_multiplier and bantime_increment_factor. Both fields are f64 in the schema, but the parser was reading them as integers — factor = 1.5 for a softer escalation curve now works. 2c5ca20

0.1.0

2026-04-24 stable sha · 1340cae

First public release. A single static musl binary replaces fail2ban's Python runtime, regex engine, and shell-out action chain. Hardened across eleven Phase-7 security issues and eight real-system SYS bugs before tagging. AGPL-3.0-or-later.

Parse throughput
5.96M l/s
271× target
Ban latency p99
932 ns
p50 365 ns
Binary (musl x86_64)
877 KB
stripped
Resident memory
22 MB
64 MB ceiling

Features

  • engine Single static musl binary · zero runtime dependencies · 877 KB stripped 5a85cc2
  • core epoll / timerfd / signalfd event loop · inotify log watcher with rotation + copytruncate handling 98cd331
  • parser Comptime-specialized parsers · no regex engine in the process · zero-allocation hot path verified with FailingAllocator ec31dee
  • firewall Pure netlink for nftables (no libnftnl, no shell-out) · argv for iptables + ipset ec31dee
  • state Fixed-arena state tracker with three eviction policies · atomic CRC-checked persistence · per-IP 128-slot ring buffer a9e77dc
  • config Native TOML parser + full --import-config /etc/fail2ban pipeline · translates filter.d regexes · maps action.d backends · emits validated TOML 3af15fd
  • filters 15 built-in comptime filters: sshd, nginx-*, apache-*, postfix / dovecot / courier, named, vsftpd, proftpd, mysqld, recidive 3af15fd
  • cli fail2zig-client — status / ban / unban / list / jails / reload · table, JSON, plain output · bash / zsh / fish completions 2130f37
  • metrics Built-in HTTP/1.1 server · /metrics (Prometheus), /api/bans (JSON), /events (WebSocket, RFC 6455) 2130f37
  • demo Live internet-exposed honeypot at fail2zig.com/demo · real sshd + nginx attack surface · three-pane dashboard 1803321
  • release Static musl binaries for x86_64 + aarch64 · SHA256SUMS · hardened systemd unit · scripts/install.sh one-shot installer 1340cae

Security

  • parser SEC-001 (HIGH) — IPv4-mapped IPv6 canonicalization closes ban-evasion by peer-format flipping 8bfc74f
  • ipc SEC-002 (MEDIUM) — umask(0o117) around bind() closes the socket-permissions race window 8bfc74f
  • http SEC-005 / SEC-008 — security headers on every response · 5s per-client read deadline · 100/sec accept rate cap 8bfc74f
  • fuzz Fuzz corpora for parser · IP parser · IPC protocol · TOML config — all with FailingAllocator to surface unbounded allocation as OutOfMemory 8bfc74f
  • systemd Hardened unit: CAP_NET_ADMIN + CAP_DAC_READ_SEARCH only · ProtectSystem=strict · NoNewPrivileges · seccomp filter · systemd-analyze security 2.4 7df630f
  • license AGPL-3.0-or-later · trademark asserted on "fail2zig" name + logo · vulnerabilities reported via GitHub Private Security Advisories ef16d7c

Performance

  • bench Parse throughput 5.96M lines/sec (271× the 22K target) · ban latency p50 365 ns / p99 932 ns (1000× under the 1ms target) 8bfc74f
  • memory Hard memory ceiling held across 50K unique IPs: 21,845 entries resident, 15,606 evictions — ceiling never exceeded 8bfc74f
  • binary Static musl binary 877 KB (x86_64) / 801 KB (aarch64) — well under the 5 MB ceiling 1340cae

Fixes

  • ws SYS-010 (CRITICAL) — use-after-free in ws.dispatchFrame on close / binary frames b101619
  • filter SYS-011 (HIGH) — sshd filter matched clean disconnects, causing operator self-ban during honeypot bring-up b101619
  • state SYS-007 (HIGH) — restored bans on restart now re-installed into the firewall; previously the state tracker held them but the kernel didn't enforce dc916a9
  • state SYS-008 (HIGH) — bantime_increment now wired from [defaults] to the state tracker (was silently ignored) 3d866c7
  • ws Broadcast wiring + event schema aligned to the documented type / ts / payload envelope; dashboard counters now tick in real time 4d75186
  • ipc ISSUE-003 — daemon and client socket-path defaults unified on /run/fail2zig/fail2zig.sock 2130f37
  • build ISSUE-004 — @bitCast size mismatch (i128 → u64) resolved via @truncate 2130f37