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>
57 lines
2.7 KiB
YAML
57 lines
2.7 KiB
YAML
# Optional anti-spam sidecar for inbound relay (docs/plans/inbound-relay.md).
|
|
#
|
|
# SelfPost does not ship or start an anti-spam engine. This fragment brings up
|
|
# rspamd next to SelfPost and points the inbound smtpd milter at it. Merge it
|
|
# only when you want filtering on port 25:
|
|
#
|
|
# docker compose -f docker-compose.yml -f antispam/docker-compose.antispam.yml up -d
|
|
#
|
|
# Requires INBOUND_RELAY_ENABLE=true in .env. The milter sees the real client
|
|
# IP, HELO and PTR — unlike the upstream, which only sees SelfPost's address.
|
|
#
|
|
# The rspamd image is pinned to one build, 4.2.2-0 - "4.2", "4" and "latest"
|
|
# are rebuilt every night - and it is the build the comments in
|
|
# antispam/rspamd/ were read against (rspamd 4.2.2, rspamd-docker v4.2.2+0;
|
|
# 3.11.1-3 until 2.14.4, security audit 2026-10-17, S4); bump it
|
|
# deliberately, and read them again. Default milter action is fail-open (accept) so a down sidecar
|
|
# does not block backup-MX. Set INBOUND_ANTISPAM_MILTER_ACTION=tempfail to
|
|
# defer instead.
|
|
|
|
services:
|
|
selfpost:
|
|
environment:
|
|
INBOUND_RELAY_ENABLE: "${INBOUND_RELAY_ENABLE:-true}"
|
|
INBOUND_ANTISPAM_MILTER: "${INBOUND_ANTISPAM_MILTER:-inet:antispam:11332}"
|
|
INBOUND_ANTISPAM_MILTER_ACTION: "${INBOUND_ANTISPAM_MILTER_ACTION:-accept}"
|
|
depends_on:
|
|
- antispam
|
|
|
|
antispam:
|
|
# The milter: rspamd 4.2.2, conf/worker-proxy.inc, "milter = yes" for the
|
|
# rspamd_proxy worker, bound to *:11332 by antispam/rspamd/worker-proxy.inc
|
|
# while every other worker stays on loopback (RSPAMD_LOCAL_ADDR below);
|
|
# it runs as uid 11333 (rspamd-docker v4.2.2+0, Dockerfile, USER
|
|
# 11333:11333) and needs no capability: it starts unprivileged, binds
|
|
# ports above 1024 and runs nothing setuid (src/rspamd.c, detect_priv()).
|
|
image: rspamd/rspamd:4.2.2-0
|
|
restart: unless-stopped
|
|
environment:
|
|
# Workers on loopback; the proxy alone is put back on every address.
|
|
RSPAMD_LOCAL_ADDR: localhost
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
cap_drop:
|
|
- ALL
|
|
# A ceiling on processes and threads (security audit 2026-10-17, I3).
|
|
pids_limit: 256
|
|
# No host ports: only SelfPost talks to the milter on the compose network.
|
|
volumes:
|
|
# The verdict headers SelfPost's inbound journal reads.
|
|
- ./antispam/rspamd/milter_headers.conf:/etc/rspamd/local.d/milter_headers.conf:ro
|
|
# The allow and deny lists kept in the panel: the rules, and the files
|
|
# SelfPost writes them to.
|
|
- ./antispam/rspamd/multimap.conf:/etc/rspamd/local.d/multimap.conf:ro
|
|
# The proxy (the milter) on the compose network, nothing else.
|
|
- ./antispam/rspamd/worker-proxy.inc:/etc/rspamd/local.d/worker-proxy.inc:ro
|
|
- ./data/antispam:/etc/rspamd/selfpost:ro
|