diff --git a/CHANGELOG.md b/CHANGELOG.md index e9e880d..499f002 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,33 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version ## [Unreleased] +### Security + +- **A sender on a private address could have his own spam verdict signed.** + rspamd's header module by default leaves a message's verdict headers + alone - removes none, adds none - when the client counts as local (a + private range, which every client has when Docker's proxy relays port 25) + or authenticated, while the routine that signs a verdict for SelfPost runs + for every message. Such a sender's `X-Spamd-Result` and `X-Rspamd-Action` + then reached Inbound > Log as rspamd's. `milter_headers.conf` now turns + both exceptions off; tried against rspamd 3.11.1 before and after + (audit 2026-10-12, stage 2c; assumptions C-R7). Copy the new + `antispam/rspamd/` files next to your compose fragment. + +### Fixed + +- **A bounce is judged by the filter lists as the panel counts it.** + rspamd's sender rules took a bounce's `From:` header when the envelope + sender was empty, so a bounce could be refused or allowed by an entry the + Filter lists page never showed as used. They now look at the envelope + sender only (assumptions C-R4). + +### Changed + +- **The rspamd image is pinned to one build**, `rspamd/rspamd:3.11.1-3`: + `3.11` and `3.11.1` move with every rebuild (assumptions C-R12). The rules + lose their `prefilter = true` lines, which rspamd 3.11.1 does not read. + ## [2.5.1] - 2026-10-14 Stage 2b of the 2026-10-12 audit: the log tailer and what the panel shows of diff --git a/deploy/antispam/docker-compose.antispam.yml b/deploy/antispam/docker-compose.antispam.yml index 753b0ea..4cb5f54 100644 --- a/deploy/antispam/docker-compose.antispam.yml +++ b/deploy/antispam/docker-compose.antispam.yml @@ -9,8 +9,10 @@ # 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. # -# Pin the rspamd tag the same way the SelfPost image is pinned; bump it -# deliberately. Default milter action is fail-open (accept) so a down sidecar +# The rspamd image is pinned to one build, 3.11.1-3 - "3.11" and "3.11.1" +# move with each rebuild (Docker Hub rspamd/rspamd tags 3.11.1-0 ... -3) - +# and it is the build the comments in antispam/rspamd/ were read against; +# 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. @@ -28,7 +30,7 @@ services: # rspamd_proxy worker, which the image binds to *:11332 (rspamd-docker # v3.11.1+3, patches/rspamd-docker-config.diff), and runs as uid 11333 # (its Dockerfile, USER 11333:11333). - image: rspamd/rspamd:3.11 + image: rspamd/rspamd:3.11.1-3 restart: unless-stopped # No host ports: only SelfPost talks to the milter on the compose network. volumes: diff --git a/deploy/antispam/rspamd/milter_headers.conf b/deploy/antispam/rspamd/milter_headers.conf index 018a376..d0e72e1 100644 --- a/deploy/antispam/rspamd/milter_headers.conf +++ b/deploy/antispam/rspamd/milter_headers.conf @@ -6,8 +6,24 @@ # and the upstream mailbox server receives them with the mail. # # rspamd removes headers of these names that a sender wrote himself before it -# adds its own. Without this file the journal still lists accepted mail, with -# no verdict next to it. +# adds its own - for every client, because of the two lines below. Without +# them it would do neither for a client it counts as local or one that +# authenticated: rspamd 3.11.1, src/plugins/lua/milter_headers.lua, +# skip_wanted() - skip_local and skip_authenticated are on by default, and +# "local" is loopback, a unix socket, or an address in local_addrs, the +# private ranges (src/lua/lua_ip.c, lua_ip_is_local(); conf/options.inc). The +# sender's own verdict would then pass untouched while the signature below, +# a custom routine that skip_wanted() never consults, signed it (assumptions +# C-R7). A private client address is ordinary here: a LAN sender, or every +# sender when Docker's proxy relays port 25 from the bridge gateway. +# Tried on 2026-10-14 in a throwaway rspamd/rspamd:3.11.1-3 (rspamc --ucl, a +# message carrying its own X-Spamd-Result and X-Rspamd-Action): without the +# two lines, from 10.0.0.5 or as an authenticated user, the milter reply +# removed neither header, added neither, and added the signature; with +# them, both removed and rspamd's own added, as from a public address. +# +# Without this file the journal still lists accepted mail, with no verdict +# next to it. # # The third header is the proof that the other two are rspamd's. The filter is # attached so that mail still flows when it is down, and then nothing removes @@ -30,6 +46,8 @@ # end-of-message (Postfix 3.7.11, src/global/mail_params.h, DEF_MILT_EOD_MACROS # "i"). use = ["x-spamd-result", "x-rspamd-action", "selfpost-signature"]; +skip_local = false; +skip_authenticated = false; custom { selfpost-signature = <