This document describes what Sovite defends against, the privileges it runs with, and the rules every component must follow. It is a living document; each roadmap phase extends it. To report a vulnerability, see SECURITY.md.
1. Assets
| Asset | Why it matters |
|---|---|
| Queued mail | Confidential content. A 250 OK is a promise to deliver it. |
| Relay permission | An open relay gets the host blocklisted and used for abuse. |
| Credentials | SMTP AUTH passwords and backend (SQL/LDAP) credentials. |
| Private keys | TLS keys and DKIM signing keys. A leaked DKIM key lets anyone forge the domain's mail. |
| Configuration and lookup tables | Decide who may relay and where mail goes. |
| Availability | Mail delayed by more than the queue lifetime is bounced. |
2. Threat Actors
| Actor | Capabilities |
|---|---|
| Remote unauthenticated client | Opens TCP connections to ports 25/465/587 and sends arbitrary bytes. This is the main attack surface. |
| Authenticated user | Valid credentials (possibly stolen); tries to relay, spoof senders, or exceed quotas. |
| Malicious remote server | Answers our outbound connections; sends hostile replies, TLS certificates, or DSNs. |
| DNS attacker | Spoofs or manipulates DNS answers (MX, TLSA, SPF, DKIM records). |
| Local unprivileged user | Shell on the host; uses sendmail(1) and may read world-readable files. |
Out of scope: an attacker with root, or with the Sovite user's privileges, on the host.
3. Privileges and Files
- Sovite runs as a dedicated unprivileged user (
sovite), never as root. - Ports below 1024 are bound with systemd socket activation or
CAP_NET_BIND_SERVICE. The VM never needs root. - The spool directory (
[queue] directory) is owned bysovite, mode0700. Queue files are0600. - The config file and lookup tables are owned by
root, groupsovite, mode0640. Sovite reads them but cannot modify them. - TLS and DKIM private keys are owned by
root, groupsovite, mode0640. - The
sendmail(1)compatibility binary (Phase 9) submits mail over a local socket and is not setuid.
4. Rules for All Code
These apply to every component and are checked in code review.
- Never an open relay. Relay requires an explicit grant: an authenticated user or a configured trusted network. The default config relays for no one.
- Durability before
250. A message is written andfsynced (file and directory) before the reply is sent. - Bounded parsing. Everything parsed from the network has a hard limit: line length, command count, header count and size, recipients per message, message size, MIME nesting depth, DNS response size, SPF lookup count. Limits are checked while streaming, not after buffering.
- No atoms from untrusted input. The BEAM atom table is never garbage-collected. Network input, DNS data, and config keys are mapped to atoms only through fixed tables, as the config loader does.
- SMTP smuggling. Only
<CRLF>.<CRLF>ends DATA. Bare LF and bare CR are rejected or normalized by an explicit policy, never interpreted inconsistently. - STARTTLS injection. Any plaintext buffered after the
STARTTLScommand is discarded once the handshake completes, on both server and client side. - Secrets are never logged (see Logging) and never appear in crash reports. Processes holding secrets use
:sensitiveprocess flags or keep secrets out of their state. - Credential checks are constant-time. Passwords are stored salted and stretched:
SCRAM-SHA-256(PBKDF2, RFC 7677) by default, or SHA-crypt. Unknown users cost as much time as wrong passwords, andSCRAM-SHA-256answers them with a stable fake salt, so neither timing nor the exchange reveals which users exist. - Modern TLS only. TLS 1.2 and 1.3 with BCP 195 (RFC 9325) cipher suites. Certificates are verified wherever the policy says so.
- Untrusted text is escaped in logs and headers. Values from the network cannot inject log lines or header fields (CR/LF in
EHLOnames, addresses, and similar). - Least data. Message bodies are streamed, not held in memory, and are not copied into crash dumps or logs.
5. Defenses by Phase
| Threat | Defense | Phase |
|---|---|---|
| Open relay | Explicit relay permission, open-relay test in the definition of done | 1 |
| Resource exhaustion | Connection limits (global and per IP), timeouts, bounded parsing | 1 |
| SMTP smuggling | Strict end-of-data handling | 1 |
| Lost mail on crash | fsync before 250, delivery results fsynced before they count, crash recovery | 1–2 |
| Hostile remote servers | Bounded reply parsing (line length and count), timeouts on every wait, remote text sanitized before it goes into notifications or logs | 2 |
| Mail loops | Notifications sent from <> and never answered; double-bounce reports never reported again; MX hosts at or below this server's preference skipped; a server greeting with our own name treated as a loop | 2 |
| Credential theft in transit | AUTH offered only after TLS by default; submission listeners require TLS; relay host credentials only sent over TLS; SASL data never logged | 3 |
| Weak or downgraded inbound TLS | TLS 1.2+ only, forward-secret AEAD suites only, server cipher order, no client renegotiation (testssl.sh: A+ with a trusted certificate) | 3 |
| STARTTLS command injection (CVE-2011-0411 class) | Input received after STARTTLS and before the handshake is discarded; the session restarts after it; the client discards pre-TLS server data | 3 |
| Brute-force AUTH | Failed logins counted per address (per /64 for IPv6) with temporary bans; delay after each failure; session closed after 3 failures | 3 |
| Sender spoofing by authenticated users | Sender login maps | 3 |
| Directory and database injection | LDAP filters parsed before user names are inserted; database access only through Ecto with bound parameters; empty LDAP passwords refused (anonymous bind) | 3 |
| A broken certificate renewal taking TLS down | Certificates reloaded only when the new pair loads and matches; otherwise the old one stays | 3 |
| Downgrade and MITM on outbound TLS | Per-destination TLS policy (encrypt, verify), DANE with DNSSEC-validated TLSA records | 3 |
| Downgrade on outbound TLS without DNSSEC | MTA-STS | 7 |
| Spam and bot traffic | postscreen-style checks, DNSBL, rate limits | 8 |
| Compromised accounts | Outbound volume and bounce-rate detection | 8 |
6. Supply Chain
- Dependencies are kept minimal and pinned in
mix.lock. CI fails on unused locked dependencies. - Release builds are reproducible and release artifacts are signed (Phase 12).