Changelog
Version history and release notes pulled directly from each project's CHANGELOG file on GitHub.
v1.6.6.1 | Feb 25 2025:
[Fix] find_recentopts incorrectly escaping find options to the right of ( -mtime .. -ctime ); previously normalized by eval; issue #440, pr#442
[Fix] persist configuration value inotify_docroot between upgrades; issue #439
v1.6.6 | Feb 19 2025:
[Fix] replaced eval usage in dynamic execution to improve security; thank you for responsible disclosure from barrebas
[Fix] malware notification emails to ignore inactive siteworx users; pr #425
[New] add reporting support for telegram channel; pr #378
[New] add statistics collection and sending to ELK; pr #359
[Fix] prune ignore_paths with find -prune; pr #423, issue #433
[Fix] suppress excessive clamav temporary file inotify alerts by adding '/tmp/.*scantemp.*' to ignore_inotify; issue #431, #104
[Fix] consistent cron.daily file sourcing to allow configuration overrides; issue #401, #115
v1.6.5 | Mar 27 2023:
[Fix] monitor mode white space detection; issue #354
[Change] event_log/clamscan_log now record year in timestamp; issue #352
[Change] -p|--purge will now trim the inotify_log; issue #350
[New] -E|--dump-report to dump reports to stdout; pr #362
[Fix] monitor mode will now fail to start if 'ed' is not installed; issue #350
inotify_log requires in-place inode pruning to prevent exponential growth
[Fix] inotify kernel support on debian11 checking only System.map; pr #398
[Fix] human-readable path not displaying on -a|--scan-all default path scan (/home); #407
[Change] default scoped scan adjusted from /var/www/html to /var/www to make sure we scope all www content; #404
[Fix] compare md5 on ignore_sigs between monitor mode cycles and only regenerate signatures on file changes; #397
[New] add detect_control_panel function to files/internals/functions to determine installed control panel; pr #409
[New] add get_panel_contacts to files/internals/functions to discover contact emails; pr #409
[New] add configuration options for From, Subject, Reply-To headers on alert emails; pr #409
[New] add flag to enable these alerts (requires email_alert to be enabled as well); pr #409
[New] add internal configuration to set the user alert template location; pr #409
[New] add a base template that will be used to create emails to control panel contacts; pr #409
[Change] ambiguous restore error modified to include file name
[Fix] adjusted ftp.rfxn.com checkout credentials; #390
[Fix] systemd unit file not copying properly; #371, #413
[Fix] monitor mode dependency failures on 'ed' not properly logging to be captured by unit file; #395
[Fix] newer versions of cpulimit explicitly enforce the usage of '--' to define where cpulimit options end; #395
v1.6.4 | Mar 18 2019:
[New] add quarantine_on_error variable to control quarantine behavior when scanner engines such as ClamAV encounter an error
[New] add support for slack alerts; pr #240 mostafahussein
[New] add ability to disable cron via conf.maldet; issue #260 / pr #300 , #304 sporks5000
[New] add cleaner rule for php.malware.magentocore_ccskim and an alias of as php_malware_hexinject for associated yara rule
[Change] update cron.daily for ispmanager5; pr #305 yogsottot
[Change] normalize variable naming of pr #300 , #304
[Change] validate cron_daily_scan is set; otherwise default to 1
[Change] update importconf for cron_daily_scan block
[Change] don't need "find" if given a file list; pr# 303 sporks5000
[Change] rename ambiguous internal variables related to user signatures
[Change] removed clamscan_return code capture from piped logic of clam(d)scan execution; now always capture return code, even on good exits
[Change] scan results now explicitly exclude any occurrences of files related to 'no reply from clamd' errors
[Change] add backward compatibility for renamed internals.conf variables
[Change] removed legacy $verbose tagging at the end of eout() calls
[Change] modified cleaner rules to set their own PATH scoping
[Change] file_stat() has been renamed get_filestat to match associated quar_get_filestat function naming
[Change] get_file_stat() will now grab md5 hash of files to avoid superfluous md5sum calls
[Change] added inotify elapsed run time to scan report output
[Change] adjust '-e|--report' output for etime value and spacing
[Change] force email_ignore_clean=1 to stop the most common email requested issue
[Fix] hitname not logging to quarantine.hist on manual quarantine run against scanid; issue #319
[Fix] typo in PR #300; missing '; then' on elif
[Fix] set default_monitor_mode to resolve issue #311 systemd service passing $default_monitor_mode as a literal string to the service
[Fix] sad mail/sendmail validation logic, fix issue #316
[Fix] normalized scan start time output in scan reports when inotify monitoring is used
[Fix] scan report list summary to always display an etime value, even if null
[Fix] ad-hoc clean calls from clean_hitlist() was not executing sigignore and gensigs functions causing clean tasks to fail due to missing variables; issue #203
[Fix] adjust semantics of comma and spaced variables being passed to '-co|--config-option'; pr #298 sporks5000
[Fix] modified quarantine_hits to force disable if clamdscan explicitly encounters a 'no reply from clamd' fatal error
[Fix] modified install.sh 'ps' execution to be BSD compliant
[Fix] clean function was not properly stripping {CAV} and {YARA} prefixes from signature names when executing cleaner rules
[Fix] clean function was not properly handling signature names with both underscores and periods
[Fix] refactored clean_hitlist() & clean() functions to resolve pathing errors when cleaning previous session hits; issue #203
[Fix] ignore_inotify file exist/empty file negative match; issue #330
[Fix] operator issue cron.daily #331
[Fix] install.sh $ver required major numbering; renamed to ver_major so that session preservation semantics continue to work
v1.6.3 | Sep 01 2018:
[Fix] ensure clamscan_max_filesize is always set; pr #296
[Fix] remove escaping from inotifywait exclude regexp; pr #246 issue #205
[Fix] always set a value for monitor mode systemd unit; pr #257
[Fix] quar_get_filestat variable collisions during restore operations
[Fix] quarantine files could be prematurely deleted, during 'cron.daily/maldet', on distributions where the 'mv' command
preserves origin file mtime; call 'touch' on quarantined files to set current mtime post-move to quarantine path; issue #294
[Fix] update tlog inotify tracking file before trimming to prevent rescan loop; pr #292
[Fix] revert pruning empty lines from signature files to 1.6.1 behavior
[Fix] usage semantics of cd'ing to a wildcard path on newer versions of Bash were causing version updates to fail; we now explicitly
'cd' to maldetect-${upstreamver}
[Fix] spelling corrections; pr# 269
[Change] update importconf text to reflect monitor mode on systemd behavior
[Change] on restore actions, reset restored files to original mtime value
[Change] increase default remote_uri timeout from 10s to 30s
[Change] increase default remote_uri tries from 3 to 4
[Change] added base_domain variable to internals.conf
[Change] cleanup .tgz/.md5 files on version updates mid-flight to prevent potential 'cd: too many arguments' errors
[Change] trim inotify log from beginning instead of end of file; pr #292
[Change] user mode scanning no longer scans system temporary paths; issue #283
[Change] improve regexp of scan start time values for '-e|--list' output
[Change] added '--beta' flag to '-d|--update-ver' to support pulling down beta release of LMD
[Change] stage v1.6.3 release; update version and date stamps
[Kudos] Thank you to those that contributed pull requests and issues during this release cycle. PR contributions from:
sporks5000
jsoref
Joshua-Snapp
mkubenka
jkronza
AnnopAlias
v1.6.2 | Jul 13 2017:
[Fix] signature updates using get_remote_file() would incorrect write temporary update files into /; issue #242
[Fix] added 'which curl' and 'which wget' for variable scoping of binary loc
... (truncated — view full changelog on GitHub)- 2.0.1 | Feb 20 2026:
-- New Subsystems --
[New] Docker/container chain preservation mode (DOCKER_COMPAT); surgical
flush via flush_apf_chains() removes only APF-owned chains, preserving
Docker, containerd, Kubernetes, and Podman chains in FORWARD and nat.
save_external_baseline()/restore_external_baseline() captures and
replays non-APF INPUT/OUTPUT rules across flush cycles. Fast load
and snapshot save auto-disabled in compat mode (conf.apf,
functions.apf, apf, .ca.def)
[New] ipset block list support: kernel-level hash tables for O(1) IP
matching. USE_IPSET config toggle, ipset.rules definition file,
ipset_load()/ipset_update()/ipset_flush() lifecycle functions,
--ipset-update CLI for cron hot-reload, cron.d.apf_ipset job,
ip_set/xt_set module loading (conf.apf, internals.conf,
functions.apf, bt.rules, apf, install.sh, ipset.rules)
[New] GRE tunnel support: encapsulated point-to-point links with dedicated
GRE_IN/GRE_OUT chains, protocol 47 rules, per-tunnel interface accept
rules. USE_GRE config toggle, gre.rules definition file, lifecycle
functions (create_gretun/destroy_gretun/gre_init/gre_flush/
gre_teardown/gre_status), --gre-up/--gre-down/--gre-status CLI,
auto-MTU, keepalive, tunnel key, role routing (conf.apf, internals.conf,
gretunnel.sh, gre.rules, firewall, functions.apf, apf, .ca.def)
[New] Centralized dependency checking via check_deps(); validates iptables,
ip, modprobe, ip6tables (critical) and wget, iptables-save/restore,
diff (warning) before firewall start with OS-aware install hints
(functions.apf, apf)
[New] systemd service unit (apf.service); install.sh prefers systemd over
SysV init when /run/systemd/system detected (install.sh)
[New] -v|--version CLI option outputs version number (apf, functions.apf)
[New] Adaptive conntrack scaling (SYSCTL_CONNTRACK_ADAPTIVE); auto-scales
conntrack_max when usage exceeds 80%, capped at SYSCTL_CONNTRACK_HIGH.
Hash table sizing via SYSCTL_CONNTRACK_BUCKETS (sysctl.rules, conf.apf)
[New] nf_conntrack_helper disabled by default to reduce ALG attack surface
(CVE-2013-6390, CVE-2019-8956) (sysctl.rules)
-- IPv6 Dual-Stack Support --
[New] Dual-stack iptables helpers: ipt() applies to both $IPT/$IP6T when
USE_IPV6=1; ipt4()/ipt6() for protocol-specific rules; ipt_for_host()
routes by address family; ipt_dst()/ipt_src() for VNET-aware port
rules. Adopted across firewall, bt.rules, log.rules, cports.common,
functions.apf
[New] valid_host() and valid_ip_cidr() input validation functions; validates
IPv4 octets (0-255), CIDR masks (0-32 / 0-128), IPv6 colon groups,
compressed forms (::1, ::/0), FQDN dot requirement (functions.apf)
[New] IPv6 port filtering with ipt_dst()/ipt_src() VNET-aware helpers;
ICMPv6 filtering via IG_ICMPV6_TYPES/EG_ICMPV6_TYPES; NDP types
133-136 always permitted (cports.common, conf.apf, functions.apf)
[New] IPv6 PKT_SANITY: IN_SANITY6/OUT_SANITY6 chains with TCP flag checks,
INVALID state blocking, PZERO6 port-zero filtering, and FRAG_UDP6 for
fragmented UDP; includes RAB integration matching IPv4 (bt.rules)
[New] IPv6 multicast blocking; MCAST6 chain blocks ff00::/8 when
BLK_MCATNET=1 and USE_IPV6=1, with NDP ICMPv6 types 133-136
exempted before DROP to preserve neighbor discovery (bt.rules)
[New] IPv6 trust system: cli_trust(), cli_trust_remove(), allow_hosts(),
deny_hosts() detect IPv6 addresses and route to ip6tables with ::/0;
bracket notation for advanced trust (e.g., d=22:s=[2001:db8::1])
with trust_protect_ipv6()/trust_restore_ipv6() escaping; .localaddrs6
prevents local IPv6 from being trust-listed (functions.apf)
[New] IPv6 fast load save/restore; ip6tables snapshot saved as .apf6.restore,
restored when USE_IPV6=1; falls through to full load when
ip6tables-restore missing or snapshot absent (apf)
[New] IPv6 sysctl hardening when USE_IPV6=1: disables accept_source_route,
accept_redirects, accept_ra on /conf/all/ and /conf/$IFACE_UNTRUSTED/;
disables forwarding when SYSCTL_ROUTE=1; route flush alongside IPv4;
all writes guarded behind proc file existence (sysctl.rules)
[New] IPv6 DNS filtering; resolv.conf nameservers routed to ipt4/ipt6
by address type (firewall)
[New] IPv6 PROHIBIT chain with icmp6-adm-prohibited reject target (firewall)
[New] IPv6 local address detection; .localaddrs6 generated when USE_IPV6=1
(firewall)
[New] IPv6 kernel module loading (ip6_tables, ip6table_filter,
nf_conntrack_ipv6) when USE_IPV6=1 (functions.apf)
[New] $IP6TS/$IP6TR variables for ip6tables-save/restore (internals.conf)
[New] nft backend detection; snapshot includes backend marker
(.apf.restore.backend) for safe restore across nft/legacy (apf)
[Change] Chain creation, MSS clamping, connection state, default policies,
helpers (FTP/SSH/Traceroute), logging, P2P/IDENT, cdports, RAB/
RABPSCAN, blocklist chains (PHP/DSHIELD/SDROP), ECN shame, dnet()
converted to ipt()/ipt4()/ipt6()/ipt_for_host() for dual-stack
(firewall, bt.rules, log.rules, functions.apf, cports.common)
[Change] flush() refactored with ipt4/ipt6 table loops; list()/refresh()
handle IPv6; cl_cports() clears ICMPv6 between VNET iterations
(functions.apf)
[Change] VNET IPv4-only limitation documented in conf.apf
-- Security Hardening --
[Fix] SYSCTL_ROUTE else branch removed; previously enabled ip_forward,
bootp_relay, and forwarding when routing disabled, turning server
into a router (sysctl.rules)
[Fix] GRE eval command injection: create_gretun() replaced eval with direct
$ip tunnel add using if/else for optional key flag (gretunnel.sh)
[Fix] Advanced trust syntax rejects unrecognized flow, direction, or protocol
fields; prevents malformed entries from injecting arbitrary iptables
arguments (functions.apf)
[Fix] Trust system input validation: cli_trust() and cli_trust_remove()
validate via valid_host(); allow_hosts()/deny_hosts() validate plain
IP entries before passing to iptables (functions.apf)
[Fix] Blocklist integrity: valid_ip_cidr() applied in all dlist_* download
functions including ecnshame; garbage from corrupted downloads rejected
at parse time (functions.apf)
[Fix] sed delimiter hardened to % in cli_trust_remove() and expirebans();
cli_trust_remove() pattern anchored with ^ and \b to prevent substring
matching (functions.apf)
[Fix] EG_DROP_CMD LOG rule now includes --cmd-owner filter; previously logged
ALL packets in DEG chain (cports.common)
[Fix] Predictable temp files replaced with mktemp: refresh(), list(), and all
7 download functions now use mktemp/mktemp -d (functions.apf)
[Fix] for-in-$(cat) glob expansion: allow_hosts(), deny_hosts(), all
dlist_*_hosts(), dnet(), expirebans(), refresh() converted to
while IFS= read -r (functions.apf)
[Fix] wget exit code checked in all 7 download functions; partial downloads
no longer processed (functions.apf)
[Fix] Unquoted variable expansion hardened in file operations (install.sh,
functions.apf)
[Change] DLIST download URLs updated from HTTP to HTTPS for all upstream
sources: cdn.rfxn.com, spamhaus.org, feeds.dshield.org (conf.apf,
.ca.def)
-- Bug Fixes --
[Fix] OUT_SANITY missing 4 TCP flag pairs vs IN_SANITY; added ALL FIN,URG,PSH,
ALL SYN,RST,ACK,FIN,URG, ALL ALL, ALL FIN to both IPv4 OUT_SANITY and
IPv6 OUT_SANITY6 (bt.rules)
[Fix] DNS INPUT rules now require ESTABLISHED,RELATED state; prevents spoofed
packets from being accepted on source port 53 (firewall)
[Fix] RESET chain catch-all DROP added for non-TCP packets (firewall)
[Fix] SSH helper: removed dead rule (mutually exclusive --syn + --state
ESTABLISHED,RELATED); removed spurious UDP rule (SSH is TCP-o
... (truncated — view full changelog on GitHub)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_DISPLAY, BAN_DURATION_DETAIL,
COUNTRY_FLAG, SOURCE_LOGS_SECTION_TEXT, REPUTATION_SECTION_TEXT);
add --sort, --limit, --24h/--7d/--30d, --active to SYNOPSIS modifiers;
update .TH date to 2026-03-17
[Fix] README: promote Periodic Reports to top-level section (§7.1→§8),
renumber sections 8-12→9-13, update TOC and cross-references;
fix template variable names in variable table
[Fix] usage_short() output line split to fit 80 columns
[Fix] conf.bfd EMAIL_LOGLINES comment: add "and event view"
[Fix] cron.daily sources internals.conf for correct LOCK_FILE in FHS packages
(lock.utime is at /var/lib/bfd/ not /usr/local/bfd/ after pkg transform)
[New] Pressure-to-response scoring model: exponential-decay replaces fixed
count thresholds; per-rule weights, trip points, and half-life via
pressure.conf; cross-service aggregate trip; old TRIG/TRIG_WINDOW
configs auto-migrate via importconf
[New] GeoIP country integration: full country names in alerts ("China (CN)"),
country pressure multipliers, IPv4+IPv6 lookup via ipcountry.dat and
ipcountry6.dat, pre-filled database (238 countries), cron.daily
auto-refresh, background download at install
[New] Firewall auto-detection: 8 backends (APF, CSF, firewalld, UFW,
nftables, iptables, route, custom); full IPv6 dual-stack detection
and banning with separate BAN_COMMAND_V6/UNBAN_COMMAND_V6
[New] Ban lifecycle: temporary bans with BAN_TTL auto-expiry; recidivism
escalation (none/linear/double with configurable cap and retry
backoff); distributed attack subnet banning (SUBNET_TRIG)
[New] Watch mode (-w/--watch): continuous daemon with configurable interval,
SIGHUP config reload, systemd unit and SysVinit init script, dead-PID
lock recovery; scan mode (--scan) for full-log processing with
--max-lines and --scan-timeout
[New] Multi-channel alerting: email (text/HTML/multipart with SMTP relay),
Slack (webhook and Bot API), Telegram, Discord; customizable {{VAR}}
template partials (21 templates) with custom.d/ overrides preserved
across upgrades; digest mode with configurable interval; severity
colors, country flags, reputation links; --test-alert for all channels
[New] Periodic threat reports (--report daily|weekly|monthly): multi-channel
delivery, ban breakdowns (temporary/escalated/permanent), top IPs with
country codes, per-service breakdown, trend comparison, styled HTML
tables, comma-formatted numbers; REPORT_ENABLED, REPORT_INTERVALS,
REPORT_CHANNELS, REPORT_EMAIL_ADDRESS, REPORT_EMAIL_SUBJECT,
REPORT_TOP_N config variables; cron-scheduled
[New] 57 service detection rules (37 new + 20 existing with 10 expanded):
fail2ban-compatible <HOST> patterns, dual IPv4/IPv6 boundary-aware
regex, per-rule IGNOREREGEX exclusion; sshd expanded from 3 to 12
patterns, dovecot 2 to 6 with ManageSieve
[New] Systemd journal reader: transparent fallback for 38 of 57 rules;
cursor-based tracking; LOG_SOURCE config (auto/file/journal); dynamic
service-to-journalctl filter registry
[New] Events CLI (-e/--events): active event dashboard with pressure scores,
per-IP investigation with log sample, CIDR subnet reports;
--sort=count|time|ip, --24h/--7d/--30d, --limit=N, --json/--csv
[New] Threat activity report (-a/--activity): aggregate summary with
dual-interval per-service breakdown, IP search; structured output
(--json/--csv) for bans, events, and activity
[New] CLI expanded from 4 to 21+ flags: health check (-c), verbose (-V),
status (-S), config (-C), rules (-R with --active filter), test and
test-pattern and test-alert, ban management (-b/-u/--flush), output
modifiers (--json/--csv/--sort/--limit)
[New] Shared libraries: tlog_lib v2.0.4 (incremental log reader),
elog_lib v1.0.4 (structured logging with JSON audit events),
alert_lib v1.0.5 (alert delivery and template engine),
pkg_lib v1.0.5 (packaging and config merge),
geoip_lib v1.0.4 (GeoIP metadata and CIDR download)
[New] Infrastructure: bfd.1 man page, bfd.bash-completion, importconf
rewritten with awk-based config merge and TRIG migration,
install.sh/uninstall.sh modernized via pkg_lib with custom paths
and service lifecycle management
[New] Security: safe_source() validates ownership and permissions before
sourcing; strict IP and email validation; rule name sanitization;
credential masking in show_config()
[New] RPM (el7/el8/el9/el10) and DEB (Debian 12, Ubuntu 18-24) packaging
with FHS layout, symlink farm, and config protection
[New] GitHub Actions CI (10-OS Docker matrix with lint) and tag-triggered
release workflow (RPM+DEB builds)
[New] Test suite: 1578 unit/integration tests, 86 UAT scenarios, 14
performance benchmarks across 3 scaling tiers with KPI tracking
[New] LOG_IDLE_SUPPRESS suppresses idle run-complete syslog messages;
FAIL_COUNT template variable shows actual failure count in alerts
-- Bug Fixes --
[Fix] Report: repeat-offender percentage silently zero when unique IP count
exceeds 999 — integer comparison on comma-formatted string failed;
preserve raw integers for arithmetic, format only for display export
[Fix] Security: eval injection replaced with safe template expansion;
show_config() whitelist; safe_source() on all sourced files; flock
on state file operations; mktemp replaces PID-based temp files;
credential sanitization in alert log excerpts; Slack token via
curl -K to prevent /proc/PID/cmdline exposure
[Fix] Watch mode: SIGTERM/SIGINT properly exit daemon; config reload
unsets vars before re-sourcing and re-registers journal filters;
validation uses return instead of exit to prevent daemon crash;
mkdir-based lock replaces flock with PID liveness check
[Fix] Scan mode: SCAN_TIMEOUT enforced for file-based rules; pressure
prune skipped in scan mode; timeout watchdog kills process group
[Fix] Email: base64-encode HTML MIME part (RFC 5321 998-char line limit);
TLS conditional on URL scheme; credentials optional for auth-free
relays; curl stderr captured for error reporting
[Fix] Upgrade path: importconf preserves pressure-country.conf, custom
rules, exclude.files, templates, tlog cursors, and cron schedule;
validates UNBAN_COMMAND; prevents stale modsec rule restoration;
merge_conf preserves file permissions
[Fix] Packaging: all 8 internal libraries included in RPM/DEB with
symlink farm; logrotate path updated to /var/log/bfd/bfd.log;
DEB postrm purges /var/log/bfd/; uninstall.sh cleans empty log dir
[Fix] Deep-legacy portability: all bare rm/cp/mv and hardcoded /usr/bin/*
paths converted to 'command' prefix for CentOS 6 / Ubuntu 12.04;
mawk /dev/stdin compatibility for _batch_ip_to_country
[Fix] install.sh: stop watch mode before upgrade; systemd unit
auto-detection; pipe fd hang fix (exec >/dev/null 2>&1 in background
subshell); sourced library permissions set to 640
[Fix] Report: template resolution (report-prefixed templates resolved
directly), trend comput
... (truncated — view full changelog on GitHub)