📋 Changelog

Release history and version notes for Bad IPs

This page tracks all notable changes to Bad IPs. Changes are organized by version with the most recent releases first.

Legend: ✨ Added | 🔄 Changed | 🐛 Fixed | ⚠️ Deprecated | 🔒 Security

v3.5.24 LATEST
June 6, 2026
🐛 Fixed
  • Config::Tiny Inline Comment Parsing: Fixed critical bug where inline comments in configuration files (e.g., escalation_ttl_1 = 600 # comment) were read as literal values, causing numeric operations to fail. Postfix plugin now correctly parses configuration values by placing comments on separate lines.
  • Initial Fetch Window: Updated Postfix plugin initial fetch from 300 seconds to 3600 seconds (1 hour) for better historical log coverage on startup.
🔄 Changed
  • Postfix Plugin Logging: Removed verbose debug output from pattern matching and IP extraction. Plugin now logs only essential information about blocking actions, reducing log clutter while maintaining visibility of actual blocks.
  • nft_blocker Input Validation: Enhanced worker thread with strict input validation to prevent malformed data from plugins from reaching nftables. Invalid IPs are skipped, invalid TTLs fall back to defaults.
🔒 Security
  • Plugin Data Validation: Added format validation for IP addresses (hex digits, dots, colons only) and TTL values (numeric only) in nft_blocker worker. Protects against injection or corruption from misbehaving plugins.
  • Queue Monitoring: Improved logging of queue sizes and item details for better visibility of data flowing through the blocking pipeline, aiding in troubleshooting and security monitoring.
v3.5.23
June 6, 2026
Added
  • Postfix Plugin for Distributed Attack Detection: New dedicated BadIPs::Plugins::Postfix plugin monitors Postfix logs for authentication failures and protocol violations with sophisticated username-based tracking. Detects distributed brute force attacks targeting the same account from multiple IP addresses.
  • Username-Based SASL Failure Tracking: Implements distributed attack detection by tracking failed SASL authentication attempts by username rather than per-IP. When 3+ attempts to the same username occur within a 10-minute window (from any IP), all attacking IPs are blocked with escalating TTL durations.
  • Protocol Violation Immediate Blocking: Instant per-IP blocking for non-SMTP commands, improper pipelining, and other protocol violations. Each violation increments an offense counter with escalating block durations (10 min → 1 hr → 6 hrs → 24 hrs → 7 days).
  • Escalating Block Duration System: Multi-level TTL escalation for both SASL attacks (per-username) and protocol violations (per-IP). Persistent attackers receive increasingly longer blocks: 1st offense 10 minutes, 5th+ offense 7 days. Offense counters reset after 30 days of good behavior.
  • TTL Parameter Support: Enhanced BadIPs.pm to accept custom per-IP TTL values in enqueue operations ($item->{ttl}), allowing plugins to specify different block durations based on attack severity and persistence.
🔄 Changed
  • Postfix Configuration Migration: Converted old detector-based Postfix configuration to new plugin-based format in 20-postfix.conf. Plugin-based approach enables threshold tracking, username-based analysis, and escalating penalties that were not possible with simple pattern matching.
🐛 Fixed
  • BadIPs.pm Typo: Fixed typo in database update comment from "PRetty" to "Pretty" (line 1371).
v3.5.22
June 5, 2026
🔄 Changed
  • Database Resilience Refactoring: Completely refactored both central_db_sync and pull_global_blocks worker threads for improved robustness. Extracted common reconnection logic into _attempt_db_reconnect() helper function with interruptible 1-second sleep loops to allow shutdown signal response. Replaced blocking sleep operations with 1-second loop patterns that check shutdown/reload flags, enabling responsive service stops and configuration reloads.
  • Periodic Health Checks: Added automatic database connection health verification every 60 seconds using $db->test_connection(). Stale connections are immediately detected and cleared ($dbh = undef) to force fresh reconnection on next operation.
  • Improved Queue Handling: Extracted queue operation logic into _try_enqueue_with_overflow_handling() helper function with optional non-blocking mode for safely adding IPs when queue depth exceeds 1000 items (prevents nft_blocker thread stall if database outage persists).
  • Separate Retry Strategies: Reconnection retries are now independent from operation retries. Failed operations include separate reconnection attempt loop before eventual requeue, rather than conflating the two concerns.
Added
  • Website Documentation: Comprehensive documentation of database resilience parameters in configuration guide with parameter table, trade-off explanations, and example calculations for exponential backoff behavior.
v3.5.21
June 6, 2026
🐛 Fixed
  • Database Connection Resilience: Implemented automatic database reconnection mechanism for both central_db_sync and pull_global_blocks worker threads. When a database operation fails, the system now attempts to reconnect up to 5 times (configurable) with exponential backoff before requeueing items. This prevents thread death during temporary database outages and ensures graceful degradation - local IP blocking continues uninterrupted even when the central database is temporarily unavailable.
  • Initial Connection Retry: Worker threads now retry the initial database connection instead of exiting immediately. This solves race conditions where bad_ips starts before the database service is ready.
Added
  • Configuration Parameters: New db_retry_interval (default: 3 seconds) and db_max_retries (default: 5) parameters allow tuning of reconnection behavior per deployment.
v3.5.20
June 2, 2026
🐛 Fixed
  • CRITICAL - nftables Installer Syntax Error: Fixed installer script that was failing with "syntax error, unexpected '}'" when creating nftables sets. The nft add set CLI syntax does not support inline comment parameters. Refactored to write persistent configuration file first, then load it with nft -f to create all infrastructure (table, sets, chain, rules) atomically. This approach ensures consistent behavior across all nftables versions.
v3.5.19
June 2, 2026
🐛 Fixed
  • Package Repository Integrity: Rebuilt version to bypass corrupted cached package in apt-cacher-ng proxy. Previous v3.5.18 package had file size mismatch in cache (50286 != 50280 bytes) preventing installation on some servers. New build creates fresh package with no cache history.
v3.5.18
May 30, 2026
🐛 Fixed
  • Critical - Thread Joining Bug: Fixed fatal error during shutdown where already-joined threads would be incorrectly flagged as stuck and attempt to detach, causing "Cannot detach a joined thread" error and service crash. Thread references are now properly nulled after successful join to prevent re-processing.
  • Reload Stability: Applied same thread tracking fix to reload handler to prevent similar errors during in-process reloads.
v3.5.17
May 30, 2026
🐛 Fixed
  • Critical - Queue Draining During Shutdown: Fixed race condition where worker threads would exit immediately upon shutdown signal before processing pending queue items. Threads now continue processing until queues are fully drained.
  • Queue Processing: Optimized batch collection in central_db_sync thread to process batches immediately when queue is draining, rather than waiting for timeout.
🔄 Changed
  • Shutdown Timeout: Reduced default graceful_shutdown_timeout from 300 seconds to 15 seconds. With queue draining fix, typical shutdown now completes in 5-15 seconds vs previous 300+ seconds.
v3.5.16
May 30, 2026
🐛 Fixed
  • Minor - build scripoto issue: Minor build scripto issue to accomodate new repo server
v3.5.15
May 30, 2026
🐛 Fixed
  • CRITICAL - Sudoers Syntax: Fixed package installation failure caused by invalid sudoers configuration. The postinst script was generating /etc/sudoers.d/bad_ips with command aliases that used wildcards in arguments (e.g., Cmnd_Alias NFT_BADIPS_ADD = /usr/sbin/nft add element inet badips * *), which sudoers does not support. This caused visudo -c validation to fail with "wildcards are not allowed in command arguments", preventing package configuration and blocking apt dist-upgrade. The sudoers rule has been simplified to use a direct permission grant with wildcards only at the end: badips ALL=(root) NOPASSWD: /usr/sbin/nft add element inet badips *, /usr/sbin/nft flush set inet badips *, ... This syntax is validated successfully and provides the same functionality.
v3.5.14
May 16, 2026
🐛 Fixed
  • Spamhaus Plugin URL Parsing: Fixed bug where comma-separated URLs in configuration (e.g., urls = url1, url2) were treated as a single concatenated URL instead of separate URLs. The plugin now correctly splits and fetches each URL individually.
v3.5.13
May 16, 2026
Added
  • nftables Persistence: Installer now writes persistent configuration to /etc/nftables.d/99-badips.nft so infrastructure survives reboots. Previously only created in-memory rules.
  • nftables Watchdog: Service now verifies nftables infrastructure during each heartbeat (every 60 seconds). If the table, sets, or chain are missing, the service logs clear instructions and exits so systemd can alert administrators.
🔄 Changed
  • Reduced Log Noise: "Fetched 0 new relevant log lines" messages changed from INFO to DEBUG level, reducing log spam when no attacks are occurring.
v3.5.12
May 16, 2026
🐛 Fixed
  • Historical Log Processing: Fixed critical bug where the GeneralLogMonitor plugin would fetch historical log lines on startup (e.g., 589 lines) but never process them to extract and block IPs. Only newly detected failures were being blocked. Now correctly calls _add_ips_to_queue() for initial fetched lines, ensuring all historical attacks are blocked on service startup.
v3.5.11
May 15, 2026
🐛 Fixed
  • Critical GeneralLogMonitor Plugin Bug: Fixed crash in _get_journal_lines() method at line 359 that prevented log monitoring from working. The bug caused shell syntax errors every 2 seconds when unit parameters weren't provided, resulting in "Fetched 0 new relevant log lines" despite dozens of failed SSH attempts. Now correctly falls back to configured journal units when called without explicit parameters.
  • Installer Idempotency: The installer script was not truly idempotent - running it multiple times created duplicate rules in the nftables chain. Now uses nft -j list ruleset with jq to check what infrastructure already exists before creating anything, preventing duplicates. Running the installer 3 times now results in exactly 6 rules instead of 18.
Added
  • jq Dependency: Added jq to package dependencies for idempotent installer verification logic.
v3.5.10
May 17, 2026
🐛 Fixed
  • nftables Infrastructure Persistence in Postinst: Removed systemctl reload nftables.service call from postinst that was wiping out infrastructure created by the installer script. The reload reads config files and has no file to load from, causing it to flush the kernel state created by nft add commands. Infrastructure now persists correctly after package installation.
v3.5.9
May 16, 2026
🐛 Fixed
  • nftables Infrastructure Persistence: Installer was creating in-memory rules with nft add commands that were lost when nftables service reloaded. Changed to write persistent configuration to /etc/nftables.d/99-badips.nft and load with nft -f, ensuring infrastructure survives service reloads and reboots.
v3.5.8
May 16, 2026
🐛 Fixed
  • GeneralLogMonitor Plugin Configuration Loading: Fixed plugin constructor to properly access configuration from plain hash structure instead of attempting to call Config::Tiny methods. Plugin now correctly loads log paths and journal units from configuration.
v3.5.7
May 16, 2026
🐛 Fixed
  • Plugin Error Logging: Improved error messages when plugin instantiation fails. The eval exception is now logged with full error details, making it easier to diagnose plugin initialization issues.
v3.5.6
May 16, 2026
Added
  • Missing Dependency: Added libfile-slurp-perl to package dependencies required by GeneralLogMonitor plugin.
🐛 Fixed
  • GeneralLogMonitor Plugin Initialization: Fixed three issues preventing plugin startup:
    • Variable naming inconsistency where plugin was storing log file paths in file_paths but accessing via undefined log_paths
    • Plugin instantiation code was not passing plugin_section parameter, causing plugins to fail loading their configuration
    • Plugin configuration values for log paths and journal units were comma-separated strings but plugin treated them as pre-parsed arrays, causing constructor to die when dereferencing
v3.5.5
May 16, 2026
Changed
  • Web Installer: Simplified curl|bash installation script by removing unnecessary nftables service state check. Installation now proceeds regardless of whether nftables.service is enabled/running, as the infrastructure installer script handles all setup automatically.
v3.5.4
May 15, 2026
Changed
  • nftables Infrastructure: Replaced static file dependency (/etc/nftables.d/99-badips.nft) with dedicated installer script and runtime validation. The systemd service no longer requires the configuration file to exist at startup.
Added
  • Infrastructure Installer Script: New /usr/local/sbin/bad_ips_installer.sh creates required nftables table, sets, chain, and rules. Idempotent and safe to run multiple times.
  • Startup Validation: BadIPs service now validates all required nftables infrastructure exists on startup before starting worker threads. Clear error messages guide users to run installer if anything is missing.
🐛 Fixed
  • Package Upgrade Issue: Service would fail to start after apt upgrade if /etc/nftables.d/99-badips.nft wasn't present. Now creates infrastructure automatically during installation and validates it at startup.
v3.5.3
May 16, 2026
🐛 Fixed
  • IP Blocking Queue Handler: Fixed critical bug in enqueue_ip callbacks that prevented any IPs from being added to nftables sets. The callbacks were enqueuing list arguments instead of hash references, causing worker threads to crash with "Can't use string as HASH ref" error. This affected both PublicBlocklistPlugins and regular Plugins, preventing all IP blocking from working.
v3.5.2
May 15, 2026
🐛 Fixed
  • Sudo NOPASSWD Configuration: Updated nftables command alias to allow nft add element operations for all static sets (never_block, never_block_v6, always_block, always_block_v6). Previously only allowed explicit set names, causing "command not allowed" errors when refreshing static IP lists.
  • Plugin Configuration Syntax: Fixed incorrect section header delimiters and plugin names:
    • Changed [Plugins::GeneralMonitor] to [Plugins:GeneralLogMonitor] (fixed double colon and incorrect name)
    • Changed all public blocklist plugin sections from [Plugins:*] to [PublicBlocklistPlugins:*] (Spamhaus, Feodotracker, Blocklist_de, DNSBL_Info, Malwaredomainlist)
v3.5.1
May 15, 2026
Added
  • UFW Detection: Installer now detects UFW (Uncomplicated Firewall) and displays compatibility information
  • Firewall Compatibility Documentation: Added comprehensive documentation explaining Bad IPs compatibility with UFW, firewalld, and custom nftables configurations
  • nftables Configuration Guide: New section in CONFIGURATION.md documenting the isolated table inet badips architecture
🐛 Fixed
  • Configuration Template Bug: Fixed nft_family_table in template from filter to badips to match actual table created since v3.2.1
v3.5.0
December 21, 2025
Added
  • Privilege Separation: Service now runs as non-root user (badips) with limited sudo privileges for enhanced security
  • PublicBlocklistPlugins Architecture: New extensible plugin system for integrating external IP blocklists
  • Spamhaus Plugin: Built-in plugin for Spamhaus DROP and EDROP lists
  • Expanded IPv6 Defaults: Added ff00::/8 (multicast), ::/128 (unspecified), and 2001:db8::/32 (documentation) to default never-block list
  • Multi-version Apt Repository: Repository now indexes all package versions for easy upgrades/downgrades
  • Separate IPv4/IPv6 Prompts: Install script now clearly separates IPv4 and IPv6 configuration with helpful descriptions
  • Configuration Documentation: Added comprehensive PublicBlocklistPlugins developer guide to website
🔄 Changed
  • Simplified Configuration: Removed hunter/gatherer mode distinction - now uses single unified auto_mode configuration
  • Single Configuration Template: Replaced separate hunter/gatherer templates with single badips.conf.template
  • Default Database User: Changed from bad_ips_hunter to bad_ips for consistency
  • Install Script UX: Improved prompts to clearly separate IPv4 and IPv6 network configuration
  • Documentation Rewrite: Complete overhaul of CONFIGURATION.md to reflect simplified architecture
🐛 Fixed
  • Database Config Permissions: database.conf now created with 640 permissions instead of 600 to allow badips user read access
  • Config File Ownership: All config files in /usr/local/etc/badips.d/ now properly set to root:badips ownership
  • Install Script Input Sanitization: Fixed newline handling in CIDR inputs that could corrupt INI file format
  • Config Parsing: Fixed awk patterns to prevent matching IPv6 parameters when reading IPv4 settings
  • Directory Permissions: Install script now properly sets permissions on badips.d directory during creation
⚠️ Deprecated
  • public_blocklist_urls - Replaced by [PublicBlocklistPlugins:Name] configuration sections
  • public_blocklist_refresh - Replaced by per-plugin fetch_interval parameter
  • Hunter/Gatherer mode configuration - Unified into single auto_mode setting
🔒 Security
  • Non-root Execution: Service runs as dedicated badips user instead of root
  • Limited Sudo Access: Sudoers rules restrict nftables operations to inet badips table only
  • Config File Security: Proper group permissions allow service user to read configs without requiring root
  • Password Protection: Database credentials in database.conf secured with 640 permissions
  • Supplementary Groups: Service user added to systemd-journal and adm groups for log access without elevated privileges