Skip to main content

Security

Zero-knowledge encryptionโ€‹

Plan data is encrypted on the client (AES-GCM). The encryption key is derived from the user's password (PBKDF2), and in a Duo plan it's shared between household members via key exchange (ECDH). The key never reaches the server in any form. We don't roll our own cryptography โ€” we use the browser's native Web Crypto API.

The upshot: even with full access to the database, the data can't be read without the user's password or recovery code.

Access control (Row Level Security)โ€‹

In cloud mode, data access is enforced by Postgres RLS: queries only see and modify the household you're a member of. Operations such as creating/joining/leaving a household or saving state go through security definer functions that check permissions in a controlled way. RLS acts as an extra layer (defense-in-depth) on top of the ciphertext โ€” it limits who can even fetch a record, which is unreadable without the key anyway.

Keys and secretsโ€‹

  • In the browser, only the public anon key is used, protected by RLS.
  • Sensitive keys (e.g. service-role, payment-provider keys) never reach the frontend โ€” they live only on the server (environment variables).
  • No secrets in the repository.

Data validationโ€‹

Every form and every read of the state goes through Zod schemas โ€” invalid or malicious data never reaches the app. Text is stripped of control characters.

Passwordsโ€‹

Passwords aren't stored in plaintext โ€” Supabase keeps only a one-way hash (bcrypt). They can't be read; they can only be reset or overwritten.

Destructive actionsโ€‹

Resetting data and deleting your account require password confirmation to prevent accidental data loss.

Transport and headersโ€‹

Communication over HTTPS, with security headers (including a CSP).