Skip to content

OptimalVault

The flagship Fabric feature: a multi-recipient age-encrypted credential vault where a server compromise cannot decrypt anything. Browser passphrase + WebAuthn passkey unlock; BIP39 24-word recovery phrase; canary blob detects non-deterministic authenticators.

Phase 10a (subphases 1–7) is shipped. Migrations are live, the iPad ceremony was walked end-to-end on 2026-05-04, and 7 of 8 P1 threat-model items closed on 2026-05-05. Live state: 6 vault recipients enrolled across 3 hardware fingerprints, 0 entries (next user action: actually add a credential — see Adding entries).

Crypto core

  • Encryption: age multi-recipient (BSD-3, Filippo Valsorda). Each entry is encrypted to N recipients (browser identity, every paired device identity, recovery identity). Adding/removing a recipient triggers atomic re-wrap of all entries — now backed by a single Postgres RPC (T8 closed e41f9d8).
  • KDF: Argon2id (m=64 MB, t=3, p=1) for passphrase → identity. Per-install salt (T11 closed ffbb8e2).
  • WebAuthn: passkey-derived keypair using PRF extension where available; fallback canary blob detects non-deterministic authenticators. PRF-derived material is wrapped via WebCrypto non-extractable key in IndexedDB (T5b closed 2ddf0e6) — no plaintext marker in localStorage.
  • Recovery: BIP39 24-word phrase. DOM zeroize on submit + 30s clipboard auto-clear (P1-#9 closed 18fefd9). Validated against Trezor reference vectors in tests/vault/crypto/recovery.test.ts.
  • Best-effort zeroization: in-memory plaintext is dropped after session TTL (default 15 min). Bun's GC isn't deterministic — TPM/SE backing is Phase 12-2.

Code: src/vault/crypto/{age,kdf,identity,recovery,recipients,webauthn,webauthn-client,passphrase}.ts.

Schema

Five tables, all on the OptimalOS Supabase instance (hbfalrpswysryltysonm):

TablePurpose
vault_entriesCiphertext blobs (one row per credential, age multi-recipient)
vault_recipientsRegistered age public keys (browser / device / recovery kinds), revoked_at for soft-revoke, canary_ciphertext column for first-unlock pubkey-mismatch detection
vault_access_logOne row per successful entry GET (audit trail visible in dashboard)
devicesPaired device registry (FK target for vault_recipients.device_id)
pairing_tokensShort-lived (10 min) pairing JWTs

Migrations live at ~/.openclaw/workspace/optimal-cli/supabase/migrations/. Versions: 20260503215150, 20260503221013, 20260503235721, plus today's 19e8b5f (atomic re-wrap RPC, T8) and 6a05e9d (per-install salt, T11).

Recipients

The vault is multi-recipient by design. Today's live state has 6 recipients across 3 hardware fingerprints. Hardware-aware labels landed in e0f0d60:

RecipientKind
iPad Safaribrowser
iPad recoveryrecovery
MacBook Air M1 Safaribrowser
MacBook Air M1 recoveryrecovery
Windows Chromebrowser
Windows Chrome recoveryrecovery

Each enrolled fingerprint produces both a browser recipient (passphrase + WebAuthn) and a recovery recipient (24-word BIP39 phrase printed once during setup). A device-kind recipient for the Pi (kanban 8f84c30e) is pending until optimal pair is run on the Pi — the CLI now exists (commits c132faf, 61b296d).

Adding or revoking any recipient triggers atomic re-wrap of every entry via the Postgres RPC.

Setup ceremony

A 4-step wizard. Total time on iPhone Safari: ~3 seconds (Argon2id is the heavy hitter).

  1. Invite — single shared password gate (INVITE_PASSWORD, mode 0600 at ~/.optimalos/transfers/.fabric-invite-password). Per-user invites are Phase 17.
  2. Passphrase — 8+ char minimum, derived to age identity via Argon2id (per-install salt).
  3. Passkey — WebAuthn registration; canary blob written to verify the authenticator is deterministic on subsequent unlocks.
  4. Recovery phrase — 24 BIP39 words displayed once, "I wrote it down" gate, then re-entrance challenge to confirm. Words zeroized from DOM on submit.

After setup completes, the browser identity is registered in vault_recipients (kind=browser), the canary ciphertext is stored, and the user is redirected to / (final landing in 859fab5 — was /vault/dashboard, was /).

Daily unlock

Two paths, depending on whether the browser fingerprint is trusted:

  • Trusted device (≤ 30 days idle): passphrase only.
  • New fingerprint (different browser, cleared cookies, or 30-day idle exceeded): passphrase + WebAuthn passkey.

Trust marker is now a WebCrypto non-extractable key wrapped via the WebAuthn PRF extension and stored in IndexedDB — not localStorage. T5 (legacy localStorage marker) and T5b (PRF-wrap follow-up) are both closed.

Recovery flow

User types the 24-word phrase, picks a new passphrase, and the server atomically:

  1. Derives a fresh age identity from the phrase.
  2. Re-wraps all vault_entries to include the new identity (single Postgres RPC).
  3. Removes the old browser identity from vault_recipients.
  4. Issues a recovery JWT (1 h TTL) so the user can complete a new passkey registration before logout.

Code: src/vault/server/storage.ts, client/vault/recovery.ts, tests/vault/rewrap.test.ts.

Threat model

Full audit at ~/.optimalos/transfers/fabric-design/06-vault-auth-threat-rerun.md. Status as of 2026-05-05 (huge progress today):

SeverityCountStatus
P05All cleared in Phase 10a-7
P187 closed today — only T2 outstanding (Phase 14 dep)
P2100 closed, 1 in kanban

P1 items — closeout log

IDFindingStatusCommit
T2RLS absent (single-tenant only)OutstandingPhase 14 dependency
T4Device-JWT revocation cross-checkCLOSED5ff9ba4 (cached 60s)
T5blocalStorage trust marker → PRF-wrapCLOSED2ddf0e6 (WebCrypto non-extractable in IndexedDB)
T6bLock-file SRI pinningCLOSEDimplied by 14-5 manifest work
T7Cloud TLS pubkey pinningCLOSEDf9142ec (TOFU + verify-every-fetch)
T8Postgres RPC for atomic re-wrapCLOSEDe41f9d8 + optimal-cli migration 19e8b5f
T11Argon2 salt rotation / per-installCLOSEDffbb8e2 + optimal-cli migration 6a05e9d
T13Access-log payload validation, JWT-bound x-session-idCLOSEDa9d9310
P1-#9Recovery phrase DOM zeroize + 30s clipboard auto-clearCLOSED18fefd9
P1-#10Origin pubkey pinningCLOSEDpart of T7 (f9142ec)

Adding entries

Three interoperable paths shipped:

Dashboard "Add Entry" drawer

  • Click Add Entry in the dashboard tab → drawer opens with form fields (name, kind, value, optional metadata JSON).
  • Encryption happens client-side: the browser fetches vault_recipients (active only), encrypts via age to the full active recipient set, and POSTs { ciphertext, recipients_hash, name, kind, metadata } to /api/vault/entries.
  • Server stores ciphertext + recipients_hash only — never sees plaintext.
  • Code: client/vault/{dashboard,add-entry,api}.ts. Commit c3f3a7d.

optimal vault add CLI

bash
export OPTIMAL_FABRIC_TOKEN='<paste from devtools → Application → Cookies → fabric_session>'
optimal vault add --name ANTHROPIC_API_KEY --kind api-key --value 'sk-ant-...'
optimal vault list
optimal vault recipients

Auth via OPTIMAL_FABRIC_TOKEN env or --token. Code: ~/.openclaw/workspace/optimal-cli/lib/vault/index.ts.

optimal vault import-env (bulk)

Bulk-import existing .env files (commit 8a04f0b). A dry-run today shows 69 entries would be imported across 4 default .env files — making this the fastest way to seed the vault from existing local state.

bash
optimal vault import-env --dry-run
optimal vault import-env   # apply

Interoperability

The drawer, vault add, and vault import-env all produce identical ciphertext shape and recipients_hash — same /api/vault/entries route, same payload schema. An entry added via CLI is decryptable in the browser (and vice versa) by any recipient whose private key is loaded.

Vault dashboard

Phase 10a-5 dashboard tab: shows access log (most recent 50 entries with timestamp + recipient + entry id), session count, and a per-recipient revoke button. Revoking sets vault_recipients.revoked_at = now(), which triggers atomic re-wrap via Postgres RPC. Device-JWT revocation is now cross-checked daemon-side every 60s (T4 closed).

  • Crypto code: ~/.openclaw/workspace/optimalOS/src/vault/crypto/
  • Server code: ~/.openclaw/workspace/optimalOS/src/vault/server/
  • Routes: ~/.openclaw/workspace/optimalOS/src/routes/vault.ts
  • Client: ~/.openclaw/workspace/optimalOS/client/vault/
  • Vault design doc: ~/.optimalos/transfers/fabric-design/02-vault-design.md
  • UI flows: ~/.optimalos/transfers/fabric-design/05-vault-ui-flows.md
  • Threat audit: ~/.optimalos/transfers/fabric-design/06-vault-auth-threat-rerun.md
  • Manual smoke checklist: ~/.openclaw/workspace/optimalOS/tests/vault/SMOKE.md

Built by Carlos Lenis in Miami