59. pepsi-stage-vks-confirm

Follow a key server’s address-verification link, and nothing else.

59.1. 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 is a separate program because it fetches URLs found in inbound mail. That is a dangerous primitive, and keeping 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: pepsi-stage-vks-confirm(1).

Everything it does not act on — nearly all mail — is advanced to NEXT_STAGE untouched, so placement is forgiving.

59.2. 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 (unless REQUIRE_AUTHENTICATION = no). 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 is https to that same host, on the default port. 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.

59.3. 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 pepsi-keys’s retry job from ever trying again. The same check decides the mail’s fate: it is discarded (DISCARD_CONFIRMED) only when at least one identity was verified, and delivered otherwise, so a confirmation that did not take effect can still be finished by hand.

Every decision and every fetch is logged at info on the vks-confirm target, which is the audit trail.

59.4. Configuration

[stage-<name>]: PROGRAM = pepsi-stage-vks-confirm, NEXT_STAGE (required — see above), VKS_HOST (defaulting to the host of [pepsi-keys] VKS_SERVER; pepsi-setup refuses a value naming any other host, since the stage would then ignore every confirmation), MAX_LINKS (3), DISCARD_CONFIRMED (yes: a confirmation mail that verified an identity is machine mail about an action Pepsi took) and REQUIRE_AUTHENTICATION (yes). See pepsi-stage-vks-confirm(1).

59.5. 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.

59.6. See also

Key management, pepsi-keys, pepsi-httpd, pepsi-keydisc, pepsi-stage-vks-confirm(1).