Patch Release Gating File and Shared-Memory Message Sources, with Fuzzy Sender Facts and Font-Aware PDF Text Extraction
FUZZY_CALLBACK gains ordering dependencies so the results exist by the time commands are built (#6179)fuzzy_hashes list with per-match objects from task:get_fuzzy_results() — rule, symbol, found and queried hashes, type, probability, flag and exactness — with a fuzzy_nested option to toggle the nested type, and adds the message size to the schema mapping so its type is declared rather than inferred per index (#6154, #6136)condition option was previously expected to return both the decision and the content for the LLM request, a contract that was non-obvious and only discoverable through debugging. The callback now returns only true/false plus an optional reason, and request content generation has moved into gpt_check itself, making custom conditions much simpler to write. Deployments whose condition returned request content must update it to return the boolean decision alone (#6159, #5857)allow_file_and_shm_inputs defaults shipped: The normal, controller and proxy worker configurations now set allow_file_and_shm_inputs = true explicitly, with a comment explaining what the option permits, that it applies to TCP listeners only (unix sockets always allow these inputs), and that a listener with it enabled must not be exposed to untrusted networks — authentication and encryption do not authorize the capability. No shipped default value changes in this release, so existing deployments keep working; the default becomes false in the next major release, so operators relying on file or shared-memory inputs over TCP should set the option deliberately now. Commented max_connections examples are also added to the controller and proxy (#6162)forbidden_ids replaces the default set: The v2 reply path checked the global default_forbidden_ids first and suppressed matching flags unconditionally, letting a per-key set only add further restrictions. That inverted the semantics: a key carrying its own forbidden_ids — including an explicitly empty one, which means "nothing is forbidden for this key" — is supposed to replace the global default, which exists only for keys with no ACL of their own. In production this meant a storage-wide forbidden_ids = [8] suppressed flag 8 for every client, including keys whose own list was empty, so that feed was not served at all. The per-key set alone is now checked whenever the session is encrypted and the key has one, with the global list as the fallbackno_share, so deployments running fuzzy checks on outbound or authenticated submission traffic sent their own users' IP addresses to public fuzzy servers. Extensions are now omitted entirely for authenticated tasks and sources in local networks — such addresses are noise in a reputation model and exactly the values that had to be filtered downstream anyway — and, as the code already claimed but did not enforce, for rules that talk to their storage in plain text, where the data would travel in the clear (#6179)max_words as a total cap that the provider passes through (the knob fasttext_embed already had), with subject words seeding the table first and body words appending up to the cap. The cap changes long-text vectors, so training and inference must agree on it (#6187)ipairs, which stops at the first hole in the table — one failed or unregistered provider in an early slot silently discarded every later provider's collected block, and downstream fusion consumers went dark with no diagnostic at all. The loop now covers the whole provider slot range including the trailing metatokens slot, so a missing provider yields a partial fused vector that dimension checks can actually see and report (#6186)@ selected the mailto: prefix, so <a href="user.name@example.com"> — where the first non-alphanumeric character is the dot in the local part — kept the http:// prefix and produced http://user.name@example.com with a userinfo component, firing URL_USER_PASSWORD (2.0 points) on legitimate messages; nodemailer emits exactly such quote-attribution links. Characters legal both in a hostname and in an email local part (., -, _, +) are now treated as ambiguous and scanning continues until a genuinely structural character decides, so javascript:/data: hrefs, relative paths and bare hostnames all keep their previous handlingget_urls_filtered() no longer drops flagless URLs: When called without an include list, the function sets its mask to "all flags", but the include test was a plain bitwise and against that mask — which a URL carrying no flags at all still fails, and URLs extracted from HTML parts carry no flags. The no-argument form, which asks for everything, silently returned nothing for HTML-only messages. The include filter now applies only when a list was actually given: rspamadm mime urls shows HTML URLs again, and URI_COUNT_ODD no longer runs its parity check on an undercount for exactly the multipart/alternative messages it targets (#6182, #5891)add_text_footer(), remove_attachments() and multipattern_text_replace() closed a fixed number of boundaries when the part walk returned from a nested part to a shallower one, rather than however many levels were actually left behind. As soon as a message nested deeper than the hardcoded count, the boundary stack desynchronised: closing delimiters landed in the wrong places, turning attachments into an unreachable epilogue in the footer case and leaking literal --boundary-- text into part bodies in the others — reproducible through rspamadm mime strip on a triply-nested message. All three now unwind to the boundary being re-entered, which also subsumes an earlier multipart/related special case, with unit tests over flat and one- to three-level nesting that fail on the unpatched code (#6173, #6178, #6170)<41> and a two-byte <0041> collided even though a CMap may declare both — the byte count is what places a code in a codespace, which the input framing already honoured but the tables then ignored, so a one-byte mapping could answer for a two-byte code or be overwritten by one. Both singles and ranges are now keyed by width as well (#6175)usecmap reference was treated as a marker and thrown away, so a CMap built as overrides on top of a base returned only the overrides — every code the base was meant to answer for silently vanished, and the surviving ones made the result look like real text. The base is a CMap resource that is not in the file and cannot be resolved, so such a CMap is now refused and the part left undecoded, where the computed-part guard can see it for what it is (#6175)charset=euc is far more likely to carry sloppy EUC-JP or EUC-KR than EUC-TW, so data that used to be left raw decoded cleanly and said something else. Both labels now resolve only where CED output is turned into a charset name, and nowhere else (#6175)rspamd_redis.null sentinel with lua_redis.is_null() (previously a missing key crashed inside the error-formatting code), sanitises non-positive TTLs that would have deleted the very database just read, reports why a hot-swap did not happen instead of a misleading file-not-found warning, loads databases asynchronously on worker startup, and compiles regexp maps read after the fork in the process that read them — previously a large pattern map served over HTTP without a cached copy stayed on PCRE for good, because its compilation queue entry lived in a process that never compiled it (#6161)LTRIM <key> 0 0, which retains list index 0 — the newest entry, since rows are prepended — so the most recent row always survived a reset; the list is now deleted outright, with a functional test covering the whole flow (#6185, #2910)asn:205640. Options are included in the haystack again, restoring the old FooTable behaviour for both the history and scan tables (#6169, #6168)File/Path/Shm/Shm-Offset/Shm-Length headers, their v3 metadata equivalents and the proxy's File query argument make rspamd open a path or map a shared memory object that the client names, and they were accepted on any transport — so any client able to reach a scanner, controller or proxy TCP port could have an arbitrary file readable by the rspamd user parsed and its content reported back. A new per-worker allow_file_and_shm_inputs boolean now gates the capability, derived solely from the transport the connection was accepted on: unix sockets always allow these inputs, since access is already controlled by the socket's filesystem permissions, while nothing the client sends takes part in the decision and no other credential — controller password, enable_password, secure_ip match, encrypted connection — unlocks it. Gating is applied before the named object is opened, mapped or statted, and two bypasses are closed: query arguments that the proxy turned into trusted upstream headers, and the fuzzy controller endpoints that build their own task. The inputs themselves are hardened — paths validated before any syscall, files snapshot with bounded reads instead of parsing a shared mapping the caller can truncate mid-parse, shared memory mapped as a page-aligned window rather than the whole object — the proxy strips client-supplied Shm headers as reserved hop-by-hop headers, the scanner's max_tasks now counts accepted body-pending connections, and the controller and proxy gain max_connections/max_connections_per_source admission limits enforced before any expensive allocation. encrypted_only also no longer exempts peers via a locality test that matched IPv6 link-local and site-local addresses, which had let any host on the segment skip the encryption requirement. The option defaults to true in this release with a prominent startup warning per TCP listener, and becomes false in the next major release (#6162)rspamadm pw emits by default) synchronously in the controller's event loop, and the password cache keys on the plaintext that succeeded — so a stream of distinct wrong passwords fell through to the KDF every time, and roughly 40 attempts per second were enough to saturate a single-process controller and stall every other request it serves. Failed attempts now feed a per-source leaky bucket checked before any password work, so a source over budget gets a 429 in well under a millisecond; a successful login clears the bucket, trusted sources (secure_ip, unix sockets) are never throttled, and throttled requests add no further penalty, so a shared source cannot be pushed into a permanent lockout. Configurable via max_auth_failures (default 10, 0 disables) and auth_failure_window (default 60 s); note the key is the real TCP peer, so behind a trusted reverse proxy all clients share one bucket. The redundant second KDF run when password and enable_password are configured to the identical hash is also skipped, halving the cost of every wrong password (#6162)This release closes a long-standing gap in the scan protocol — the File, Path and Shm message sources were accepted from any TCP client, letting anyone who could reach a scanner, controller or proxy port have arbitrary readable files parsed — behind a new transport-derived `allow_file_and_shm_inputs` option, and rate-limits the controller's expensive password verification per source. Fuzzy checks gain a privacy-conscious sender-facts extension over encrypted rules, riding on a wire format made forward compatible so new extension types no longer break older storages, while per-key fuzzy ACLs once again replace the global default as documented. PDF text extraction becomes font-aware, decoding through each font's /Encoding and /ToUnicode CMap instead of guessing over raw character codes, and the hyperscan cache finally works against replicated Redis. Recommended upgrade for all users; operators exposing scanner, controller or proxy ports beyond localhost should review the new `allow_file_and_shm_inputs` and connection-limit options now, ahead of the default flip in the next major release.