# Mounted into the sidecar as /etc/rspamd/local.d/milter_headers.conf. # # rspamd writes its verdict into two headers of every message it lets through: # the action it chose and the score with the symbols behind it. SelfPost's # inbound journal reads them (Inbound > Log shows them next to the message), # 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 - 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 # what a sender wrote: he could send his own verdict. So rspamd signs each # message - an HMAC-SHA256 of the message's queue id under a key SelfPost # generates and leaves in the directory it shares with this container - and # SelfPost reads a verdict only next to a signature it can recompute. The # signature travels on with the mail and is of no use to whoever sees it: it # fits that one queue id. # # rspamd 3.11.1, src/plugins/lua/milter_headers.lua: a custom routine is called # as function(task, common_meta) and returns error, headers to add, headers to # remove, metadata; a remove value of 0 removes every header of that name # (docs.rspamd.com/modules/milter_headers, "Custom routines"). # src/lua/lua_cryptobox.c, rspamd_lua_hash_create(): "sha256" with a key is # HMAC-SHA256 over EVP_sha256(), the key being the bytes given - here the hex # text. task:get_queue_id() is the Queue-Id the proxy took from the milter # macro "{i}" or "i" (src/libserver/milter.c, rspamd_milter_macro_http(); read # back in src/libserver/protocol.c), which Postfix sends at # end-of-message (Postfix 3.10.13, src/global/mail_params.h:3555, # DEF_MILT_EOD_MACROS "i"). use = ["x-spamd-result", "x-rspamd-action", "selfpost-signature"]; skip_local = false; skip_authenticated = false; custom { selfpost-signature = <