Orbis 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 isn't a normal threat model. It requires a different kind of security — one where even we cannot read what it knows about you.
This document explains the zero-knowledge architecture that makes that true. The server holds 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's 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 |
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 — it is 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 ZK claim is scoped. It holds against specific threats and doesn't hold against others. Both are worth being precise about.
"The ZK 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 (Content Security Policy, strict input handling), shorten the attack window (inactivity TTL, we now do 30 minutes), and be honest about what the boundary is.
Why the bar is higher here than anywhere else.
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. Orbis has access to your email, calendar, documents, spreadsheets, task graph, and persistent memory across every session. The ZK 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 your entire professional life: every project, every relationship, every decision pattern Orbis has learned. The stakes justify a different standard — and this implementation meets it.
"Zero-knowledge for an AI that knows everything about you is not a feature. It is the only acceptable architecture."
Brainsless Research Lab · 2026| Cryptographic design |
9.5/10
|
| ZK correctness |
9/10
|
| Threat model clarity |
9.5/10
|
| Test coverage |
9/10
|
| Scope of data protected |
10/10
|
| Overall |
9.5/10
|
The 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 honestly accounted for. Everything else is correct by design, verified by 75 tests, and audited end-to-end.
[1] PBKDF2-SHA256 at 600,000 iterations matches the OWASP Cheat Sheet recommendation for 2024. On modern hardware this takes approximately 50–300ms per derivation attempt, making offline brute-force of a reasonable passphrase impractical at scale.
[2] The AES-256-GCM authentication tag is 128 bits appended to the ciphertext. Any modification to the ciphertext, IV, or additional authenticated data causes decryption to throw a DOMException before any plaintext is returned. Verified directly in test suite suite 1.
[3] The 30-minute session inactivity TTL was chosen to balance UX (not re-prompting mid-session) with security (limiting XSS window). It resets on every encrypt() or decrypt() call. Sessions are also cleared on tab close (sessionStorage) and on explicit lock.
[4] Tier-1 covers all primary workspace data: spaces, connection graphs, chat history, and relationship indexes. UI preferences (theme, zoom, interaction speed) are not encrypted — they contain no sensitive content and encrypting them adds latency with no security benefit.