Security
Built as if every project were a stranger
Herald carries sign-in codes, receipts and the newsletters of products that trust it, so it was built the way a public service has to be: each project isolated from every other by the database, the keys and the mail servers, and nothing kept that it doesn’t need.
Isolation
Apart at every layer
A bug in one place doesn’t join two projects together: each layer enforces the line on its own.
The database
Row-level security is forced on every project’s table, for one project and one mode (live or test) at a time. A query outside that context sees nothing, and a test key can’t read live mail.
Foreign keys
Every reference names its project as well as its row, so a list or template can’t point across the line.
Checked by the catalog
A test reads Postgres’s own catalog and fails if any table, partition or function steps outside the rules.
The mail servers
KumoMTA learns a message’s project only from a token Herald signs, never from a header, and queues and pauses each project apart.
Tokens
Every link, unsubscribe and return path carries a token signed for its project. Another project’s token resolves nothing, and a click only ever goes where the sender’s link went.
Tested both ways
An isolation suite tries every way we know for one project to reach another’s mail, contacts, keys, webhooks and queues. Each must fail.
Keys
Sealed, and opened only for a reason
A key for each project
Message content, webhook secrets and inbound mail are sealed under the project’s own data key (AES-256-GCM, bound to the project, the mode and the purpose).
One holder of the master key
Only the keyring holds it: an internal service with no route to the internet. The API and the mail servers never see it.
DKIM stays home
Signing keys never leave the keyring. Herald sends it the parts to sign and gets back the signature.
Inbound, sealed on arrival
Mail to your routes is sealed to your project (HPKE) the moment the MX accepts it.
Opening a message
Console opens content only after the keyring checks the person again: their session, their place in the team, the permission to read mail, and a passkey in the last five minutes.
Sensitive projects
A project of sign-in codes can keep no content at all: once it’s sent, it’s gone.
People
Passkeys, and nothing to steal
No passwords
Console and Backoffice sign in with passkeys alone. Everyone keeps a second way back in, and anything that can’t be undone asks for the passkey again.
API keys
Shown once, stored as a keyed hash, scoped, and limited by domain, address and expiry if you choose.
The platform team
Backoffice needs a device certificate, an allowed address and a passkey, and binds each session to the device it was made on.
Support access
Herald’s staff can’t see your contacts or mail without opening support access, for an hour, with a reason you’ll read in your own audit log. They can never read content.
An audit log you can trust
Every change is written to a hash-chained log: none can be altered or removed unseen.
The edges
What comes in, and what goes out
Untrusted mail
Bounces, complaints, inbound mail, imports and images are read only in a sandbox with no network, no database and no keys. Images are re-encoded; SVG is refused.
Outbound requests
Webhooks, link checks and the image proxy leave only through an egress proxy that refuses private and internal addresses, so a URL can’t reach inside.
SMTP
Submission only over TLS. Bare line endings are refused (no SMTP smuggling), sizes are enforced, and the MX never relays.
The web
A Content-Security-Policy with a fresh nonce on every page, no third-party scripts, and HSTS everywhere.
Supply chain
No dependency younger than 14 days is installed, so a hijacked release has time to be caught.
Servers
Containers run read-only, as non-root, with no capabilities, each on its own networks; only the edge and the mail ports are published. Hosts talk over WireGuard, on encrypted disks.
Disclosure
Found a weakness?
Tell us through the report form: it reaches the platform team directly. Please give us a chance to fix it before you tell anyone else, and don’t access other people’s data while you look. Our contact details are also in security.txt (RFC 9116).