Repository navigation
threat model
github-actions[bot] edited this page Nov 7, 2026
·
1 revision
What unoMail protects, who can reach it and how, the risks it accepts, and what an operator
should check and do. The controls themselves are summarised in the
security model; configuration is in server/src/config.rs. To report a
vulnerability, see the security policy.
| Asset | Where it lives |
|---|---|
| Message contents and attachments | blob store (<data>/blobs or S3), optionally sealed (blob.key) |
| Message metadata, search index, contacts, calendars | SQLite database <data>/unomail.db (plaintext) |
| Account credentials | Argon2id hashes, app-password hashes, TOTP secrets (sealed), passkey public keys |
| Browser sessions | random tokens; only SHA-256 hashes stored; HttpOnly SameSite=Strict cookies |
| Server secrets |
secret.key (seals third-party credentials, TOTP and S/MIME keys), blob.key, vapid.key, DKIM and TLS private keys (database) |
| Outbound reputation | DKIM keys, the server's IP addresses, the delivery queue |
| Backups | directory or bucket set by UNOMAIL_BACKUP_*
|
| Entry point | Who reaches it | Boundary enforced by |
|---|---|---|
| SMTP port 25 (MX) | anyone | no authentication offered; recipients must be local (Relay access denied otherwise); SPF/DKIM/DMARC/ARC verdicts added by us (skipped only for loopback and UNOMAIL_TRUSTED_NETWORKS), forged Authentication-Results removed; DNSBL, reputation, greylisting, size and recipient limits; strict CRLF (no SMTP smuggling) |
| SMTP 587/465 (submission) | users | TLS before AUTH; envelope sender and From must be the user's own address, alias or identity; per-account hourly limits with automatic blocking; outbound spam/virus scan |
| LMTP (off by default) | front mail server | only private networks or the allow list; relaying refused |
| IMAP 993/143, POP3 995 | users | TLS before credentials (LOGINDISABLED in plaintext); per-mailbox rights (ACL); literal size limits before reading |
| ManageSieve 4190 | users | STARTTLS before AUTHENTICATE; 1 KiB literals before sign-in, 64 KiB after |
| HTTPS web API and JMAP | users, admins | session cookie + per-session CSRF token on every state change; JMAP objects scoped to the account or shared folders; role checks (super admin, domain admin confined to delegated domains) |
| JMAP WebSocket | users | same authentication; cross-origin upgrades refused (Origin check) |
Public pages /lists/..., /.well-known/mta-sts.txt
|
anyone | unguessable tokens (256-bit random, or 128-bit HMAC for unsubscribe); GET never changes anything |
| Outbound connections (SMTP delivery, image proxy, push services, MTA-STS, BIMI, report POSTs, identity provider) | the server | destinations resolved and refused when private/loopback/link-local (IPv4 and IPv6), connection pinned to the checked address, redirects re-checked, size and time limits |
| Milters, rspamd, clamd | trusted local services | configured by the operator only |
CLI (backup, restore, migrate-blobs) |
operator with shell access | filesystem permissions |
Attacker classes considered: unauthenticated internet clients (SMTP, HTTP, IMAP), malicious remote mail servers and DNS zones, senders of hostile messages (HTML, MIME, attachments, S/MIME/OpenPGP structures), authenticated malicious users, compromised accounts, and other tabs or sites in the user's browser. A compromised server process or operator is out of scope: it can read everything except OpenPGP mail encrypted to keys the server never holds.
- Database in plaintext. Encryption at rest covers message blobs and backups, not the live database (subjects, addresses, previews, search index). Use disk encryption for that.
-
Plaintext seen by the server. Before storage the server (and rspamd, clamd, milters) reads
every message, including those then encrypted to a user's OpenPGP key. S/MIME private keys
are held by the server (sealed with
secret.key). -
Backscatter by design. Mailbox-full bounces go to the claimed sender (headers only now).
TLS-RPT reports go to whatever
ruathe receiving domain publishes, without external verification (RFC 8460 defines none); mailing-list confirmation mails go to the address inFrom. Volumes are small (one per request or per domain and day). -
List amplification. A list open to posts from anyone (
posting = anyone) with many remote members multiplies one message; posts from non-members are held by default. -
Spoofed list posts. "Members only" trusts
From/envelope; forged member addresses pass unless DMARC of the member's domain rejects them. - TLS-RPT/DMARC report and push verification requests go to addresses chosen by remote domains or users (public addresses only, bounded in number).
-
No coverage-guided fuzzing yet (cargo-fuzz needs a nightly toolchain); the robustness tests
are random, not coverage-driven.
cargo denyis not installed here; licences and duplicate crates were not checked. - Administrators can reset a user's password and second factors (audited) and thereby reach their mail; there is no separation from the operator.
- Multi-node deployments are not supported; one compromised host holds everything.
-
Unsafe content is recognised by rules, not by a scanner. Link, sender-name and attachment
warnings in the web client and the server's refusal of executable attachments work on names
and addresses. Archives other than ZIP (7z, RAR, ...), password-protected or nested archives,
and documents carrying exploits or macros are only caught by a virus scanner: keep ClamAV
(or Rspamd's antivirus module) enabled. The warnings appear in this web client only; IMAP
and POP3 clients get the messages as they are. A look-alike domain written in plain ASCII
(
paypa1.com) is not detected unless the link text names a different domain, or the domain is on the downloaded phishing list (which, like any blocklist, lags behind new sites).
- Set
UNOMAIL_HOSTNAME, a real certificate (ACME or uploaded) and keepUNOMAIL_COOKIE_SECUREandUNOMAIL_HTTP_REDIRECTat their defaults (true). - Never set
UNOMAIL_ALLOW_PRIVATE_DESTINATIONSoutside tests. - Make sure port 25 sees the real client addresses (no NAT or userland proxy in front, or a
warning appears in the log); list real relays only in
UNOMAIL_TRUSTED_NETWORKS. - Keep Refuse executable attachments on, and the bundled rspamd and clamd (or milters) running; Accept mail when the filter is down (Server settings) and
UNOMAIL_MILTER_FAIL_OPENstay off. - Expose only the mail and web ports; LMTP stays off unless a front server needs it, and then only on a private network.
- Back up
secret.key,blob.keyandvapid.keyseparately from the data backups. - Run
./audit.shbefore releases (and weekly in CI), andcargo test,pnpm testand the Playwright suite. - Encrypt the disk holding
/data.
- Compromised account: disable it or reset its password (signs out every session, ends open IMAP/POP3/SMTP connections within a minute, revokes its app passwords and cancels scheduled messages), reset its second factors (also revokes app passwords, ends sessions and cancels scheduled messages), review the audit log, the passkeys, sender identities, Sieve redirects and sent mail.
- Spam outbreak: the account is blocked automatically on exceeding its hourly limit; check the queue page, hold or delete queued messages, review the outbound log.
-
Exposed secret: rotate DKIM keys (new selector), replace the TLS certificate, and for
secret.keyre-enter the third-party credentials it sealed (identity SMTP passwords, LDAP, OIDC secret, webhook secrets) and ask users to set up TOTP and S/MIME again. -
Data breach: sessions can be revoked globally by deleting rows of the
sessionstable; notify affected users as the law requires.