Security overview.
Last updated: 2026-06-18.
This page is the technical companion to the Privacy policy. It describes the controls we have in place today, the controls we haven't built yet but are committed to, and how to report a problem.
We try to be specific. Vague claims about being "bank-grade" or "enterprise-secure" don't help anyone reason about a system. Where something isn't done yet, we say so and we say when.
What we encrypt at rest
The following fields are encrypted with AES-256-GCM before they hit the database, using a per-record cipher tag and a server-side key:
- Your login email address
- Your secondary (recovery) email address
- The destination address each alias forwards to
- The sender address recorded for each contact
- Message subjects, message bodies (text and HTML), and attachments
The cipher implementation is cloak_ecto, a well-reviewed Elixir
library. Keys are versioned: every encrypted record carries a tag
that names the key it was sealed under, so we can rotate keys without
re-encrypting old data. New writes use the current key; old reads
transparently fall through to the older one.
How we look up encrypted data
Encryption breaks lookup. To get it back without decrypting on every query, we store a blind index alongside each searchable encrypted field: an HMAC-SHA-256 fingerprint computed with a server-only key that's separate from the cipher key.
Lookups happen against the fingerprint. A database dump on its own doesn't reveal the underlying value — and because the HMAC key lives in application memory, never in the database, an attacker who steals a database backup cannot brute-force the fingerprints back to plaintext addresses without also stealing the running server.
The HMAC key is versioned independently of the cipher key. Both can be rotated without service interruption.
What we encrypt in transit
- HTTPS (TLS 1.2+) on every public surface —
maski.dev,api.maski.dev,ws.maski.dev. - WSS for the WebSocket transport.
sslmode=requireon the PostgreSQL connection between the application and the database.- STARTTLS advertised on inbound SMTP. We use opportunistic TLS: senders that support it get an encrypted leg, senders that don't fall back to plaintext over the public internet (this is how every email server on the open internet works).
- TLS verified on every outbound API call to sub-processors (AWS SES, Dodo Payments).
Disk encryption
The production database volume runs on LUKS full-disk encryption. A stolen disk does not yield a readable database without the LUKS passphrase, which lives in a separate operator-controlled secret store.
Authentication
- Magic-link sign-in. Tokens are 32 bytes of cryptographically random data, expire in 10 minutes, and are single-use. They are rate-limited per IP and per target email.
- Passwords (optional). Hashed with Argon2id — the password-hashing competition winner. Memory and time costs are tunable from configuration. The password field has no length cap outside what's reasonable.
- Recovery codes. 10 single-use codes generated at recovery setup, HMAC-fingerprinted at rest, displayed once.
- Sessions. Refresh tokens live in an
HttpOnly,Secure,SameSite=Strictcookie scoped to the API origin. The access JWT lives in client memory only — never inlocalStorage. Refresh tokens rotate on every use and are revocable server-side from the Sessions tab. - Step-up. Sensitive actions (delete account, change primary email, regenerate recovery codes, billing operations) require a fresh authentication step within the past few minutes.
Rate limiting and abuse controls
Every public endpoint is gated by a rate limiter. Specifics:
- Inbound SMTP: per-IP rate limits and a global concurrency cap.
- Auth: per-IP and per-target-email caps on magic-link requests; per-IP and per-account caps on password attempts; account lockout after a threshold of failed password attempts.
- Aliases: per-user daily creation cap.
- Authenticated API: per-user request cap.
- Outbound forwarding: per-user daily quotas, per-plan.
- Bounce processing: hard-bounce threshold per destination — at the threshold, forwarding to that destination stops and we notify you through your secondary channel.
- Sub-processor webhooks (SES bounce/complaint via SNS, Dodo billing): signature-verified, replay-windowed, and IP-rate-limited as defence in depth.
Operational discipline
- No standing operator access to your message contents in plaintext. Database access by Maski operators is restricted, audited at the OS level, and time-bound.
- Logger filters strip email addresses, message bodies, API keys, and session tokens before any log line is written.
- Backups are encrypted at rest and stored on the same provider region as the primary database. Restoration is tested.
- Secrets management. Cipher key, blind-index HMAC key, JWT signing key, refresh-token key, recovery-code key, and reverse-token key are required on boot — the application refuses to start if any is missing or shorter than 32 bytes.
- Dependency hygiene.
mix hex.auditandsobelowrun in CI on every push to main.
Where the data lives
| Surface | Provider | Region | | --- | --- | --- | | Application + database | Amazon Web Services (EC2) | ap-south-1 (Mumbai) | | Outbound email delivery | Amazon SES | ap-south-1 (Mumbai) | | Backups + exports | Amazon S3 | ap-south-1 (Mumbai) | | Billing | Dodo Payments | Global |
Breach notification
If we confirm an incident that exposes your data — a database leak, unauthorized access, or a sub-processor breach with material impact on you — we will notify you within 72 hours of confirmation. The notification goes to your primary login email, your secondary (recovery) email if you have one set, and to an in-app banner on your next sign-in. The notification will describe what was exposed, when, the steps we have taken, and the steps we recommend you take.
What we have not done yet
We don't claim certifications we don't hold. Today:
- SOC 2 Type I. Initiated when the developer API launches (Phase 3).
- ISO 27001. Targeted before we serve EU enterprise customers (Phase 4).
- Formal breach-response runbook. Will be in place before we cross 1,000 active users.
- Audit log table of admin actions. Will be added within one month of launch; at launch, admin access is logged at the operating system level.
- End-to-end encryption of message contents between sender and recipient. Email's open-relay model makes this complicated, and we'd rather offer real field-level encryption with operator discipline than ship a marketing claim that doesn't hold up. We'll revisit if the need is concrete.
- Bug bounty. No paid bounty in Phase 2. We acknowledge reporters publicly (with their consent) and credit them.
Reporting a vulnerability
If you've found something, we want to hear about it. Email hey@maski.dev with:
- A clear description of the issue and the impact you observed.
- The steps required to reproduce it.
- Any traffic captures, payloads, or screenshots you can share.
- A way to contact you back.
Safe harbor. We will not pursue legal action or report you to law enforcement for security research conducted in good faith against Maski's production systems, provided you:
- Don't access or modify data that doesn't belong to you (use a test account).
- Don't degrade the service for other users (no DoS testing).
- Don't disclose the vulnerability publicly until we've had a reasonable window to fix it (we aim for 90 days, sooner where possible).
- Don't violate any other law in the course of the research.
We respond to vulnerability reports within 5 business days with an acknowledgement and a triage owner.
Contact
For security disclosures or any other inquiries: hey@maski.dev.