46. pepsi-stage-check-whitelist¶
Mark mail from trusted senders as non-spam.
46.1. Role¶
pepsi-stage-check-whitelist consults a named whitelist of trusted senders and,
when the message’s From: header matches, records state.spam = false so a
later pepsi-stage-anti-spam lets the message through without payment. Place
it ahead of the anti-spam stage. It only annotates the state and advances — it
never drops, bounces or pauses a message. Reference:
pepsi-stage-check-whitelist(1).
46.2. Features¶
Database-backed whitelists: the
pepsi.whitelisttable groups rows under awhitelist_name.WHITELIST_NAMEis a comma-separated list of the groups to consult, and an entry may be a template containing{localpart}or{login}, expanded per envelope recipient — soWHITELIST_NAME = correspondents, {localpart}/correspondentsconsults the operator’s shared list and each recipient’s own, the namespace pepsi-whitelist lets that user manage. Only a recipient at a local domain may name a whitelist (otherwise an attacker-chosen co-recipient would expand to any local user’s list), and{login}additionally needs the address to resolve to a passwd account, so place the stage after pepsi-stage-aliases when using it. Every applicable name is checked in one query.POSIX ERE matching: each
whitelist_regexis an extended POSIX regular expression matched case-insensitively (PostgreSQL’s~*operator, in a single round-trip) against the field the row’smatch_fieldnames:from(the default) — the addr-spec parsed out of theFrom:header, never the raw header value: otherwise a whitelisted address sitting in the display-name position would spoof a match. RFC 5322 comments and CFWS are removed, and aFrom:naming more than one mailbox matches nothing (the DKIM/DMARC verdict is computed for the first address, so matching a later one would answer two questions about two senders).list-id— the RFC 2919 identifier of the message’sList-Id:header. A mailing list’s postings carry arbitraryFrom:addresses, so nofrompattern can say “anything that came through this list”.
The expression itself is unanchored; anchor it with
^/$to match a whole address (which is what pepsi-stage-auto-whitelist writes).Optional conditions per row:
dkim_required→ matches only if theFrom:domain is authenticated:state.auth.dkim = pass, or an ARC pass that DMARC also confirms (state.auth.arc = passandstate.auth.dmarc = pass). ARC pass alone is only chain integrity (a one-hop chain can be self-minted over a forgedFrom:), so it does not satisfy the row by itself. The column defaults to true, aspepsi-whitelist adddoes.signature_required→ matches only ifstate.signature_verifiedistrue, which pepsi-stage-decrypt sets for a message whose end-to-end signature verified against a trusted key. Avalid-untrustedverdict deliberately does not set it, so such a row is a genuine “this correspondent signs, verifiably” gate rather than a “someone signed this” one. Place a decrypt stage before this one, or the row never matches.sealer_domain→ matches only if the message arrived with an ARC chain that validated and the named ADMD is among its sealers (state.auth.arc = passand the domain instate.auth.arc_sealers). This is what makes alist-idrow safe:List-Id:is unauthenticated text anyone can copy, and a valid ARC chain conveys no trust by itself (RFC 8617 §8.4) — naming the sealer is what turns the pair into evidence.
Non-destructive: on a match it merges
spam: falseand advances toNEXT_STAGE; on no match it advances unchanged. A message with noFrom:header, and one no configured whitelist name applies to, match nothing.
46.3. Configuration¶
[stage-<name>]: PROGRAM = pepsi-stage-check-whitelist, WHITELIST_NAME
(required — the comma-separated list of whitelist_name groups, literal or
templated), NEXT_STAGE (required — the stage always hands the message on),
and the shared locality options LOCAL_DOMAINS (defaulting to
[pepsi-ingress] ACCEPTED_DOMAINS), TARGETS and RECIPIENT_DELIMITER,
which are used only to expand the placeholders. See
pepsi-stage-check-whitelist(1).
46.4. State¶
Inputs:
state.auth.dkim/state.auth.arc/state.auth.dmarc(fordkim_requiredrows),state.auth.arc_sealers(forsealer_domainrows) andstate.signature_verified(forsignature_requiredrows; written by pepsi-stage-decrypt). TheFrom:header comes from thefrom_headercolumn;List-Id:is read from the header block, which is why this stage loads the headers (but never the body).Outputs: on a match,
spam: falsemerged intostate; otherwisestateis unchanged.
46.5. See also¶
pepsi-stage-anti-spam, pepsi-stage-arc, Supported Features, pepsi-stage-check-whitelist(1).