Security & Encryption

    What we do, what we don't do, and how to verify it yourself.

    Your data is encrypted on your device, before it reaches our servers.

    Your portfolio is encrypted in your browser using a key derived from your password. We only ever see ciphertext. A full database leak — by us, our hosting provider, or anyone else — would reveal nothing about your finances.

    Cryptographic primitives: XChaCha20-Poly1305 for encryption, Argon2id for password-based key derivation. Both via libsodium.

    How your key hierarchy works

    your password
    never sent to server
    recovery code
    24-word BIP-39, opt-in
    ↓ Argon2id
    KEK
    Key Encryption Key
    ↓ Argon2id
    Recovery KEK
    Key Encryption Key
    ↓ both unwrap (XChaCha20-Poly1305) ↓
    Data Key (DK)
    random 256-bit key, stored encrypted; rotated only on wipe
    ↓ XChaCha20-Poly1305 + random nonce + AAD(user_id)
    ciphertext
    the only thing stored on the server — no keys, no plaintext

    What we defend against

    • Database leaks. Our hosting provider (Supabase) only sees ciphertext. Anyone who steals a backup or the database itself sees ciphertext.
    • Hosting provider read access. Supabase staff cannot read your data, even with full database access.
    • Subpoenas of stored data. We can comply by handing over the encrypted blob — but the blob alone reveals nothing without your password.
    • Cross-user attacks. The encrypted blob is cryptographically bound to your user ID. Even with full database write access, an attacker cannot move one user's data into another user's account without it failing to decrypt.
    • Stolen / lost device, after sign-out. Your encryption key lives only in browser memory and is wiped on sign-out, tab close, and idle timeout.
    • Automated attacks on the login. Sign-up and sign-in are rate-limited and gated by a CAPTCHA (Cloudflare Turnstile), which blunts bot sign-ups and credential-stuffing attempts.

    What we do not protect against

    These are real limits. We list them here because trustworthy encryption claims start with honest non-goals.

    • An actively malicious server. Every web app, including this one, downloads JavaScript from a server every time you visit. A compromised server could ship modified JS that exfiltrates your password as you type. Bitwarden, ProtonMail, Standard Notes — all web-based E2E systems share this limit. Our first-party code ships as hashed, immutable assets, which means a replacement of the bytes requires a deploy by us. Signed builds and a native client remain the stronger mitigation; subresource integrity is not applied to our third-party origins because the vendors do not publish per-version content hashes and a pinned hash would break on the next silent rotation.
    • A compromised device. Malware, keyloggers, and malicious browser extensions run with your privileges. No application can defend its own user from this.
    • Metadata. We can see that you have an account, your email, when you saved data, and roughly how big your portfolio JSON is. We can't see what's in it.
    • A forgotten password without a recovery code. If you forget your password and skipped the recovery code, your encrypted data is permanently unrecoverable. This is a property of true E2E encryption, not a bug.
    • Coercion. If someone forces you to disclose your password, they disclose everything.
    • A supply-chain attack on our dependencies. We pin lockfiles and review updates, but we are not immune.

    Forgotten passwords and recovery codes

    Because we cannot read your data, we cannot reset it for you. If you forget your password, the only way back in is a 24-word recovery code generated when you sign up.

    Setting up a recovery code is opt-in. We strongly recommend you do. The code is a BIP-39 mnemonic with 256 bits of entropy. We display it once and never store it (only a wrapping derived from it). Save it somewhere offline — a printed copy, a safe, or a password manager.

    You can set up or rotate the recovery code from Settings → Security.

    A note on password reset. Resetting your password through the standard email flow rotates your account password, but it cannot rewrap your existing encrypted data — only your old password or your recovery code can do that. If you reset your password and you have a recovery code, we'll prompt you for it on next sign-in to restore access. If you reset your password and you skipped the recovery code, your previously encrypted snapshots become permanently unrecoverable. This is a property of true end-to-end encryption, not a bug.

    Verify it yourself

    We don't ask you to take our word for it. Both the design and the implementation are open and inspectable.

    • Encryption design document — full threat model, primitive choices, key hierarchy, AAD framing, schema, and migration plan.
    • Crypto source code — small, pure functions; no I/O. Tests cover round-trip, tamper detection, AAD binding, and cross-user isolation.
    • Repository — full source. File issues if you find a problem.

    Accessibility

    Quantive aims to follow WCAG 2.1 AA accessibility best practices. As a micro-business under EU criteria, we are not subject to formal accessibility-conformance reporting requirements, but we treat accessibility as a baseline quality bar. If you encounter an issue, please email [email protected].

    Disclosure and contact

    If you find a security issue, please disclose it responsibly — do not open a public issue. Email [email protected] or use a GitHub private advisory.