ADR-0108: VayuMail Autoconfig Discovery & Public-Discovery Hardening
Status: Accepted
Date: 2026-07-04
Author: @johalputt
Relates to: ADR-0106 (VayuMail onboarding), ADR-0076 (VayuPGP keys / WKD)
Context
VayuMail-Mobile can onboard an account by scanning a QR or pasting a setup code (ADR-0106). Both carry the server coordinates in the payload. For a returning or manual setup, the operator otherwise had to type the IMAP/SMTP host and ports by hand. VayuPress already served a Mozilla/Thunderbird autoconfig XML for generic mail clients, but nothing exposed those coordinates in a shape the first-party app could consume cleanly, and the app could not set up an account from an email address alone.
Separately, the two products independently implement the WKD address hash
(z-base-32 of SHA-1 over the lowercased local-part). The server publishes a key
at /.well-known/openpgpkey/hu/<hash>; the app looks it up at the hash it
computes itself. A silent divergence between the two implementations would break
key discovery with no error on either side.
Finally, the unauthenticated .well-known discovery endpoints were unhardened:
the WKD handler scans the entire keystore on every request (a DoS amplifier),
sent no cache validators (clients refetched keys endlessly), and the app trusted
whatever a WKD lookup returned without checking the key matched the requested
address.
Decision
First-party mail autoconfig
- VayuPress serves
GET /.well-known/vayumail/autoconfig.json— a small, versioned (vayumail-autoconfig/1) JSON document carrying the mail server's IMAP/POP3/SMTP host, ports, TLS mode, username policy and declared auth mechanism. It contains only the public coordinates already shown on the Connect tab, never a secret; it 404s when VayuMail is inactive. - VayuMail-Mobile's
account.DiscoverAutoconfigfetches it (with anautoconfig.<domain>fallback) and returns a prefilled accountConfig. The manual account screen gains an "Auto-detect from email" button. The document shape is pinned byte-for-byte by a contract test duplicated in both repos, so server output and client parser can never silently drift.
WKD interop, locked
- An expanded known-answer vector table for the WKD address hash is kept identical in both repos' test suites, so any drift between the two independent z-base-32/SHA-1 implementations fails CI on whichever side moved.
Public-discovery hardening
- Rate limiting. A dedicated, generous per-IP limiter
(
PublicDiscoveryRateLimit, default 240 req / 10 min,DISCOVERY_RATE_LIMIToverride, trusted IPs bypass) guards the WKD and autoconfig routes; it answers a bare429 + Retry-Afterrather than the JSON API envelope. Bucket memory is bounded by the existing sweeper. - WKD caching. WKD responses now carry a strong
ETag(over the key bytes) plusCache-Control: public, max-age=3600and honourIf-None-Matchwith304. Correctness survives key rotation (ADR-0076): a changed key changes the ETag, so a revalidation returns the new key rather than a stale one. - Client-side address verification.
DiscoverWKDimports a fetched key only if it carries a User ID for the exact address requested, so a hostile or misconfigured endpoint cannot get a different-address key mis-associated with a contact. Redirect-following is disabled and the domain is validated as a clean hostname (SSRF, CWE-918).
Consequences
- A VayuPress mailbox can be configured from just an email address, over standards-based discovery, alongside QR and paste-code onboarding — no new Go dependency on either side.
- The WKD/autoconfig wire contracts are enforced by shared, byte-exact tests, so the two sovereign codebases stay interoperable as they evolve.
- The public discovery surface is DoS-resistant, cacheable, and the client no longer trusts a mismatched key — without weakening the strict CSP or the self-hosted, no-CDN model.