Changelog
Version history and release notes pulled directly from each project's CHANGELOG file on GitHub.
v2.0.1 | Mar 25 2026:
-- New Features --
[New] Monitor mode: supervisor model replaces double-fork; 12 pre-existing defects resolved (graceful shutdown, crash recovery, session rotation, event filter, PID guard, ClamAV cache); issue #454, #447, #459
[New] Config: digest_interval, digest_escalate_hits, monitor_paths_extra for monitor mode
[New] TSV canonical session format: 11-field hit records replace plaintext session files;
session_legacy_compat config (auto/0/1) for backward compatibility
[New] JSON report output: --json-report [SCANID|list|newest] renders TSV sessions as
JSON (v1.0 schema); --format text|json|html and --mailto ADDRESS
[New] Compound signature (csig) scanning: multi-pattern boolean logic (AND/OR/threshold),
case-insensitive matching (i:), wide UTF-16LE matching (w:), bounded gap wildcards
({N-M}); csig.dat and custom.csig.dat; scan_csig config var
[New] SHA-256 hash-based scanning: CPU hardware acceleration auto-detection (SHA-NI on
x86, SHA2 on ARM); scan_hashtype config (auto/sha256/md5/both); sha256v2.dat
signature file; ClamAV .hsb integration with version gate (>= 0.97);
custom.sha256.dat for user-defined sigs
[New] Native batch scan engine: 43x faster HEX+CSIG; parallel grep workers;
micro-chunked processing (scan_hex_chunk_size config); Perl dependency removed
[New] Batch hit processing: unified bulk operations replace per-hit fork loops across
all scan engines; batched chattr/chmod/chown for quarantine
[New] Native YARA scanning: scan_yara=1 enables YARA as independent scan stage with
yara or yr (YARA-X) binary; custom.yara and custom.yara.d/ drop-in directory;
compiled rules via yarac, --scan-list batch scanning; issue #392, #277, #239
[New] Email alerts reimagined: HTML+text dual-format with template engine ({{TOKEN}}
placeholders); SMTP relay support (smtp_relay, smtp_from, smtp_user, smtp_pass);
email_format config (text/html/both); Slack Block Kit, Telegram MarkdownV2,
Discord embed templates; on-demand HTML rendering from session data
[New] Discord alerting: discord_alert and discord_webhook_url config; scan reports
uploaded via Discord webhook multipart upload
[New] Shared library integrations: alert_lib, elog_lib (structured event logging with
audit trail), tlog_lib (incremental log tracking), pkg_lib (packaging primitives)
[New] ClamAV signature validation gate: clamscan -d validates sig files before
deployment; sigup() SIGUSR2 reload gated on validation success; issue #467
[New] sigup_interval: independent 6-hourly signature update cron job via
/etc/cron.d/maldet-sigup; --cron-sigup handler; configurable interval (0 to disable)
[New] cron.watchdog: weekly watchdog script for independent fallback signature updates
[New] cron.daily: explicit cPanel addon/subdomain document root detection via
/etc/userdatadomains; Bitrix panel detection; issue #268, #381
[New] -v/--version flag displays version information and exits 0
[New] scan_clamscan and scan_yara support 'auto' mode for runtime binary detection
[New] Hook scanning API: modsec/ftp/proftpd/exim/generic mode dispatch; --list/--stdin
batch; rate limit; sig masking
[New] Hook escalation: immediate alert on hit threshold; deduplicated with digest
[New] Hook digest and reporting: --digest on-demand alerts; --report hooks with
time/mode filters
[New] --test-alert {scan|digest} {email|slack|telegram|discord}: send test alert with
synthetic data through the real rendering pipeline; channel isolation; root-only gate
[New] Audit log: 7 event types (purge, update, alert_failed, hookscan source/rate metadata)
[New] importconf: config migration extracted from install.sh for shared use
[New] RPM and DEB packaging: FHS-compliant layout with backward-compatible symlink farm
[New] Package install tests: FHS layout, symlink farm, permissions verification
[New] Symlink farm enforcement: static manifest ships with RPM/DEB; pkg_fhs_verify_farm
auto-detects and repairs broken symlinks at startup
[New] FHS fallback sourcing: maldet boots from /usr/lib/maldet when legacy symlinks broken
[New] Portable source-tree execution: run maldet directly from git clone or tarball without
install.sh; auto-detects inspath from script location; LMD_BASEDIR env override
[New] Scan lifecycle management: --kill, --pause/--unpause, --stop/--continue for running
scans; -L/--list-active shows running/paused/stopped scans (text/json/tsv);
--maintenance rotates history, compresses sessions, archives by month; session index
for O(1) report listing; scan.meta tracks per-scan state; scan_meta_cleanup_age config
[New] SCANID format validation: ^[0-9]{6}-[0-9]{4}\.[0-9]+$ regex enforced at all 5 CLI
entry points (--kill, --pause, --unpause, --stop, --continue)
[New] -e list: redesigned columnar output with header row; --all flag shows full scan
history (default: recent 14 entries); QUAR column; stopped/checkpointed scans visible
between active and history sections (text and JSON); pager when --all on TTY;
JSON active array; FreeBSD sort support
[New] Logrotate config: /etc/logrotate.d/maldet rotates event_log, clamscan_log, and
audit.log weekly (12 rotations); inotify_log excluded (managed by --maintenance);
installed only when logrotate is available; RPM/DEB: %config(noreplace)
[New] post-scan hook: configurable script execution after scan completion;
supports args/file/json output tiers, sync/async execution, timeout
with SIGTERM/SIGKILL, elog audit trail, min_hits threshold, scan type filter;
scan_start epoch in JSON payload (LMD_SCAN_START env); issue #477
[New] ignore_inotify: two-file union model — LMD-managed defaults
($inspath/internals/ignore_inotify.defaults) refreshed for systemd-private
tmpdirs, modern MariaDB, PostgreSQL, Redis, ClamAV runtime; user-owned
ignore_inotify retained for site overrides; dead legacy regex defaults
replaced (broken since _monitor_escape_ere change); issue #480
[New] monitor_scan_owner_filters config variable: when 0 (default), monitor mode
admits all files regardless of owner so root-owned malware is not silently
dropped; when 1, scan_ignore_root/user/group ownership filters are applied
in monitor mode (opt-in); issue #485
[New] tests/uat/05-monitor-mode.bats: UAT scenario asserting monitor quarantines
a root-owned EICAR dropped into a webuser-owned docroot under reporter's
config (scan_ignore_root=1 + monitor_scan_owner_filters=0 default);
regression test closing the coverage hole that let rc1..rc3 ship with the
filter bug; issue #485
-- Changes --
[Change] slop: remove 15 unused LMD_<STEM>_VERSION sub-lib constants +
paired shellcheck disable=SC2034 suppressions; set at sub-lib
extraction, never consumed by any code, test, packaging, or
doc surface; _LMD_<STEM>_LOADED source guards (distinct symbols)
remain as the idempotent-sourcing mechanism; round 3 slop audit
[New] _monitor_to_ere_entry(): semantic dispatch helper for ignore_inotify
entry preparation (defaults-always-escape, user-raw-or-literal:)
[Change] tests/48-monitor-ignore-regex.bats: integration coverage for
ignore_inotify ERE + literal: prefix + ignore_paths monitor-mode
consistency + malformed-regex symmetry; issue #484
[Change] docs: ignore_inotify ERE-default + literal: prefix semantics
across ignore_inotify header, README §7, maldet.1 FILES; issue #484
[Change] tests: prune two tautological assertions — the
"uninstall.sh delegates service removal to pkg_service_uninstall"
grep-for-token check in 01-install-cli.bats (string presence, not
behavior) and the "ignore_inotify.defaults is installed at
internals path" existence check in 47-ignore-inotify-defaults.bats
(adjacent "has non-comment entries" test already fails i
... (truncated - view full changelog on GitHub)- 2.0.2 | Apr 11 2026:
-- New Features --
[New] Connection tracking limit (CT_LIMIT): global per-IP connection limit via
periodic conntrack table scanning; configurable threshold, scan interval,
block duration, port/state filters, TIME_WAIT exclusion, CIDR exemptions;
VNET per-IP overrides; temp-deny blocking with PERMBLOCK escalation;
CLI: --ct-scan (manual scan), --ct-status (show config and active blocks)
[New] GeoIP country blocking: ipset-based high-performance country filtering
using cc_deny.rules/cc_allow.rules with ISO 3166-1 alpha-2 codes and
continent shorthand (@EU, @AS, @NA, @SA, @AF, @OC); tiered data sources
(ipverse.net, ipdeny.com) with CC_CACHE_TTL freshness tracking; atomic
ipset swap for zero downtime updates; audit mode (CC_LOG_ONLY) for impact
measurement; dual-stack IPv4/IPv6; advanced per-port/protocol syntax;
granular entry removal (apf -u preserves unrelated entries for same CC);
--cc IP reverse lookup via awk CIDR containment; --cc CC detail display;
per-CC download progress feedback; geoip_lib.sh shared metadata library
[New] Structured event logging via elog_lib.sh: dual log model (application log
+ JSONL audit trail at /var/log/apf/audit.log); severity filtering;
eout() backward-compatible wrapper; 23 audit events across 9 event types
covering trust, blocking, config, and error lifecycle
[New] SYN flood protection: SYNFLOOD chain rate-limits inbound TCP SYN packets
via iptables limit module; configurable rate/burst thresholds, LOG +
DROP for excess, dual-stack support
[New] SMTP outbound blocking: SMTP_BLOCK restricts outbound SMTP (ports 25,
465, 587) to whitelisted users/groups; SMTP_ALLOWUSER/SMTP_ALLOWGROUP
exemptions; works independently of EGF toggle
[New] Temporary allow/deny with per-entry TTL: apf -ta/-td HOST TTL adds
time-limited trust entries (300, 5m, 1h, 7d); automatic cron expiry;
list with --templ, flush with --tempf; block escalation auto-promotes
repeat temp denies to permanent via PERMBLOCK_COUNT/PERMBLOCK_INTERVAL
[New] Custom hook scripts: hook_pre.sh/hook_post.sh sourced before/after
iptables rules; activated by chmod 750; preserved across upgrades
[New] Silent IP blocking: silent_ips.rules drops all traffic to/from listed
addresses with no logging; IPv4/IPv6 supported; preserved across upgrades
[New] CLI overhaul: new commands (-g search, --rules, --info, --lookup,
--validate, --list-allow/--list-deny, --cc/--cc-update, --dump-config);
subcommand architecture (apf <noun> <verb>) with 7 groups (trust, cc,
config, status, gre, ipset, ct); per-group help; CSF compatibility
aliases (-ar, -dr, -tr, -i) with --csf-help mapping; all 2.0.x flat
flags preserved as silent aliases
[New] Per-port connection limiting via xt_connlimit: IG_TCP_CLIMIT and
IG_UDP_CLIMIT with port:limit pairs, port range support, VNET binding
[New] ipset 7-field format: per-list refresh interval and max entry count;
backward-compatible auto-migration from 4-field and 5-field formats;
batch loading via ipset restore for large block lists
[New] Advanced trust syntax in CLI: apf -a/-d/-ta/-td/-u accept
proto:flow:port:ip format (e.g., "tcp:in:d=22:s=10.0.0.0/8") for
protocol/direction/port-scoped trust rules; duplicate detection, IPv6
bracket syntax, and port ranges supported
[New] FQDN pre-resolution in trust system: hostnames resolved via getent
before loading into iptables; multiple A/AAAA records, IPv6 support,
configurable timeout via FQDN_TIMEOUT; resolved= metadata enables
removal without DNS; refresh (apf -e) re-resolves FQDNs; --lookup
resolves FQDN entries when queried with an IP address
[New] validate_config(): startup config validation with clear error messages
covering interfaces, stop targets, rate/burst formats, connlimit
syntax, RAB parameters, logging, expiry, and sysctl settings
[New] Ban expiry: structured addedtime=EPOCH markers in all trust entries
for reliable expiry; backward-compatible with pre-2.0.2 entries
[New] Auto-detection for USE_IPSET, IPT_LOCK_SUPPORT, and DOCKER_COMPAT:
each resolves at startup based on system capabilities; CentOS 6
(iptables 1.4.7, no -w) safely degrades; replaces hardcoded defaults
with "auto" (upgrade-safe)
[New] install.sh: auto-detect default network interface and conflicting
firewall services (firewalld, ufw); structured output via pkg_lib;
post-install dependency warnings; fix directory permissions (750/640);
verify source files; prevent backup collision; re-create periodic cron
entries on upgrade
[New] Unified download helper: curl primary with wget fallback, redirect
following, timeouts, retries; TLS diagnostic hints on failure
[New] uninstall.sh: removes all system-wide artifacts including cron, man
page, symlinks, and audit log directory; prompts before removing
install directory and logs
[New] Bash tab completion: apf.bash-completion installed to
/etc/bash_completion.d/apf; subcommand-aware with context-sensitive
completions for trust hosts, temp TTL values, and country codes
[New] RPM and DEB packaging with FHS-compliant layout: binary at /usr/sbin/apf,
libraries at /usr/lib/apf/internals/, config at /etc/apf/; 25-symlink
backward-compatible farm for /etc/apf/internals/; .symlink-manifest and
pkg_fhs_verify_farm() startup self-healing; Docker build infrastructure
(RPM el7/el9, DEB Debian 12); package install verification test suite;
migration from install.sh via importconf; 11 RPM conffiles with
%config(noreplace)
[New] RPM/DEB: auto-detect default network interface via ip route on fresh
install; sets IFACE_UNTRUSTED in conf.apf when system default differs
from eth0
[New] Man page (apf.8) and README restructure with complete CLI, trust
system, GeoIP, and configuration reference
-- Bug Fixes --
[Fix] apf --cc <CC> and apf cc info <CC>: restore geoip_cc_known() function
deleted by canonical lib sync; production saw "command not found" for
every country code input
[Fix] importconf: fix config merge corruption on reinstall; drop
pkg_config_merge for internals.conf (conditional syntax was mangled);
guard vnet/*.rules glob; preserve hook permissions and TCR_PORTS during
upgrade; restore internals.conf from backup; migrate DOCKER_COMPAT,
USE_IPSET, IPT_LOCK_SUPPORT to "auto"
[Fix] RPM: move install.sh migration from %pre to %pretrans to prevent
.rpmnew conffile conflicts; abort on backup failure; pass explicit
BK_LAST to importconf
[Fix] RPM/DEB: wipe legacy install path after backup to prevent cpio
dir-to-symlink extraction failure; clean pre-decomposition files and
runtime state on removal; fix SysV init double-stop on RHEL; DEB adds
util-linux dependency for flock
[Fix] install.sh: graceful migration from RPM/DEB FHS symlink farm. Detects
and removes /etc/apf/{extras,doc} dir symlinks plus per-file symlinks
under internals/ and vnet/vnetgen pointing into /usr/lib/apf or
/usr/share/doc/apf before pkg_copy_tree. Without this, install.sh over
a package-managed install failed with "cp: cannot overwrite
non-directory" and would silently write through per-file symlinks into
package-managed paths. User-created symlinks are preserved.
[Fix] Trust validation: anchor grep/sed patterns to prevent IP substring
collisions; glob expansion protection in trust_parse_fields(); control
character and metacharacter rejection in valid_trust_entry(); add hint
for abbreviated CIDR notation (e.g., '0/0' -> '0.0.0.0/0')
[Fix] Add mutex_lock to -a, -d, -u, -ta, -td CLI handlers; closes race
window with cron --temp-expire on flock-absent systems
[Fix] Trust removal: spec-based iptables rule deletion
... (truncated - view full changelog on GitHub)2.0.2:
-- New Features --
[New] Text email alerts: format C redesign — dense key:value layout with
lowercase labels (when/host/rule/action/why/logs), subject-line-first
dynamic header "[BFD] <verb> · <svc> · <host> (<CC>) · <hostname> ·
<severity>" for single bans and "[BFD] <N> bans · <services> · <host>"
for multi-ban digests. ESCALATED permanent bans and subnet/C-class
bans are called out explicitly in both subject and body. Adds
EMAIL_SUBJECT_STYLE config (summary|legacy) — set "legacy" to keep
EMAIL_SUBJECT verbatim with "(N bans)" suffix for multi-ban digests
[New] CLI subcommand namespaces: bfd ban, bfd ignore, bfd test, bfd cdn,
bfd report, bfd status — grouped help, bash completion, typo
suggestions ("Did you mean?"), and bfd help <topic> routing
[New] bfd ignore: add/remove/list/check for ignore.hosts management with
flock safety, CIDR normalization, and containment checking
[New] bfd status sub-views: lock (PID/age/staleness), cursors (tlog positions),
pool (attack pool stats), pressure (decay-adjusted scores)
[New] bfd ban history: query across rotated bans.history archives with
IP filter, time windows (--24h/--7d/--30d), and --json/--csv output
[New] CDN/trusted proxy subsystem: ignore/exclude/derate treatment modes,
binary-search IP range lookup (IPv4/IPv6), cdn-providers.conf with 5
default providers, bfd --cdn CLI, CDN_ENABLE and CDN_UPDATE_DAYS config;
cron.daily auto-refresh and --json output support
[New] CDN_ENABLE="auto" mode: auto-detect from cdn-providers.conf (uncommented
entries = enabled); first-run background fetch at install; config_init
one-shot retry for missing cdn.dat; default changed from "0" to "auto"
(no behavior change for fresh installs — all providers ship commented)
[New] DATA_PATH variable in internals.conf: data files (ipcountry.dat,
ipcountry6.dat, cdn.dat, cdn6.dat, .last_update) moved from
$INSTALL_PATH/ root to $INSTALL_PATH/data/ subdirectory; importconf
migrates from flat layout on upgrade; RPM/DEB install to
/usr/share/bfd/data/ with legacy compat symlinks
[New] Symlink farm enforcement: sbin symlink manifest + runtime self-healing
(pkg_lib v1.0.6)
[New] Structured audit events for ban escalation, manual ban/unban/flush,
alert delivery, detection triggers, and scan cycle bookends;
audit.log added to logrotate
[New] Config validation: REPORT_ENABLED, REPORT_TOP_N, REPORT_CHANNELS checked
at load time; 8 missing vars added to SIGHUP reload unset block
[New] Color-aware help output: bold section headers respecting NO_COLOR,
non-terminal, and TERM=dumb
[New] Documentation: man page CDN/Trusted Proxy, Country Weighting, and
SUBCOMMANDS sections; --cdn CLI options, sub-library FILES entries;
README redesign with SECURITY.md and CONTRIBUTING.md
-- Bug Fixes --
[Fix] RPM/DEB: exclude.files sed anchored to prevent ignore.hosts.local
path mismatch (self-ban risk on FHS package installs)
[Fix] RPM/DEB: comprehensive packaging fixes — add 9 sub-libraries +
update-cdn-providers.sh to manifests; tlog BASERUN set to
/var/lib/bfd/tmp; /var/log/bfd/ created at install for logrotate;
background ipcountry + CDN database downloads on fresh install; cron
files marked config(noreplace); DEB daemon-reload in postinst; symlink
manifest for pkg_fhs_verify_farm(); defensive %pre/preinst cleanup for
orphaned source-install directories; .shellcheckrc and .gitmodules
excluded from release tarball
[Fix] DEB: /var/log/bfd permissions clobbered by dh_fixperms (normalized to
755, wanted 750). Postinst now restores 750, matching the existing
pattern for /var/lib/bfd/tmp and /var/lib/bfd/stats
[Fix] File permissions: lock.utime (creation and heartbeat) and ignore.hosts.local
consistently set to mode 640
[Fix] Report: --7d/--30d correctly maps to weekly/monthly interval; top-IPs
table emits all 7 columns; messaging templates use JSON-escaped vars
[Fix] --status error for unknown service directed to stderr
[Fix] threat_detected audit event now fires on ban-failed path
[Fix] Multi-channel alert rendering: per-channel dispatch prevents cross-channel
contamination, Telegram MarkdownV2 escaping for special characters
[Fix] CIDR sidecar enrichment data preserved across all rendering passes
[Fix] pam_generic rule: PREREQ changed from AUTH_LOG_PATH to /etc/pam.d/other
so journal fallback works on rsyslog-less systems (RHEL 10+, Ubuntu minimal);
added SYSLOG_FACILITY=10 journal registration (39 of 57 rules now covered)
[Fix] Temp file hardening: extract_hosts uses $INSTALL_PATH/tmp;
_check_file_safety handles 4-digit setuid/setgid perms
[Fix] show_status "Top services" trailing comma when fewer than 3 services
[Fix] Lock contention message log-file-only when holder is alive
[Fix] Stale IPv6 GeoIP build dirs cleaned up at update-ipcountry.sh startup
[Fix] install.sh: consistent "Updating ... (background)..." format for
IP country and CDN provider database fetch messages
[Fix] FIREWALL validation rewritten as case statement (was for-loop over
space-split list) — IFS-independent, prevents intermittent failures
[Fix] Deep-legacy portability: all bare coreutils (chmod, mkdir, cat, touch,
ln, cp, mv, rm) use command prefix for CentOS 6 PATH compatibility
[Fix] Unban error message includes hint for firewall-only blocks
[Fix] status_pressure: use grep -Fq for IP presence check to prevent regex
interpretation of dot characters in IP addresses
-- Changes --
[Change] Dead code cleanup: remove empty firewall setup hooks
(_fw_firewalld_setup, _fw_ufw_setup, _fw_custom_setup) and their
fw_setup() dispatch arms. These backends need no init; removing
the noop stubs drops 3 function defs with zero behavior change
[Change] tlog wrapper relocated from files/tlog to files/internals/tlog,
matching LMD's private-tool layout. The /usr/local/sbin/tlog symlink
is dropped on all install types (RPM, DEB, source). For interactive
byte-offset log debugging, invoke /usr/local/bfd/internals/tlog
directly or create a user symlink
[Change] Vendored library sync: tlog_lib 2.0.6, alert_lib 1.0.7,
elog_lib 1.0.6, pkg_lib 1.0.10, geoip_lib 1.0.7. Includes POSIX
herestring compat + ln -sfn symlink safety (pkg_lib), geoip_cc_known
regression fix (geoip_lib), and comment-discipline cleanup across
all five. Zero functional change
[Change] pressure.conf and pressure-country.conf migrated to whitespace
format; dual-format parsers for backward compat; importconf auto-converts
[Change] --check shows "no service detected" for rules with log-based PREREQ
[Change] Help output: usage_short() condensed to 22-line three-section layout;
-h shows full reference, --help opens man page (progressive disclosure)
[Change] conf.bfd reordered: 12 to 10 sections grouped by admin workflow
priority; Slack/Telegram/Discord combined into Messaging Alerts;
Advanced split into Detection Tuning and Logging & Output
2.0.1:
-- New Features --
[New] CIDR subnet ban alerts enriched with aggregate pressure and failure stats
[New] Contributing hosts breakdown in all alert channels (email, Slack, Telegram, Discord)
[Fix] CIDR alert pipeline fields 4 and 13 now carry real aggregate values (was broken)
[Fix] Packaging: cron.daily keeps /usr/local/bfd INSTALL_PATH in FHS packages
(symlink farm provides the indirection; /var/lib/bfd has no conf.bfd or bfd)
[Fix] Packaging: alert template count assertion updated from 14 to 21
[Fix] Packaging: DEB changelog rule count corrected from 42 to 57
[Fix] CI: add bfd_report.sh to bash -n and shellcheck lint targets
[Fix] Man page: add --ban, --unban, --events, --test long forms to OPTIONS;
fix template variable names (FAIL_COUNT_DISP
... (truncated - view full changelog on GitHub)