Security audit 2026-10-17, finding S4 (low), stage 4. The sidecar ran
rspamd 3.11.1 (December 2024) with default capabilities; upstream is 4.2.2
(2026-10-10), and the line between fixed a heap overflow in
util.lower_utf8, a use-after-free on reload and parser crashes - rspamd
reads every byte a stranger sends to port 25.
A read-only reading of rspamd 3.11.1 and 4.2.2 and of rspamd-docker
v3.11.1+3 and v4.2.2+0, side by side (sources in dev/upstream), found every
function SelfPost relies on unchanged: milter_headers (skip_wanted, the
custom routine, remove 0, create_specific_keyed, get_queue_id from {i}),
the X-Spamd-Result text, multimap (from/smtp, ip, the reject pre-result,
file-map rereading), the image's uid 11333 and CMD. It also found that the
image binds the normal worker (which takes client-named file and shm
inputs) and the controller (default password "q1") on every address,
reachable from any container on the compose network - so:
- image rspamd/rspamd:4.2.2-0 (the one build of 4.2.2; the others float);
- cap_drop: [ALL], no-new-privileges (rspamd starts unprivileged, ports
above 1024, nothing setuid);
- RSPAMD_LOCAL_ADDR=localhost keeps every worker on loopback, and a new
antispam/rspamd/worker-proxy.inc puts the proxy alone on *:11332. rspamd
expands its template syntax in comments too - the first version of that
comment quoted it and the config failed to load; it quotes none now.
Checked in a throwaway container on the test server with the three files
and the fragment's options (dev/server/rspamd-try.sh): proxy on any
address, 11333/11334 on loopback; from a private address a sender's own
verdict headers removed and rspamd's added; X-SelfPost-Filter equal to
openssl's HMAC-SHA256 of the queue id; a denied address refused with the
deny list's message; healthy. The e2e has no rspamd; the live sidecar is
checked at the rollout. Citations re-pointed to 4.2.2.
Docs: guide § Inbound relay (the third file; copy antispam/ again);
CHANGELOG; plan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
94 lines
4.1 KiB
Plaintext
94 lines
4.1 KiB
Plaintext
# Mounted into the sidecar as /etc/rspamd/local.d/multimap.conf.
|
|
#
|
|
# The allow and deny lists kept in the panel (Inbound > Filter lists). SelfPost
|
|
# writes them as six files under data/antispam/, which the fragment mounts at
|
|
# /etc/rspamd/selfpost; rspamd rereads a file when it changes, no restart.
|
|
# (rspamd 4.2.2 (as 3.11.1), src/libserver/maps/map.c: a file map is watched with libev's
|
|
# ev_stat - inotify where the kernel has it, else a stat poll every
|
|
# map_watch_interval x map_file_watch_multiplier, 5min x 0.1, conf/options.inc;
|
|
# docs.rspamd.com/configuration/maps asks for an atomic rename, which
|
|
# internal/inbound writes with.)
|
|
#
|
|
# Deny wins over allow, whichever entries match. A denied sender is refused
|
|
# whatever else is found (the rule's action is a pre-result, below). An
|
|
# allowed one is not accepted outright - rspamd would then let whichever of
|
|
# the two rules ran first decide - but given a weight no spam score
|
|
# outweighs, which a refusal by the deny list still overrides. (rspamd
|
|
# 4.2.2 (as 3.11.1), src/plugins/lua/multimap.lua: every rule is registered as a
|
|
# "normal" symbol, one with an action with the "passthrough" augmentation;
|
|
# no "prefilter" setting is read. Measured with this file in a throwaway
|
|
# rspamd/rspamd:3.11.1-3, 2026-10-15: every mix of an allow and a deny entry
|
|
# matching one message - address, domain, IP - came out reject, and a
|
|
# changed map file was in use about ten seconds later.)
|
|
#
|
|
# A sender rule looks at the envelope sender only, extract_from = "smtp":
|
|
# by default multimap.lua takes the header From: when the envelope sender is
|
|
# empty (the "from" callback, has_from('smtp') then has_from('mime')), so a
|
|
# bounce would be judged by an address the panel never counts it under
|
|
# (assumptions C-R4). Tried on 2026-10-14 in rspamd/rspamd:3.11.1-3: a
|
|
# bounce whose From: domain is on the deny list was refused by default and
|
|
# is not with this setting; a sender on the list is refused either way.
|
|
#
|
|
# A domain entry is that domain, not its subdomains. (rspamd 4.2.2 (as 3.11.1),
|
|
# src/libserver/maps/map_helpers.c: a hash map is looked up by the whole key,
|
|
# case-insensitively - rspamd_icase_hash, rspamd_lc_cmp; "email:domain" gives
|
|
# the address's domain part, src/plugins/lua/multimap.lua, apply_addr_filter().)
|
|
#
|
|
# A refusal is rspamd's "pre-result": multimap.lua calls task:set_pre_result()
|
|
# with the rule's action and message, and that action stands whatever the score
|
|
# (docs.rspamd.com/modules/multimap, "action"). The message reaches the client
|
|
# as "554 5.7.1 <message>": src/libserver/protocol.c, rspamd_scan_result_ucl(),
|
|
# puts it into messages.smtp_message, src/libserver/milter.c,
|
|
# rspamd_milter_send_task_results(), replies with it.
|
|
#
|
|
# SelfPost counts how often an entry decided a message by what these rules
|
|
# leave behind: the deny message below and the names of the allow symbols.
|
|
# Change either and Inbound > Filter lists shows "never" for every entry.
|
|
|
|
SELFPOST_DENY_IP {
|
|
type = "ip";
|
|
map = "/etc/rspamd/selfpost/deny_ip.map";
|
|
action = "reject";
|
|
message = "Refused by this server's deny list";
|
|
description = "Client address on the deny list";
|
|
}
|
|
SELFPOST_DENY_FROM {
|
|
type = "from";
|
|
extract_from = "smtp";
|
|
map = "/etc/rspamd/selfpost/deny_from.map";
|
|
action = "reject";
|
|
message = "Refused by this server's deny list";
|
|
description = "Sender address on the deny list";
|
|
}
|
|
SELFPOST_DENY_DOMAIN {
|
|
type = "from";
|
|
extract_from = "smtp";
|
|
filter = "email:domain";
|
|
map = "/etc/rspamd/selfpost/deny_domain.map";
|
|
action = "reject";
|
|
message = "Refused by this server's deny list";
|
|
description = "Sender domain on the deny list";
|
|
}
|
|
|
|
SELFPOST_ALLOW_IP {
|
|
type = "ip";
|
|
map = "/etc/rspamd/selfpost/allow_ip.map";
|
|
score = -100.0;
|
|
description = "Client address on the allow list";
|
|
}
|
|
SELFPOST_ALLOW_FROM {
|
|
type = "from";
|
|
extract_from = "smtp";
|
|
map = "/etc/rspamd/selfpost/allow_from.map";
|
|
score = -100.0;
|
|
description = "Sender address on the allow list";
|
|
}
|
|
SELFPOST_ALLOW_DOMAIN {
|
|
type = "from";
|
|
extract_from = "smtp";
|
|
filter = "email:domain";
|
|
map = "/etc/rspamd/selfpost/allow_domain.map";
|
|
score = -100.0;
|
|
description = "Sender domain on the allow list";
|
|
}
|