Vsevolod Stakhov [Wed, 14 Jan 2026 22:48:06 +0000 (22:48 +0000)]
[Fix] Propagate control request ids in replies
Ensure workers include cmd->id in control replies to avoid 'unknown request id 0' warnings. Update functional control tests and make RSPAMD_TMPDIR visible to child suites.
Vsevolod Stakhov [Wed, 14 Jan 2026 14:29:32 +0000 (14:29 +0000)]
[Feature] Route all hyperscan cache operations through Lua backend
- Route file backend through Lua for consistency with redis/http
- Add zstd compression support with magic byte detection for backward
compatibility (reads both .hs and .hs.zst files)
- Fix rspamd_util.stat() return value handling (returns err, stat tuple)
- Fix timer management for synchronous Lua callbacks to prevent early
termination of re_cache compilation
- Fix use-after-free in load path by pre-counting pending items
- Add priority queue for re_cache compilation (short lists first)
- Add ev_run() flush before blocking hyperscan compilations to ensure
busy notifications are sent
- Add hyperscan_notice_known() and hyperscan_get_platform_id() Lua APIs
Vsevolod Stakhov [Tue, 13 Jan 2026 21:52:13 +0000 (21:52 +0000)]
[Fix] Refactor control socket to use ID-based request/reply matching
Replace the serialization-based control command handling with an ID-based
approach using khash, mirroring the existing rspamd_srv_requests pattern.
Key changes:
- Add uint64_t id field to control command/reply structs
- Use khash for O(1) request lookup by ID instead of GHashTable
- Add rspamd_control_reply_handler() for centralized reply processing
- Add rspamd_control_pending_new/destroy/remove_all() API functions
- Add control_ev watcher to worker struct for reply monitoring
- Call rspamd_srv_pipe_cleanup() on worker shutdown to prevent leaks
- Handle ID collisions gracefully (warn and free old entry)
This fixes hash table iterator corruption crashes that occurred when
modifying the hash during iteration, and provides more robust concurrent
command handling.
Vsevolod Stakhov [Tue, 13 Jan 2026 14:19:53 +0000 (14:19 +0000)]
[Feature] Disable periodic recompile timer for file cache backend
The periodic recompile timer (default 60s) is only useful for shared
backends (Redis, HTTP, Lua) where another rspamd instance might have
compiled new hyperscan databases.
For file backend, recompilation is already triggered by:
- Config reload (forks new hs_helper process)
- Explicit RECOMPILE command (sent on map updates)
This eliminates unnecessary periodic checks for file-based deployments.
Vsevolod Stakhov [Tue, 13 Jan 2026 13:56:27 +0000 (13:56 +0000)]
[Fix] Fix re_cache hyperscan file tracking and buffer size
Two fixes for hyperscan cache file handling:
1. Increase hyperscan_cache_file.filename buffer from 64 to 80 bytes
to accommodate full filenames (64 hex hash + ".hs.unser" = 73 chars)
2. Add rspamd_hyperscan_notice_known() call in re_cache.c after loading
hyperscan databases. Without this, re_cache files weren't registered
as "known" and would be deleted by cleanup_maybe() on restart,
causing unnecessary recompilation.
Vsevolod Stakhov [Tue, 13 Jan 2026 11:02:09 +0000 (11:02 +0000)]
[Fix] Fix fd leaks and double-free in srv_pipe error handling
- Close attached_fd before freeing request data when sendmsg fails
- Fix double-free in rspamd_srv_pipe_ctx_destroy: items in send_queue
are also in the hash table, so only iterate hash to free
- Close attached_fd for unsent requests during shutdown
Vsevolod Stakhov [Tue, 13 Jan 2026 10:44:42 +0000 (10:44 +0000)]
[Fix] Reduce hyperscan_cache_file command from CONTROL_PATHLEN to 64 bytes
Send only the filename (hash.hs) instead of the full path in the
hyperscan cache notification. Main process reconstructs the full
path using cfg->hs_cache_dir.
This is the last CONTROL_PATHLEN field in rspamd_srv_command.
Vsevolod Stakhov [Tue, 13 Jan 2026 10:20:58 +0000 (10:20 +0000)]
[Fix] Refactor srv_pipe to use queue-based architecture with ID dispatch
Replace per-request ev_io watchers with a single watcher using khash
for ID-based reply matching. This fixes potential deadlocks when multiple
commands are queued rapidly (e.g., during hyperscan compilation).
Changes:
- Add rspamd_srv_pipe_ctx with single watcher, send queue, and ID hash
- Make srv_pipe non-blocking on both ends with proper EAGAIN handling
- Add EAGAIN handling to main process write path
- Remove cache_dir from hs_loaded commands (available from config)
Vsevolod Stakhov [Mon, 12 Jan 2026 16:25:30 +0000 (16:25 +0000)]
[Fix] Reduce control message size to prevent sendmsg crash
The rspamd_srv_command and rspamd_control_command structures grew too
large (~8KB) due to multiple CONTROL_PATHLEN fields in mp_loaded and
re_map_loaded, exceeding socket buffer limits and causing crashes in
sendmsg during worker startup.
Fix by:
- Removing redundant cache_dir fields (all processes know it from config)
- Using consistent name[64] for both mp_loaded and re_map_loaded
- Getting cache_dir from cfg->hs_cache_dir at receive time instead
Vsevolod Stakhov [Mon, 12 Jan 2026 12:56:57 +0000 (12:56 +0000)]
[Fix] Correct CSS duplicate property handling to use last declaration
Fix two bugs in CSS property handling that caused text to be incorrectly
marked as invisible:
1. Fixed isset() macro misuse in override_values() - was passing a bitmask
instead of a bit index, causing the override to never find matching values
2. Changed add_rule() to call override_values() instead of merge_values()
when duplicate properties with normal priority are encountered, ensuring
later CSS declarations properly override earlier ones per CSS spec
This fixes an issue where HTML emails with duplicate color declarations
(e.g., "color:#FFFFFF;color:#232333") would have text incorrectly filtered
as invisible, since only the first color was being used.
Added test case for duplicate color property handling.
Vsevolod Stakhov [Mon, 12 Jan 2026 12:18:33 +0000 (12:18 +0000)]
[Fix] Include content URLs in rspamadm mime urls output
Change get_urls(true) to get_urls_filtered() to include URLs
extracted from content (e.g., PDF attachments) in the output.
The get_urls() function excludes RSPAMD_URL_FLAG_CONTENT URLs
by default for backward compatibility, but get_urls_filtered()
with no arguments returns all URLs including content URLs.
Vsevolod Stakhov [Mon, 12 Jan 2026 12:17:32 +0000 (12:17 +0000)]
[Fix] Add defensive checks to PDF parser for malformed input
Add pcall wrappers and type checks throughout pdf.lua to handle
malformed PDFs from untrusted sources without crashing:
- Add nil checks for stream objects before accessing fields
- Wrap grammar matches in pcall to catch parsing errors
- Add type validation before ipairs calls on trie match results
- Wrap span extractions in pcall to handle invalid offsets
- Add defensive checks in processor functions (trailer, suspicious)
- Wrap URL creation in pcall for malformed URI strings
Errors are logged via debugm for diagnosis while allowing
processing to continue gracefully.
Vsevolod Stakhov [Mon, 12 Jan 2026 08:39:46 +0000 (08:39 +0000)]
[Fix] Prevent infinite loop in fuzzy_check config transform
When transforming max_score -> hits_limit for backward compatibility,
directly assigning UCL object references between fields can corrupt
the internal linked list pointers (next/prev become self-referential).
This caused an infinite loop in ucl_object_lua_push_array() when the
C code tried to push the config object to Lua via LL_FOREACH macro.
Fix by using tonumber() to extract the numeric value instead of
copying the UCL object reference.
Reported-by: User via GDB backtrace showing hang at lua_ucl.c:240 Fixes: 7fd47dad2f9 ("[Feature] Rename fuzzy_check max_score to hits_limit for clarity")
Vsevolod Stakhov [Sun, 11 Jan 2026 20:23:55 +0000 (20:23 +0000)]
[Fix] Use base name for OpenMetrics counter TYPE declarations
OpenMetrics specification requires counter metrics to have _total suffix
on the metric value, but HELP and TYPE declarations must use the base
name without the suffix.
Before: # TYPE rspamd_scanned_total counter
After: # TYPE rspamd_scanned counter
This fixes parser rejections due to name clashes when metrics scrapers
see _total in the TYPE line and append another _total.
Vsevolod Stakhov [Sun, 11 Jan 2026 18:08:02 +0000 (18:08 +0000)]
[Feature] Add HTTP content negotiation framework
Add content type negotiation based on Accept header for HTTP responses.
This allows clients like DataDog's OpenMetrics scraper to receive
responses with Content-Type matching their Accept header preferences.
- Add http_content_negotiation.c/h with Accept header parsing
- Support quality factors (q=) in Accept header
- Parse Accept-Encoding for gzip/zstd/deflate support
- Add rspamd_controller_send_openmetrics_negotiated()
- Update /metrics endpoint to negotiate Content-Type
- Fallback to text/plain for Prometheus 0.0.4 compatibility
Vsevolod Stakhov [Sun, 11 Jan 2026 11:38:22 +0000 (11:38 +0000)]
[Fix] Enable FALLBACK mode for RE multipatterns (stop words)
- Create pats array for all multipatterns, not just TLD
- Use rspamd_multipattern_build_acism() for proper RE fallback
- Add regex fallback path in lookup while HS is compiling
- Clean up mp->res in destructor for hyperscan path
This fixes stop words multipatterns which use RSPAMD_MULTIPATTERN_RE
to properly use FALLBACK mode instead of falling through to SYNC mode
and creating .hs files during config loading.
Vsevolod Stakhov [Sat, 10 Jan 2026 21:18:21 +0000 (21:18 +0000)]
[Feature] Compile small hyperscan databases in memory without file caching
For small pattern sets (< 100 patterns), compile hyperscan databases
synchronously in memory without saving to file or Redis cache.
These databases are shared with workers via fork() COW semantics.
Large pattern sets (like TLD with 10000+ patterns) continue to use
async compilation via hs_helper with Redis caching.
This eliminates unnecessary .hs files in /var/lib/rspamd for small
databases while maintaining the async path for expensive compilations.
Vsevolod Stakhov [Sat, 10 Jan 2026 17:19:32 +0000 (17:19 +0000)]
[Fix] Ensure stable re_cache class hashes independent of other classes
Previously, the global regexp index `i` was included in per-class hashes,
which caused class B's hash to change when class A got new regexps
(because indices shift). This made Redis caching ineffective as databases
were constantly being recompiled.
Now the global index is only included in the global hash, not in per-class
hashes, ensuring each class hash depends only on its own regexps.
Vsevolod Stakhov [Sat, 10 Jan 2026 15:58:40 +0000 (15:58 +0000)]
[Feature] Enhance hyperscan cache debug logging and correlation
- Add entity_name parameter to async cache API for better traceability
- Correlate cache requests with callbacks (show entity/key in both)
- Use rspamd_zhs prefix by default for compressed Redis data
- Switch to idiomatic lua_util.debugm for Lua debug logging
- Log Redis backend config (prefix, ttl, compression) on creation
[Fix] Properly terminate hs_helper during shutdown
Add RSPAMD_SRV_BUSY command to allow hs_helper to notify main process
when busy with long-running hyperscan compilation. Main skips heartbeat
checks while worker is busy and logs busy reason during shutdown.
Key fixes:
- Prevent notifications being sent after worker receives termination signal
- Propagate ev_break through rspamd_worker_set_busy to properly exit event loop
- Add shutdown monitor timer to log pending workers during termination
- Pass worker pointer to re_cache compile functions for termination checks
[Conf] Add configuration support for hs_helper worker
Add worker-hs_helper.conf and worker-hs_helper.inc config files that are
only installed when hyperscan support is enabled. The main rspamd.conf
uses try=true to gracefully handle missing config on non-hyperscan builds.
* [Feature] Add task registry for safe Lua task reference validation
* [Feature] Add text quality analysis for PDF garbage filtering
* [Feature] Implement basic PDF text extraction with UTF-16 detection
* [Feature] Add extra tables API for clickhouse plugin
* [Feature] Add confighelp documentation for RBL module
* [Feature] WebUI: add backend API interaction error log
* [Fix] Neural: by default include symbols with no flags
* [Fix] Symcache: make FINE propagation deterministic
* [Fix] URL: Prevent false positives from numeric IP regeneration in mailto URLs
* [Fix] Settings: Allow spaces in selector regexps
* [Fix] Prevent use-after-free in Redis callbacks after session cleanup
* [Fix] Lua 5.4 compatibility in clickhouse and elastic plugins
* [Fix] Use exact map lookup for DKIM key_table instead of glob
* [Fix] Handle connection errors with io_uring backend in HTTP client
* [Minor] Update public suffix list
[Fix] Use free() for hyperscan-allocated buffers in lua_hyperscan
hs_serialize_database() uses the standard C allocator, so the returned
buffer must be freed with free(), not g_free(). Mixing allocators
causes memory corruption when hiredis is configured to use glib.
[Fix] URL: Prevent false positives from numeric IP regeneration in mailto URLs
Fixes #5823 - Google Fonts URLs containing wght@0 parameter were incorrectly triggering URL_NUMERIC_IP and URL_BACKSLASH_PATH due to the @ symbol being interpreted as an email pattern and "0" being expanded to "0.0.0.0".
Also fix URL_BACKSLASH_PATH to actually check for backslashes instead of relying on the ambiguous obscured flag.
[Minor] Skip ACISM fallback build when file cache hit
In FALLBACK mode, try loading from file cache first. If successful,
skip building the ACISM trie to save memory. ACISM is only built on
cache miss (when async compilation is needed).
[Fix] Prevent hs_helper from deleting multipattern cache files
Add rspamd_hyperscan_is_file_known() API to check if a file is in the
known hyperscan files cache. Modify hs_helper cleanup to skip files
that are known (e.g., multipattern TLD cache files) even if they
aren't part of the re_cache.
[Fix] Fix ACISM fallback for multipattern async compilation
- Add per-pattern is_tld flag instead of checking multipattern-level flag
- Store pattern ID in ACISM wrapper struct for correct callback reporting
- Use ACISM-specific escaping for all patterns in fallback array
- Fix callback to use per-pattern TLD boundary check
- Set FALLBACK mode for URL scanner TLD trie
Add deferred hyperscan compilation for multipatterns (TLD patterns):
- Build ACISM fallback immediately during pre-fork (fast)
- Queue multipatterns for async HS compilation by hs_helper
- Workers hot-swap from ACISM to hyperscan when compilation completes
IPC additions:
- RSPAMD_SRV_MULTIPATTERN_LOADED: hs_helper → main
- RSPAMD_CONTROL_MULTIPATTERN_LOADED: main → workers
Bug fixes:
- Use per-pattern TLD flags instead of multipattern-level flags
- Add word boundary check in ACISM callback for TLD matching
[Feature] WebUI: add backend API interaction error log
Add an error log modal with a responsive table providing:
- tracking of the last 50 errors using a circular buffer
- an "unseen since last view" counter on the badge in bottom-right corner
- copy-to-clipboard support with execCommand fallback for HTTP connections
- color-coded error types
- automatic column hiding on smaller screens
[Minor] Add clear logging for multipattern compilation states
- Log when ACISM fallback trie is built
- Log when hyperscan cache hit/miss occurs
- Log when hot-swap to hyperscan completes
- Remove misleading "start compiling" message from url.c
[Fix] Add RSPAMD_MULTIPATTERN_TLD flag to search_trie_full creation
The TLD flag must be present at multipattern creation time for the
ACISM fallback to work. Without this flag, mp->pats array is not
created and ACISM patterns are not stored, causing fallback to fail
when Hyperscan cache is not available.
[Fix] Refactor multipattern to use per-multipattern TLD flag
This commit fixes the multipattern implementation to properly support
per-multipattern TLD flag instead of per-pattern flags.
Key changes:
- Remove acism_id_offset field - no longer needed since TLD is now
per-multipattern, not per-pattern
- Fix hyperscan TLD pattern suffix: use (?:[^a-zA-Z0-9]|$) instead
of (:?\b|$) because \b requires HS_FLAG_UCP which causes issues
- Initialize pats array in create functions when TLD flag is set
- Add TLD patterns to pats array at start of add_pattern_len for
ACISM fallback during hyperscan compilation
- Simplify ACISM callback - strnum IS the pattern ID for TLD patterns
For TLD multipatterns, the system now builds BOTH:
- ACISM patterns (for fallback during HS compilation or when unavailable)
- Hyperscan patterns (when available)
At lookup time: use Hyperscan if ready, fall back to ACISM otherwise.
[Fix] Fix multipattern cache file cleanup and ACISM fallback
- Register multipattern cache files with rspamd_hyperscan_notice_known()
to prevent hs_helper from cleaning them up during cache cleanup
- Fix ACISM pattern ID offset for mixed multipatterns (static + TLD):
when ACISM callback returns strnum, add acism_id_offset to get the
actual pattern ID that the URL scanner expects
[Feature] Add multipattern state machine for async compilation support
Add state machine (INIT/COMPILING/COMPILED/FALLBACK) to multipattern
for future async hyperscan compilation. Build ACISM fallback for TLD-only
patterns to allow matching while HS compiles. Mixed TLD/non-TLD patterns
use sync compile. Also update cache format to unified .hs extension.
[Feature] Unified hyperscan cache format for multipattern
Add C helper functions for serializing/deserializing hyperscan databases
with the unified format (magic, platform, CRC). Migrate multipattern from
raw .hsmp files to the unified .hs format compatible with re_cache.
- Add rspamd_hyperscan_serialize_with_header() and load_from_header()
- Update multipattern to use unified format with platform validation
- Fix CRC calculation in Lua bindings to match re_cache format
[Feature] Add Lua hyperscan compilation bindings and orchestration module
- Add rspamd_hyperscan Lua module with compile/serialize/deserialize/validate
- Create lua_hs_compile.lua orchestration module for unified compilation
- Support pluggable cache backends via lua_hs_cache integration
- Use unified file format with magic, platform info, CRC validation
This commit adds infrastructure for pluggable hyperscan cache storage
backends and FD-based shared memory distribution:
- Add platform ID function (rspamd_hyperscan_get_platform_id) for
platform-aware cache keys
- Create lua_hs_cache.lua with file, Redis, and HTTP backends
- Add FD-based loading APIs (rspamd_hyperscan_from_fd,
rspamd_hyperscan_create_shared_unser)
- Add fd_size field to control messages for FD passing
- Update worker to handle attached FDs in hyperscan notifications
- Add cache_backend configuration option to hs_helper
Vsevolod Stakhov [Wed, 31 Dec 2025 10:54:55 +0000 (10:54 +0000)]
[Feature] Add extra tables API for clickhouse plugin
Allow other plugins to dynamically register custom Clickhouse tables
via rspamd_plugins['clickhouse'].register_extra_table(). Supports
per-table schemas, row callbacks (single or multiple rows), and
independent retention settings.
Vsevolod Stakhov [Mon, 29 Dec 2025 22:28:40 +0000 (22:28 +0000)]
[Fix] Fix replxx build with LLVM 21+
- Simplify CMakeLists.txt to use CMAKE_CXX_STANDARD 20
- Replace std::unordered_map with std::map to avoid libc++ ABI issues
- Add operator< to UnicodeString for std::map compatibility
Vsevolod Stakhov [Sun, 28 Dec 2025 21:20:12 +0000 (21:20 +0000)]
[Fix] Avoid SDK headers in include path when package ROOT is specified
- Add NO_DEFAULT_PATH to FIND_PATH when PKG_ROOT is set to prevent
macOS SDK C headers from polluting include paths before libc++
- Fix typo: {RSPAMD_DEFAULT_INCLUDE_PATHS} -> ${...}
- Remove obsolete paths (/opt/csw, /sw), add /opt/homebrew for macOS
Vsevolod Stakhov [Sun, 28 Dec 2025 18:45:05 +0000 (18:45 +0000)]
[Feature] Rename fuzzy_check max_score to hits_limit for clarity
The option name max_score was confusing as it doesn't refer to the
symbol score but rather the number of fuzzy hash hits at which the
normalized score reaches ~1.0 (formula: tanh(e * hits / hits_limit)).
- Rename max_score -> hits_limit in fuzzy_check.c and default config
- Add backward compatibility: max_score is still accepted as an alias
- Add lua_cfg_transform to handle legacy configs (max_score overrides
hits_limit to ensure local.d overrides work correctly)
- Add explanatory comments in config and documentation
Vsevolod Stakhov [Sat, 27 Dec 2025 10:59:05 +0000 (10:59 +0000)]
[Fix] Add resilience to lua_cfg_transform
- Check :type() before indexing UCL objects to handle null values
- Wrap transform sections in pcall to prevent one bad config section
from breaking the entire configuration load
- Log errors with section name for easier debugging