.. 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-stage-vks-confirm ======================= *Follow a key server's address-verification link, and nothing else.* Role ==== A key uploaded to a verifying key server is stored but **not findable by address** until somebody follows a link in a mail the server sends to that address. ``pepsi-stage-vks-confirm`` does that, on the inbound path, so key publication completes without a human reading every user's mailbox. It exists as a separate program because of what it is: something that fetches URLs found in inbound mail. That is a dangerous primitive, and putting it in its own small, unprivileged stage keeps it out of the programs that hold key material or parse hostile ciphertext, and makes its position in the pipeline visible in the configuration. Reference: :manpage:`pepsi-stage-vks-confirm(1)`. Everything it does not act on — nearly all mail — is advanced to ``NEXT_STAGE`` untouched, so placement is forgiving. What it refuses =============== Every one of these must hold before a byte is fetched. Failing any of them means the message is delivered instead, and the user can click the link themselves — which is what lets the conditions be this strict. * **The envelope sender's domain is exactly** ``VKS_HOST``, not a subdomain, not a suffix match, and not the ``From:`` header. * **SPF or DMARC passed.** A ``MAIL FROM`` is a claim; this is what turns it into evidence. ``dkim=pass`` deliberately does not count — it says a signature verified, not whose. * **Every link points at that same host.** A message naming any other host is refused entirely rather than having the foreign link skipped: a genuine verification mail links to the key server and nowhere else. The host is read after any userinfo, because ``https://keys.example.org@evil.test/`` is a request to ``evil.test``. * **A recipient is an address we are actually waiting on** — published, uploaded, not yet verified. An unsolicited verification mail is delivered, not clicked, which is also what stops a stranger making Pepsi issue requests by mailing a served address. * **At most** ``MAX_LINKS`` **per message**, fetched through the same hardened client key discovery uses: HTTPS only, certificates verified, the resolved address vetted against the SSRF guard, the body capped while streaming. Confirmation is verified, not assumed ===================================== A ``200`` from the link is the server's answer to a request, not a statement that the address is now published. The stage therefore asks the key server whether it serves *our* key for the address, and only a fingerprint match records the identity as verified — marking it on the ``200`` would stop :doc:`pepsi-keys`'s retry job from ever trying again. Every decision and every fetch is logged at ``info`` on the ``vks-confirm`` target, which is the audit trail. Configuration ============= ``[stage-]``: ``PROGRAM = pepsi-stage-vks-confirm``, ``NEXT_STAGE`` (**required** — see above), ``VKS_HOST`` (defaulting to the host of ``[pepsi-keys] VKS_SERVER``), ``MAX_LINKS`` (3), ``DISCARD_CONFIRMED`` (``yes``: the confirmation mail is machine mail about an action Pepsi took) and ``REQUIRE_AUTHENTICATION`` (``yes``). See :manpage:`pepsi-stage-vks-confirm(1)`. State ===== * **Inputs:** ``state.auth.spf`` / ``state.auth.dmarc``, the envelope sender and the message body. * **Outputs:** none. The stage never writes to ``state`` and never rewrites the message; what it changes is the key store's publication bookkeeping. See also ======== :doc:`../key-management`, :doc:`pepsi-keys`, :doc:`pepsi-httpd`, :doc:`pepsi-keydisc`, :manpage:`pepsi-stage-vks-confirm(1)`.