.. This file is part of PEPSI. Copyright (C) 2026 Pepsi contributors PEPSI is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. =============== pepsi-whitelist =============== *Manage the sender whitelist by hand.* Role ==== ``pepsi-whitelist`` manages the ``pepsi.whitelist`` table — the named sets of sender patterns that mark a message as trusted (non-spam). It connects through the shared ``[pepsi-postgres]`` section and reads the optional ``[pepsi-whitelist]`` section. The same table is read by :doc:`pepsi-stage-check-whitelist` and populated automatically by :doc:`pepsi-stage-auto-whitelist`; this tool lets an administrator add, list and remove entries directly, and lets **each user** seed a whitelist of their own from the mail they have already sent. It is **not** a stage. Reference: :manpage:`pepsi-whitelist(1)`. Unlike the other operator tools, which simply *become* the ``pepsi`` service account when started as ``root``, this one keeps the invoking identity and switches only its **effective** ids around each database call: ``import --user`` and ``--all-users`` need ``root`` in order to scan another user's mailbox under that user's credentials. Features ======== * **add** — insert an entry: a ``whitelist_name`` group and a ``whitelist_regex`` (a POSIX extended regular expression, stored verbatim — matched case-insensitively against the ``From:`` header by the check stage). ``--no-dkim-required`` clears the per-row ``dkim_required`` flag (set by default); ``--signature-required`` sets ``signature_required`` (clear by default). Idempotent: re-adding an existing ``(name, regex)`` pair is a no-op and leaves its flags unchanged. * **list** — list entries ordered by name then regex, optionally restricted to a single ``whitelist_name``, as a table or ``--json``. * **remove** — delete the entry identified by its exact ``whitelist_name`` and ``whitelist_regex``. * **import** — seed a whitelist from a mailbox: every message whose ``From:``/``Sender:`` is at a local domain is one the user **sent**, so its ``To:``/``Cc:``/``Bcc:`` addresses are people they correspond with and are added as entries. Received mail is ignored (whitelisting whoever wrote to you would defeat the purpose), as are recipients at a local domain. Reads mbox, Maildir (including its sub-folders), IMAP (``--imap``) and Dovecot's own formats (``--doveadm``); only header blocks are read, never bodies. ``--user`` / ``--all-users`` let root seed other accounts, each into its own namespace. * **import --auto-wildcard** — offer to replace the individual addresses at a domain with one ``*@domain`` entry once at least ``WILDCARD_THRESHOLD`` (10) distinct addresses were seen there. Proposals are confirmed one at a time on a terminal, all accepted with ``--yes``, and all **declined** when there is no terminal — an unattended run never widens a whitelist to a whole domain by itself. Public e-mail hosters (``HOSTERS_FILE``, ``/usr/share/pepsi/hosters.txt``) are never proposed, however many correspondents a user has there: "everyone at ``gmail.com``" is not a set of people the user knows, and it would hand every spammer with a free account a bypass of the payment gate. Their addresses are still imported individually. Whitelist namespaces ==================== A ``whitelist_name`` is either *global* (one segment, e.g. ``correspondents`` — the operator's) or *owned by a user*: ``/``, e.g. ``alice/correspondents``. Segments are letters, digits, ``_`` and ``-``; the separator is ``/`` because no POSIX-ish system allows it in a login name, whereas ``-``, ``_``, ``.`` and a trailing ``$`` all are. A dot is excluded from segments, which makes ``..`` — and therefore any relative path — unrepresentable. Ordinary users may read and write only their own namespace, keyed on the **real** uid's passwd login (never ``$USER`` or an argument). That includes ``list``: the shared whitelist records who the organisation corresponds with, and a program every user may run must not print it. ``root`` and the ``pepsi``/``pepsi-owner``/ ``pepsi-whitelist`` accounts may manage everything. For a per-user whitelist to *matter*, a check stage must consult it — see the ``{localpart}`` / ``{login}`` placeholders in :doc:`pepsi-stage-check-whitelist`'s ``WHITELIST_NAME``. Privilege model =============== Reaching the shared table needs a database identity ordinary users lack; reading a mailbox needs *theirs*. So ``pepsi-whitelist`` is installed **setuid** ``pepsi-whitelist`` (mode 4755) and immediately lowers its effective ids to the invoking user, raising them again only for the few milliseconds it talks to PostgreSQL (peer authentication keys off the effective uid). That account is deliberately not the ``pepsi`` service account: :doc:`pepsi-setup` grants its role ``SELECT``/``INSERT``/``DELETE`` on ``pepsi.whitelist`` **only**, so compromising a binary every local user can execute reaches neither the message queue nor the signing keys. The mailbox parsing — megabytes of attacker-controlled RFC 5322 — happens in :manpage:`pepsi-helper-mailbox-scan(1)`, spawned with ``setresuid`` setting the real, effective *and* saved ids to the target user. That process has no privileged id left in its credentials, so it cannot become ``pepsi-whitelist`` even in principle; it is the only ``pepsi-helper-*`` program with no setuid bit at all. Finally, ``-c``/``--config`` is refused for unprivileged callers, and the environment variables that steer configuration discovery (``HOME``, ``XDG_CONFIG_HOME``), ``${DATADIR}`` expansion (``PATH``) and libpq (``PG*``) are removed before the configuration is read. Unlike :doc:`pepsi-stage-auto-whitelist` (which escapes an address into a boundary-anchored pattern), ``add`` stores the ``whitelist_regex`` exactly as given, so the operator controls the matching directly. See also ======== :doc:`pepsi-stage-check-whitelist`, :doc:`pepsi-stage-auto-whitelist`, :doc:`pepsi-queue`, :doc:`../architecture`, :manpage:`pepsi-whitelist(1)`, :manpage:`pepsi-helper-mailbox-scan(1)`, :manpage:`pepsi.conf(5)`.