Security
Last updated 2026-09-15.
Zero-knowledge encryption
K = Argon2id(passphrase, salt), then two independent keys via HKDF-SHA256: an authKey sent to the server as your login credential, and a KEK that never leaves your device. The KEK wraps a per-account data encryption key, which in turn encrypts every sensitive field (AES-256-GCM) before it’s sent. A compromised server exposes ciphertext and metadata - card list, due dates - never balances, merchant names, or notes.
No password reset, on purpose
The server never learns your passphrase, so it has nothing to reset. Account recovery uses a 24-word recovery phrase, generated once at signup and shown exactly once - it wraps the same data key independently of your passphrase. Losing both means the data is gone, including to whoever operates the deployment.
Passkeys and two-factor authentication
WebAuthn passkeys and TOTP two-factor are both available (Settings > Security). Both strengthen sign-in, but neither replaces your passphrase for unlocking data on a given device - only the passphrase can derive the key that does that, by design.
Transport & headers
HSTS, a nonce-based Content-Security-Policy, Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, a restrictive Permissions-Policy, and same-origin COOP/CORP on every response.
Rate limiting & CSRF
5 requests/minute on authentication routes, 60/minute per session and per hashed IP on the app’s API. Every mutating API request carries a double-submit CSRF token.
Audit log
Sign-ins, lockouts, passkey and two-factor changes, passphrase changes, and card create/delete are logged to an account-scoped audit trail (Settings > Audit log) - never the contents of a card, balance, or transaction.
Dependency & code scanning
Every change runs through Semgrep’s OWASP Top Ten ruleset, pnpm audit at the high-severity threshold, and an OWASP ZAP baseline scan in CI - none pass with a known High or Critical finding.
What isn’t mitigated
Said honestly rather than implied away: a malicious browser extension with page access, a global adversary, a hosting provider with hypervisor-level access, or someone with direct physical access to an unlocked device. Zero-knowledge encryption protects data at rest and in transit to a server that can’t read it - it doesn’t protect a device that’s already been compromised while you’re using it.