antispam: rspamd 4.2.2-0, no capabilities, only the milter on the network (audit S4)
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>
This commit is contained in:
@@ -130,7 +130,7 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
|
||||
rspamd removes a sender's own copies of the three headers and writes and
|
||||
signs its own for every message it lets through: `skip_local` and
|
||||
`skip_authenticated` are off in that file, so this holds whatever the
|
||||
sender's address (rspamd 3.11.1, `src/plugins/lua/milter_headers.lua`,
|
||||
sender's address (rspamd 4.2.2 (as 3.11.1), `src/plugins/lua/milter_headers.lua`,
|
||||
`skip_wanted()`).
|
||||
With the filter down - it is attached fail-open - a sender's own verdict
|
||||
headers carry no signature and the row is that of mail nobody judged.
|
||||
@@ -171,7 +171,7 @@ smtp/inet` removes Debian's stock smtpd). Outbound delivery still uses the
|
||||
allowed IP with a denied address were all `reject` (assumptions
|
||||
D-arch:121). A sender entry is matched against
|
||||
the envelope sender only (`extract_from = "smtp"`; by default rspamd takes
|
||||
a bounce's `From:` header instead - rspamd 3.11.1,
|
||||
a bounce's `From:` header instead - rspamd 4.2.2 (as 3.11.1),
|
||||
`src/plugins/lua/multimap.lua`, the `from` callback).
|
||||
How often an entry decided a message is not stored: `Lists.Usage` reads it
|
||||
off the journal on each view of Filter lists - the rows a list decided (the
|
||||
|
||||
@@ -1300,6 +1300,13 @@ rereads the list files by itself and reads the key anew for every message. A
|
||||
restart is needed only for a change of its own configuration - the files of
|
||||
`antispam/rspamd/`.
|
||||
|
||||
The third file there, [worker-proxy.inc](../deploy/antispam/rspamd/worker-proxy.inc),
|
||||
keeps rspamd's scanner and its controller - whose default password is public -
|
||||
on the sidecar's own loopback and puts only the milter on the compose network;
|
||||
the fragment runs rspamd with no Linux capability. Upgrading SelfPost to
|
||||
`2.14.4` brings rspamd 4.2.2 with it: copy `antispam/` again, then
|
||||
`docker compose ... up -d` recreates the sidecar.
|
||||
|
||||
The same directory holds
|
||||
[multimap.conf](../deploy/antispam/rspamd/multimap.conf), the rules for the
|
||||
**allow and deny lists** kept in the panel. SelfPost does not apply the lists
|
||||
|
||||
@@ -817,10 +817,20 @@ the argument.
|
||||
**Sonnet**, 1.5 h. *Done 2026-10-17:*
|
||||
TestImportKeyRefusesAnOversizedKey and the import cases (a key under
|
||||
1024 bits the parser already refuses).
|
||||
- [ ] **S4.** rspamd pin → `4.2.x` after reading `milter_headers`/`multimap`
|
||||
- [x] **S4.** rspamd pin → `4.2.x` after reading `milter_headers`/`multimap`
|
||||
against the 4.x docs and one e2e run (`inbound_upstream_test`,
|
||||
`antispam_test`); `cap_drop: [ALL]`, `no-new-privileges`, `pids_limit` on
|
||||
the sidecar. — **Opus**, 1.5 h.
|
||||
the sidecar. — **Opus**, 1.5 h. *Done 2026-10-17:* rspamd
|
||||
4.2.2 and 3.11.1 read side by side (the functions SelfPost relies on are
|
||||
unchanged); `rspamd/rspamd:4.2.2-0` with `cap_drop: [ALL]`,
|
||||
`no-new-privileges`, and - recommended by that reading - the normal
|
||||
worker and the controller (default password "q1") kept on loopback, the
|
||||
proxy alone on the network (`worker-proxy.inc`, `RSPAMD_LOCAL_ADDR`). The
|
||||
e2e has no rspamd, so a throwaway container on the test server with our
|
||||
three files checked it: the listeners, a sender's own verdict headers
|
||||
removed from a private address, rspamd's added, the signature equal to
|
||||
openssl's HMAC, the deny list's 554 message, healthy. The live sidecar at
|
||||
the rollout.
|
||||
|
||||
### Stage 5 — supply chain and currency (~2.5 h)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user