Witbitz docs PlatformTrustAll docs

The Double Blind — verifiable privacy

Witbitz's core privacy claim: for an app built on it, neither the platform operator nor the app's own owner can read the users' data. And — the part that makes it a property not a promise — that claim is meant to be checkable from the code, not taken on faith.

This doc is the whole argument in one place: what holds today, exactly where it stops, and Sealed Spaces (§5) — the opt-in tier that turns the remaining gap from a behavioural promise into a property of the build.

See also: async Spaces (the sealed ledger + decrypt-once render), the Space link is the auth (the keys live in the link), and Verify it yourself (run the checks).


1. Ciphertext at rest → the platform is blind

A Space's data is a snapshot sealed under the room key mk and stored as ciphertext; the platform never holds mk (agent/envelope.mjs seal/open; the key is committed only as commit(mk), room-link-auth). At rest, to the operator, a Space is opaque bytes — storage and transport are content-blind.

There is no operator recovery key. Every record is sealed to the room key alone — the platform writes no support or recovery recipient of any kind, so there is nothing readable-by-the-operator to read at rest. The double blind is the only configuration that runs in production.

2. The decrypt-once render → the one plaintext door

Plaintext opens in exactly one code path — the certified render — but that path runs on two triggers: an AI turn (open the ledger with a present member's mk, run the declared program, re-seal, drop mk) and every changed read (the poll decrypts the new entries to return them, because the client is thin and decrypts nothing itself — async Spaces §3–4). Reads outnumber turns, so the honest exposure surface is the render, not "only when the AI acts." The key reaches that render only from a present member's client, per active window — never from storage — so the operator can act on the data only while a member is present, and learns nothing at rest. Keeping it one auditable path is exactly what makes the attested tier tractable.

The honest boundary, stated plainly. Because the client is thin, mk travels to the render on every read and write, and the server opens the ledger in place — a decryption oracle for the moments a member is active. So content-blind means at rest and against a passive (honest-but-curious) operator, not one that captures mk in flight; and because the whole ledger is sealed under one long-lived room key, an operator that logged mk once could read that Space's history past and future — there is no per-Space forward secrecy yet.

That boundary is the reason §5 exists.

3. Backendless → the app owner is blind too

The second blind is the one nobody else offers. A Witbitz app is a static page with no backend of its own that connects only to the audited Witbitz API. So the app's developer/owner has no backend through which to read its users either: the users' content is sealed to a key the owner doesn't hold, and the app has nowhere else to send it. Data is encrypted from the owner, not merely to them.

To be exact — "backendless" is a claim about the app, not the platform. Witbitz is still a backend (one shared, constrained, inspectable platform). What's removed is each developer's own, opaque, app-specific server. So the trust surface isn't smaller in a hand-wavy "no infrastructure" sense — it's consolidated: one auditable platform to check, instead of N unknown app backends. That consolidation is exactly what makes the footprint declarable (§4).

4. Verifiable → check the code, not the operator

Because the app is a static page with a declared egress (it can only talk to the Witbitz API), its entire data footprint is inspectable and declarable. You verify the code and a signature, not a "trust us" policy.

Four of those checks run in your own terminal today — the admission gate enforcing, the egress lock, the content-blind store, and the deployed render being a reproducible build of published source. Verify it yourself is the commands. What is not yet checkable about your own traffic is the render being unobservable in use; see §6.

5. Sealed Spaces — the tier that removes the key from our reach

Opt-in, and a tier — not a replacement.

Today's Spaces are double-blind, but §2's blindness is behavioural: the operator is trusted not to look at a key it does, briefly, receive. A single capture defeats it — a logging toggle, a compelled order, a compromised render reads the entire history, past and future, because the key arrives on a schedule and nothing bounds one room's exposure. A Sealed Space takes the key off that schedule. The server never receives mk. There is no capture to toggle. The platform stores ciphertext and holds exactly one capability — the ability to append, never to read.

5.1 The design — four changes, each assembling a primitive already in the platform

  1. The ledger becomes an append-only log of individually sealed entries — not one snapshot re-sealed on every write. This is the load-bearing move: appending no longer requires reading, which is exactly what today fuses "write" to "hold the key."
  2. The client decrypts locally. It syncs sealed history once, caches it on the device, then pulls sealed deltas by cursor — the same poll shape as today, just opaque to the server.
  3. A session-scoped, ephemeral actor does the reasoning. On the first turn the client seals the assembled context to the actor's session public key; after that the actor receives only new messages. It never sees mk, holds nothing across sessions, and is evicted on idle.
  4. Write-back is a write-only capability. The actor appends its reply by sealing to the Space's public write key (sealTo / openBox). It can add to the ledger without being able to open it.

The net: the platform routes ciphertext, stores ciphertext, and can append ciphertext. It cannot read the room.

5.2 What it protects — and what it doesn't

A security claim is only worth the threat model it names. Three columns, not two.

Structurally protected: at-rest capture (storage dumps, a compelled disk or database order, a logging toggle, a compromised render — the platform holds only ciphertext and a write-only key); and blast radius, because per-Space epoch keys mean one Space's compromise doesn't cascade and rotation bounds it going forward.

Not protected, stated plainly:

Naming the third column is not a weakness. It is the honest meaning of content-blind: blind at rest, bounded in use.

5.3 What it costs

5.4 Status

In hand: per-entry sealed append and content-blind polling (scaffolded behind a flag, inert until enabled); sealTo/openBox for write-only append; epoch re-key in the Bridge; the reproducible-build + egress-lock + certificate stack. To build: the sealed client → actor session channel; ephemeral-actor lifecycle and idle eviction; cursor-based sealed-delta sync as the default read path; epoch-key distribution to members without the platform in the loop. To measure first: the real prompt-caching cost on a busy Space.

6. Status — what holds today

Being precise, because this is exactly where a claim can overreach:

Property Status
Ciphertext at rest (sealed ledger, platform holds no key) shipped
No operator recovery key (sealed to the room key alone) shipped & CHECKABLEPOST {"op":"sealed"}recipients:["room"] (verify §Test 5)
Admission gate enforces (reads and writes refused without an allow-listed identity) shipped & CHECKABLE (verify §Tests 1–3)
Decrypt-once render, mk from the client only, dropped after shipped
Backendless static app (no backend of its own) shipped
Egress-lock (CSP connect-src/script-src allowlist) shipped & ENFORCED (block-mode) (verify §Test 4)
Client E2EE primitive (the sealed-blob envelope) shipped
Signed build certificate (deployed build hash + egress, signed from ground truth) shipped & CHECKABLE/cert.json (verify §Test 6)
Reproducible build of published source (rebuild it → the deployed hash) shipped & CHECKABLE/source.tar.gz (verify §Test 7)
Sealed Spaces (the platform never receives mk) — §5 designed — primitives in hand, the session channel is not built
Attested tier (operator provably blind in use) built & independently verifiable — NOT on production traffic (the attested tier)
Attested client (knowing the code running on the user's side) design (the attested client)

7. The moat — honestly

The moat is not "incumbents can't do this." They can: Apple's Private Cloud Compute ships attested confidential inference today, and a competent team could match everything Witbitz has shipped in a quarter. Two things are actually defensible:

8. Code map

Concern Where
At-rest ledger seal (symmetric, under mk only) + key commitment agent/envelope.mjs (seal, open, commit, newRoomKey); agent/sessionStore.mjs encodeMemory — one recipient, mk. No operator/beta/admin recipient in prod.
Recipient sealing (ECDH sealTo/openBox) — seals to a member/agent public key, never an operator scoped agent reads (agent/spaceScopedView.mjs), Bridge epoch keys (agent/bridgeMembership.mjs), the collection-blob — a separate mechanism from the at-rest ledger above
Sealed ledger + decrypt-once render agent/sessionStore.mjs, agent/asyncTurn.mjs
Content-blind poll (etag over ciphertext) agent/spacePoll.mjs
Egress-lock CSP (block-mode, enforced on prod Spaces) spaces/tools/gen-space-csp.mjs
The render, published and readable (~600 lines) github.com/kibitz-chat/witbitz-render

Sealed Spaces is not "trust us less." It is "you don't have to." The current tier remains for the workflows that need an always-on agent; Sealed is for the rooms where the key should never leave your hands — and where you can prove it didn't.

Machine-readable source: the-double-blind.md · every doc in one fetch: llms-full.txt · ← async-spaces · the-live-call