Security
A plain-English explainer of how we keep your data safe.
Per-user encryption
Every OAuth token, 2FA secret, and backup code we hold is encrypted with a key that is different for every customer. Even our database administrators cannot read your tokens — they would need both the database backup AND our master key, and the master key only ever lives in process memory and a sealed password manager.
master_key ──HKDF-SHA256──> your_user_key
▲
(master + your user id)
your_user_key ──AES-256-GCM──> ciphertext stored in DB
+ random 12-byte IV
+ auth tag (verified on every read)Algorithm
- AES-256-GCM for every encrypted value at rest. GCM authenticates the ciphertext — any tampering is detected on read and the data is refused.
- HKDF-SHA256 derives a per-user key from
GUSTAVIA_VAULT_MASTER+ your user id. Stealing one user's key reveals nothing about anyone else's. - bcrypt cost 12 for password hashes.
- HS256 JWT sessions, 24h expiry, single-use jti tracked server-side so you can revoke any session from Settings.
- HMAC-SHA256 for OAuth state + SMS ingest signatures.
- PKCE S256 on every OAuth flow (Google + Microsoft).
Tenant isolation
Every API endpoint scopes every query by your user id. We never trust route parameters as authorisation. We have automated tests that prove User B cannot read, save, dismiss, or write-to-calendar against User A's data, and that the per-user encryption keys are cryptographically bound to the user id (so a row stolen from User A can never be decrypted by User B's key).
What we log, what we don't
Server access logs include path, status code, and your user id. We do not log message bodies, recipient identities, OAuth tokens, or passwords. Audit log entries store only the action type, a SHA-256 hash of your IP, and a truncated user-agent. Error reporting (Sentry) auto-redacts request bodies, query strings, Authorization/Cookie headers, and your email before any event leaves our servers.
Hardening checklist
- OWASP ASVS L1 controls reviewed and passing (see internal SECURITY.md).
- 10/10 pen test probes pass on every release.
- Rate limits: 10/min auth, 60/min capture, 5/min support.
- Strict CORS allow-list; no wildcard origins.
- Helmet security headers + Content Security Policy.
- Body size capped at 512 KB.
- Gitleaks + pre-commit hook block secret leaks at the commit boundary.
Responsible disclosure
Found a bug? Email [email protected] with proof-of-concept. We respond within 48 hours, fix confirmed issues within 30 days for High/Critical, and credit you publicly if you want. Please give us a reasonable window before public disclosure (90 days for most, 7 days for actively-exploited issues).