# Trust model Witbitz makes **two separate trust claims.** Keep them separate: 1. **Privacy is verifiable — the double blind.** A Space is sealed to a key the platform never holds; the app has no backend of its own; the browser egress footprint is locked; and the deployed render can be rebuilt from published source and checked against a signed certificate. Neither the operator nor the app's own owner can read the users' data — and that's a *property you check*, not a promise. 2. **Humans keep authority — delegated authority.** An AI agent can propose work, but the runtime requires a human approval before any high-stakes action executes. No action runs without an approval. This page states both guarantees in full. It is intentionally explicit about what is live today and what is not — start with [Status](./status.md) if you need the shipped / preview / designed map, and [Verify it yourself](./verify.md) to run the checks in your own terminal. --- ## What is protected today | Claim | Production status | |---|---| | A gated Space refuses reads and writes without an allow-listed identity | Live and checkable | | The stored Space ledger is ciphertext sealed to the room key | Live and checkable | | There is no operator recovery recipient in the production envelope | Live and checkable | | The app's browser egress is locked to a published allowlist | Live and checkable | | The deployed render publishes a signed build certificate | Live and checkable | | The render source can be rebuilt to the deployed hash | Live and checkable | | High-stakes agent actions require approval | Live and tested | Run the checks: [Verify it yourself](./verify.md). ## What is not hidden Witbitz does not hide everything, and the docs should never imply that it does. - **Metadata remains visible.** The platform sees that a room exists, when it changes, and roughly how large writes are. - **The model provider sees turn content — on the frontier trade.** A remote frontier model must receive the text it reasons over, so a room that keeps the frontier model (the default Space) exposes its turns to that vendor. The **confidential/attested tier closes this**: its model call leaves the measured image only through an attested no-log gateway into confidential-compute inference on an **open-weights** model (fail-closed if that gateway does not attest) — see [the attested tier](./the-attested-tier.md). - **The render sees plaintext in use today.** Production Spaces open plaintext inside the server-side render during a member's active read or turn. - **Endpoints still matter.** A compromised member device can read what that member can read. - **Availability is not privacy.** The operator can refuse service. The content-blind claim is about reading, not guaranteeing access forever. --- ## Guarantee 1 — The double blind {#the-double-blind} 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. What holds today, exactly where it stops, and how the remaining "readable in use" gap is closed by the [attested tier](./the-attested-tier.md), now in production. ### Sealed to a key we never hold → 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)`). To the operator, a Space is opaque bytes — storage and transport are **content-blind**, because the platform holds no key. There is **no operator recovery key**: every record is sealed to the room key alone, so there is nothing on the platform that can read it. The double blind is the only configuration that runs in production. ### 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](./async-spaces.md)). 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 when no one is. **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 the **attested tier** exists (below). Keeping it **one** small, auditable path is exactly what makes attestation tractable. ### 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. ### 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](./verify.md) is the commands. What is *not* yet checkable about your own traffic is the render being unobservable **in use**; that gap is closed cryptographically only in principle (a never-receives-`mk`, append-only tier whose sealed client→actor channel is unbuilt and not planned) — the shipped way to close it is the [attested tier](./the-attested-tier.md). ### Status — what holds today | Property | Status | |---|---| | Sealed ledger, platform holds no key (nothing on the platform can read it) | **shipped** | | No operator recovery key (sealed to the room key alone) | **shipped & CHECKABLE** — `POST {"op":"sealed"}` → `recipients:["room"]` ([verify Test 5](./verify.md)) | | Admission gate enforces (reads *and* writes refused without an allow-listed identity) | **shipped & CHECKABLE** ([verify Tests 1–3](./verify.md)) | | 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](./verify.md)) | | 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](./verify.md)) | | **Reproducible build** of published source (rebuild it → the deployed hash) | **shipped & CHECKABLE** — `/source.tar.gz` ([verify Test 7](./verify.md)) | | Attested tier (operator provably blind **in use**) | **Production** — verifiable on real Nitro hardware, scale-to-zero; the app builder enables the enclave per room ([attested tier](./the-attested-tier.md)) | | Attested **client** (a hardware-attested native shell carrying the audited bundle) | **Design** — native shells not shipped; the deployed web app *already* verifies the enclave's attestation, which is part of the attested tier above ([verified client](./client-architecture.md)) | ### 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: - **Neutral + backendless.** A vendor cloud reads *to itself* by default; a Witbitz app has no backend of its own and isn't bound to one provider's stack. The trust surface is **consolidated to one auditable platform, not removed** — the honest claim, *not* "no backend" (Witbitz **is** the backend, singular and inspectable). Awkward for an incumbent whose business *is* the readable backend to adopt — but not something they're *barred* from. - **The verifiability layer.** The signed certificate and the reproducible build have **shipped**, and the attested tier is **in production** — verifiable on real Nitro hardware, launch-on-demand and scale-to-zero. Four checks run in a stranger's terminal today, and the fifth is in production for any Space the app builder runs in the enclave. What remains genuinely unbuilt is the attested **client** in native shells. ### 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) | [the published render source](https://github.com/kibitz-chat/witbitz-render) | *The double blind is not "trust us less." It is "you don't have to" — sealed to your key by default, and provably unobservable in use for any Space that turns on the attested tier.* --- ## Guarantee 2 — Delegated authority {#delegated-authority} The second guarantee. An agent never *owns* authority — it **receives** it, limited and revocable, and a human approves anything high-stakes before it happens. The runtime enforces one invariant: **no action runs without an approval.** > Built + tested (async Spaces M2 Cut 4); runs inside the deployed `/space` render. Code: `agent/delegatedAuthority.mjs`. ### Propose → approve → execute An effectful action moves through three recorded steps, asynchronously (the human may approve on a *later* turn, not in-the-moment): 1. **propose** — on its turn, the agent proposes an action; it is recorded **pending**, **not run** (`propose(room, { action, by })`). 2. **approve / deny** — a human decides on a later turn (`decide(room, { id, approve, by })`). A decision on a non-pending proposal is refused; a decided proposal can't be flipped. 3. **execute** — only an **approved** proposal runs, through the injected executor (`execute(room, { id, run })`), and only once. ### The invariant Enforced and unit-tested: **an un-approved proposal never runs, and no proposal double-runs.** Approval is a precondition to execution, checked in the runtime — not left to the model's discretion. The model reasons, the human authorizes, the runtime gates. ### Capabilities are just tools A Space's effectful capabilities are ordinary **tools** in its config (`send_summary`, `schedule_checkin`, …) — there is no separate "actions" concept. The turn runs the normal brain tool-loop; the only async-specific twist is that a high-stakes tool call is **deferred to a human approval** instead of running in-room. On approve, `execute` runs the same proposal through the **same tool executor** a live call would use, keyed on the tool name — so approval routes to the real implementation. ### The audit trail Every step — propose / approve / deny / execute (with result or error) — is appended to a single per-room **`authority` doc**, an append-only log. It is **mk-sealed** like the ledger, so it is **content-blind** (the platform can't read it) and it **persists across weeks**. A wrong `mk` yields nothing — the trail is readable only to a present member, in the decrypt-once render. Richer policy — spend caps, per-capability authority tiers, org-level delegation — builds on this primitive. The one-method-call SDK surface for it is [Delegated authority in the SDK](./sdk.md#private-lanes-and-human-approved-actions). --- ## Identity and admission A Space starts with **key possession**: holding the room key proves membership. Gated Spaces add signed identity on top — Google/OIDC identity, signed invites, agent keys, owner policies. The link is the admission capability; the owner decides who may join and at what tier; and members join, rotate devices, and are revoked over a Space's life. The full mechanism — identity, the verified room, admission tiers, the owner rule, member lifecycle, and recovery — is one doc: **[Identity and admission](./identity-and-admission.md)**. ## Stronger tiers | Tier | Status | What it changes | |---|---|---| | **Owner-governed rooms** | **Implemented** | The app owner can mandate admission tiers and capabilities; built and tested, not yet enabled in production ([owner rule](./identity-and-admission.md#the-owner-rule)). | | **Attested server tier** | **Production** | A Space's turns run inside measured Nitro hardware, key sealed to the image; launches on demand, scales to zero when idle. The platform can run any room in it; enabling it is the app builder's choice ([attested tier](./the-attested-tier.md)). | | **Attested client** | **Design** (native shells) | The deployed web app *already* verifies the enclave's attestation and pins its measurement before sealing your key — that half is live, part of the attested tier; a *native* binary carrying the audited bundle is still future work ([verified client](./client-architecture.md)). | For the full mechanism-by-mechanism breakdown — the canonical maximum, the orthogonal privacy axes, the named modes, and the feature × mode matrix — see the privacy spine: **[Privacy tiers](./privacy-tiers.md)**. ## Where to go next - Check the claims: [Verify it yourself](./verify.md) - Configure privacy: [Privacy tiers](./privacy-tiers.md) · the first-party [Spaces app defaults](./spaces-app-defaults.md) - Understand admission and lifecycle: [Identity and admission](./identity-and-admission.md) - Go unobservable in use: [The attested tier](./the-attested-tier.md) - Know the code on the user's side: [The verified client](./client-architecture.md)