This document describes the security threats relevant to a webmail client and the mitigations in place or planned for letrvu.
Threat: Senders embed 1×1 transparent images. Loading them confirms the recipient's email address is live, leaks their IP address, approximate geolocation, mail client, and read timestamp to the sender's server.
Mitigation (implemented): Remote images (http://, https://, and protocol-relative // URLs in <img src>, inline style attributes, and <style> blocks) are replaced with a local placeholder before the HTML is rendered. A banner informs the user and offers a one-click opt-in to load images for that message.
Threat: Malicious HTML in an email body (<script>, inline event handlers like onerror=, javascript: hrefs, CSS expression()) executes code in the reader's browser session.
Mitigation (implemented): HTML email is rendered inside a sandboxed <iframe srcdoc="..."> with sandbox="allow-popups". This blocks script execution, same-origin access, and form submission while allowing user-initiated links to open in new tabs. allow-scripts must never be added.
As defense-in-depth, DOMPurify sanitizes the HTML in the frontend before it is set as srcdoc. DOMPurify runs in the browser using the same HTML parser that will render the output, which eliminates mutation XSS (mXSS) attacks where a server-side sanitizer and the browser would parse the same markup differently. It strips script tags, javascript: URLs, inline event handlers, and other dangerous constructs.
Threat: An email displays a trusted domain as the link text while the href points to a different, malicious destination. Users click without inspecting the URL.
Mitigation (implemented): All http(s) links in HTML email are given target="_blank" rel="noopener noreferrer" so they open safely in a new tab. For links where the visible text looks like a URL (starts with http(s):// or www.) but the registrable domain of the text differs from the registrable domain of the href, the link is highlighted in red with a wavy underline and a ⚠ suffix, and a descriptive title is added. A warning banner is shown above the email body with the count of suspicious links detected. The detection and styling happen client-side in the HTML processing pipeline, before the HTML is set as srcdoc.
Threat: IMAP/SMTP credentials stored server-side can be exfiltrated if the database is compromised.
Mitigation (implemented): Credentials are encrypted with AES-256-GCM. The encryption key is derived via HKDF-SHA256 from two independent secrets: the server-side SESSION_SECRET (never leaves the server) and a per-session 16-byte random nonce stored in the browser cookie. Neither alone is sufficient — recovering the plaintext password requires both the database row (for the ciphertext) and either the server secret or the individual session cookie.
Threat: A stolen session cookie grants full account access, including the ability to read, send, and delete mail.
Mitigation (implemented):
letrvu_session:HttpOnly,SameSite=Strict, andSecure(whenSECURE_COOKIES=true) are set.letrvu_csrf:SameSite=StrictandSecure(whenSECURE_COOKIES=true) are set.HttpOnlyis intentionally omitted so JavaScript can read the token.- Set
SECURE_COOKIES=truein production whenever the app is served over HTTPS.
Note: User-Agent binding was considered and rejected. The UA is present in the same HTTP request as the cookie, so any realistic theft scenario (network sniffing, XSS, devtools) gives the attacker both simultaneously. It adds schema complexity and spontaneous logouts on browser updates for no meaningful security gain.
Threat: A malicious third-party site triggers a state-changing API call (send message, delete message) on behalf of a logged-in user whose cookie is sent automatically by the browser.
Mitigation (implemented): SameSite=Strict on session cookies prevents the cookie from being sent on cross-site requests, which covers most CSRF scenarios. As belt-and-suspenders, a double-submit CSRF token is required on all mutating API endpoints: the server sets a non-HttpOnly letrvu_csrf cookie on login, and the frontend reads it and sends it as an X-CSRF-Token header. The server validates that both values match using constant-time comparison.
Threat: The IMAP_INSECURE_TLS=true environment variable disables TLS certificate validation. On a real mail server this exposes credentials and message content to network interception.
Mitigation (partial): This flag must never be set in production. The UI should display a visible warning when the server connection was established without certificate verification.
Threat: Even with email-body isolation, reflected or stored XSS in the Vue application itself (e.g., via subject lines, sender names, or folder names rendered without sanitization) can execute code.
Mitigation (implemented): The Go server sets the following headers on every response:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; object-src 'none'; frame-ancestors 'none'; connect-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
The srcdoc iframe is governed by its own sandbox attribute, not the parent page's CSP, so this header does not conflict with HTML email rendering.
| Priority | Item |
|---|---|
sandbox="allow-same-origin" to sandbox in MessageView.vue |
|
srcdoc |
|
Content-Security-Policy header in the Go HTTP server |
|
HttpOnly, Secure, SameSite=Strict) |
|
| Low | Per-sender "always show images" preference persisted in settings |
| Low | UI warning when IMAP_INSECURE_TLS=true is active |