Zero Accessby Railman

Client boundaries

Better Auth helpers vs vault and chat — both products use zeroAccessClient, and elevate only when a write needs sudo.

Client boundaries

Better Auth plugin clients are small HTTP helpers. Vault and chat are additional product packages. They do not ship their own auth HTTP. They call authClient.zeroAccess, and authClient.elevate only when a write needs sudo.

Two rings

authClient is not a key manager and not a messenger. Elevate never opens a vault. Vault unlock never mints sudo. Chat never encrypts because elevate succeeded.

Better Auth helpers (one createAuthClient)
  passkeyClient          login / register
  loginFactorsClient     types only
  elevateClient          sudo ceremony
  zeroAccessClient       wraps, recovery, e2e relay HTTP

Product (additional)
  zero-vault   local MEK → publishes via zeroAccess
  zero-e2e     identity from vault → relay via zeroAccess

Canonical engineer spec: monorepo docs/CLIENT_BOUNDARIES.md.

Better Auth helpers

PluginOwns
Stock passkeyClient()Login / credential HTTP
loginFactorsClient()session.loginFactor types — no HTTP
elevateClient()/elevate/* — L1 sudo
zeroAccessClient()/zero-access/* — wraps, recovery, chat relay

zeroAccessPasskeyClient() is a no-op. Use stock passkeyClient() plus PRF helpers.

Elevate (elevateClient)

Step-up on an existing session. Default 300s. Verify JSON is UI-only; server requireElevate / requireAccess authorize.

CallDoes
elevate.methods()Enrolled factors — UI
elevate.verify({ method, … })Password, TOTP, or passkey assertion
elevate.withPasskey()Options → credentials.get → verify

Not this client: login, enroll, MEK, PRF, chat.

When vault / chat call it

ActionElevate?
Daily vault unlockNo
Persist wraps / recovery enrollUsually (host assertAccess)
Chat send / inboxNo (session on relay)
Chat identity rotateIf the host gates that write

Products call authClient.elevate.* then authClient.zeroAccess.*. Unlock and sealMessage must not mint sudo.

Three passkey ceremonies

CallLayerPRF?
Stock passkeyClient()L0 identityno
authClient.elevate.withPasskey()L1 sudono
vault.unlockWithPasskey()L2 unwrap MEKyes

Zero-access (zeroAccessClient)

Shared persistence for both products: MEK wraps, account recovery, and e2e prekey/inbox/contacts. The server never holds MEK, mnemonic, PRF, vault password, or message plaintext.

Relay HTTP belongs here — not a chatClient() plugin. Use authClient.zeroAccess.putE2ePrekey (and siblings) from the typed client.

Vault (createVaultClient)

Not a BA plugin. Local create / unlock / reset. Wire: authClient.zeroAccess to publish wraps; authClient.elevate before privileged writes.

createVault has no HTTP (show the recovery phrase first). Publishing is storeMek + putWrapSlot (the persistCreatedVault helper is sugar over those). Daily unlock is L2 only.

Chat (@railman/zero-e2e)

Not a BA plugin. Crypto only (X3DH + Double Ratchet). Identity seed from a vault grant. Delivery through the same zeroAccessClient e2e routes. No second WebAuthn; no elevate-to-encrypt.

Next

On this page