The AI co-founder in Planless has access to your email, calendar, documents, spreadsheets, task graph, and persistent memory across every session. It knows your patterns, your relationships, your decisions. That is not a normal threat model, and it does not get a normal security posture — the design goal is that even we cannot read what it knows about you.
This document explains the zero-knowledge architecture that makes that true. The server holds a salt, a verification blob, and ciphertext. It has no key. It cannot decrypt. The passphrase never leaves the browser. We ran 75 tests against the cryptographic core, threat-modeled the full stack, and audited every integration path. This is how it works, and how we know it holds.
What happens when you enter your passphrase.
Everything starts client-side. The passphrase goes into PBKDF2-SHA256 with a 32-byte random salt, fetched from the server — not secret, just unique per user. At 600,000 iterations, the OWASP 2024 recommendation, this produces an AES-256-GCM key. That key lives only in memory as a non-serialisable CryptoKey object. It is never sent anywhere.
The encrypted blob format is fixed: { encrypted: true, v: 1, iv: string, ct: string }. The iv is 12 random bytes per encryption call, the standard GCM nonce length. The ct is the ciphertext plus the GCM authentication tag, 16 bytes appended. GCM's authentication tag means any bit-flip in either the ciphertext or the IV is detected and rejected on decryption — a property verified directly in our test suite.
Verification without a password oracle
To confirm a passphrase is correct without attempting to decrypt real data, we store a verifyBlob on the server: the AES-GCM encryption of the fixed string 'planless-vault-v1-verify' under the derived key. On unlock, we decrypt this blob and check the plaintext matches. If it does, the key is correct. No real user data is touched during the check.
Recovery
If a recovery code is provided at setup, a second key is derived from it using the same PBKDF2 salt. The main key's JWK is encrypted under this recovery key and stored as recoveryBlob on the server. Recovery codes are 10 random bytes expressed as uppercase hex in four dash-separated groups — 80 bits of entropy, offline brute-force resistant at 600k PBKDF2 iterations per guess.
What lives where, and who can read it.
| Location | Contents | Server can decrypt? |
|---|---|---|
| Cloud — vault metadata | Salt (32 random bytes), verifyBlob (AES-GCM of fixed label), recoveryBlob (AES-GCM of main key JWK, optional) | No key |
| Cloud — workspace data | Encrypted blobs: spaces, connections, chat sessions, memory — all as { encrypted, v:1, iv, ct } |
No key |
| sessionStorage | { jwk, ts } — the exported vault key and last-activity timestamp. Cleared on tab close. Auto-expires after 30 min of inactivity. |
Client only · XSS risk |
| localStorage | Encrypted blobs for Tier-1 keys (spaces, connections, chats). Non-sensitive UI preferences stored plaintext. | Encrypted blobs only |
| Memory (CryptoKey) | The in-memory CryptoKey object. Cleared on lock, tab close, or 30 min inactivity. |
Never leaves device |
Access rules enforce ownership at the data layer — only the authenticated user can read or write their documents. Even if those rules were misconfigured, the cloud content is opaque without the passphrase. The salt is non-secret; it is just random bytes that make PBKDF2 user-specific. Knowing the salt without the passphrase tells you nothing.
Seventy-five tests. All passing.
We wrote a dedicated test suite that runs directly against the cryptographic core — no mocks, no network. All operations run against native webcrypto. Eleven suites covering every layer: key derivation, encryption correctness, ZK properties, session management, recovery, edge cases, multi-user isolation, and attack resistance.
all passing
across all layers
after review
PBKDF2 per suite
| Suite | What it tests | Tests |
|---|---|---|
| 1. Cryptographic Correctness | Salt entropy, IV uniqueness across 50 calls, GCM auth tag on bit-flip (ciphertext and IV), cross-key rejection, PBKDF2 salt independence | 10 |
| 2. Zero-Knowledge Properties | Passphrase never in stored metadata, no key material on server, verifyBlob opacity, lock/unlock state machine, wrong passphrase returns false | 9 |
| 3. Edge Cases | Minimum length enforcement, null/empty rejection, missing blob fields, null value roundtrip, 1 MB payload, unicode/emoji, all byte values 0–255, corrupted blobs | 13 |
| 4. Recovery Code | Format validation, uniqueness across 100 runs, 80-bit entropy, correct code unlocks, wrong code returns false, cross-session decrypt | 8 |
| 5. Session Storage | Save/restore roundtrip, corrupted entry cleared, lock removes key, inactivity expiry, session scoping per user | 6 |
| 6. Vault Reset | Clears all state, sessionStorage, and in-memory key; throws on invalid input | 2 |
| 7. State Listeners | Locked/unlocked events, unsubscribe removes callback, listener exception does not crash the vault | 4 |
| 8. Blob Detection | Correct identification of encrypted vs. plaintext vs. null vs. malformed blobs | 5 |
| 9. Attack Resistance | Truncated ciphertext, empty ciphertext, cross-user ciphertext rejection, post-reset unreadability, session key isolation | 11 |
| 10. Performance | PBKDF2 brute-force cost (>20 ms per attempt), 100 encrypts under 1 s, full unlock under 5 s | 3 |
| 11. Multi-User Isolation | Same passphrase, different salts → different keys → cross-decrypt fails, status correctly scoped to userId | 2 |
Test environment: isolated Node.js Jest environment. No mocks, no network calls. All cryptographic operations run against native webcrypto. Suite is internal, not open-sourced.
What the audit found.
We audited the full vault stack: the key manager, storage layer, cloud sync engine, session handling, and backend access rules. The cryptographic primitives were correct throughout. A set of integration-layer issues were identified in the paths connecting those primitives to the rest of the application. All were addressed. One residual item is documented below — a known property of browser-based cryptography, not a flaw in the design.
| Finding | Component | Status |
|---|---|---|
| Integration layer: several code paths connecting the cryptographic core to cloud sync and local cache did not consistently apply encryption. All paths now encrypt before any write. | Sync engine · Storage layer · Auth handler | Fixed |
| Binary serialiser failed on large payloads due to call-stack limits. Replaced with a bounded implementation. | Key manager | Fixed |
| The derived key is held in sessionStorage for session persistence across page refreshes. Same-origin scripts can read it. Shared by all browser-based E2E systems (Signal web, 1Password, Bitwarden). 30-minute inactivity TTL limits exposure window. | Key manager | Mitigated |
| Legacy format detection uses a prefix heuristic with a narrow false-positive edge case. | Storage layer | Tracked |
What this protects against, and what it doesn't.
The zero-knowledge claim is scoped. It holds against specific threats and doesn't hold against others. Both are worth being precise about.
The zero-knowledge claim is zero-knowledge from the server. It was never zero-knowledge from the browser.
Brainsless Research Lab · 2026The residual XSS risk is inherent to browser-based cryptography. Every client-side E2E system shares it — Signal's web client, 1Password's browser extension, Bitwarden's web vault. The mitigations are the same: minimize XSS surface with a Content Security Policy and strict input handling, shorten the attack window (we now use a 30-minute inactivity TTL), and be precise about what the boundary is.
Why the bar is higher here than most encryption systems.
Most encryption implementations protect a single data type — a password vault, a notes app, a file store. The threat model is narrow. Planless is different. Its AI co-founder has access to your email, calendar, documents, spreadsheets, task graph, and persistent memory across every session. The vault protects the full cognitive layer — everything an AI knows about how you work. That changes what "secure enough" means.
An attacker who breaches a password manager gets a list of passwords. An attacker who breaches an unprotected Planless workspace gets the structure of an entire professional life: every project, every relationship, every decision pattern the AI has learned. The stakes justify a different standard, and this implementation meets it.
Zero-knowledge for an AI that knows this much about you is not a feature. It is the only acceptable architecture.
Brainsless Research Lab · 2026The 0.5 remaining is the residual XSS surface inherent to all browser-based cryptography — a boundary shared by every client-side E2E system in existence, including Signal's web client and 1Password's browser extension. It is documented, mitigated, and accounted for honestly. Everything else is correct by design, verified by 75 tests, and audited end to end.