Zero Accessby Railmandocs

Architecture

Package map, responsibilities, and what does not belong in the key store.

Architecture

Package map

PackageRole
@railman/auth-zero-access (zeroAccess())Better Auth blind key store. MEK metadata, wrap slots, account recovery, optional E2E relay. Never holds MEK, mnemonic, PRF, or vault password.
@railman/auth-zero-access-passkey (enhancePasskey(passkey, …) + browser crypto config)Passkey counter guard, hardened stock composition, and client PRF helpers (salt/info, ceremony, suites).
@railman/auth-elevate (elevate())Step-up / sudo: short window, elevatedAt + amr, password / TOTP / passkey. Takes passkeyCounterGuard from the composition.
@railman/auth-login-factorLogin-factor stamps and server-constant requireAccess maps.
@railman/zero-vaultProduct vault UX — create vault, daily unlock, recovery reset. Not a Better Auth plugin. Pass passkeyOptions from defineZeroAccessPasskeyCryptoConfig.
@railman/zero-e2eX3DH + Double Ratchet primitives that consume a Vault-derived identity seed. Directly WebAuthn-free at the library boundary; layered on Vault in the product.

auth-* means Better Auth (or BA-adjacent) surface area. Creating a vault is product code, not the store plugin.

Wrap kinds (key store)

All are client-wrapped ciphertext the server validates and stores:

KindTypical KDFDaily unlock?
prf_passkeyHKDF from WebAuthn PRFYes
password_kdfPBKDF2-SHA256 (≥ 600_000 iterations)Yes
recovery_bip39PBKDF2 from BIP-39 seed (≥ 600_000)Reset only (product policy)

Key store endpoints (sketch)

Under your Better Auth basePath (for example /api/auth):

MethodPathPurpose
POST/zero-access/mekStore MEK meta + recovery wrap
GET/zero-access/mekList associations for the session user
POST/zero-access/wrap-slotsPut a daily wrap
GET/zero-access/wrap-slotsList wraps (?mekId=)
POST/zero-access/wrap-slots/revokeRevoke a slot
*/zero-access/account-recovery/*Enroll / start / finish / session-to-registration exchange

Full contract: Plugin contract.

Out of scope for the key-store plugin

ConcernOwner
“Create vault” screens and copyApp / zero-vault
WebAuthn login register/assertenhancePasskey → stock @better-auth/passkey
PRF ceremony + tenant saltBrowser defineZeroAccessPasskeyCryptoConfig
Sudo windowauth-elevate
App domain encryption (notes, files)App (sealBytes helpers optional)

Composition

const passkeyStack = enhancePasskey(passkey, {
  rpID,
  origin,
  assertCredentialAccess: "session", // rung 0; upgrade to a step-up assertion
});

plugins: [
  ...passkeyStack.plugins, // [stock passkey (pinned options), guard plugin]
  loginFactors(),
  elevate({
    deploymentMode: "single-instance",
    passkeyCounterGuard: passkeyStack.controller,
    passwordOnlyIfNoStronger: true,
    elevatedTtlSec: 300,
  }),
  zeroAccess({
    deploymentMode: "single-instance",
    requireRecoveryExportAck: true,
  }),
];

Follow the layered matrix in Use cases. Elevate runs standalone with password/TOTP (omit passkeyCounterGuard); the passkey stack alone hardens login. Vault automatically binds the installed passkey guard by exact identity, and Chat extends the unlocked Vault identity.

Elevate: Better Auth session → fresh password/TOTP/(optional passkey) → approval claim

Vault:   hardened passkey + PRF → local MEK unlock

Chat:                              Vault identity seed → zero-e2e

Browser product path:

import { defineZeroAccessPasskeyCryptoConfig } from "@railman/auth-zero-access-passkey/client";
import { createVaultClient } from "@railman/zero-vault";

const cryptoConfig = defineZeroAccessPasskeyCryptoConfig({ salt });
createVaultClient({ userId, passkeyOptions: cryptoConfig });
// unlock → seal app data → never POST MEK

Security tests

Cross-package security regressions live in the monorepo as tests/security/ (not a product package). Prefer pnpm ci:core for the hosted-equivalent gate; pnpm test:security runs the matrix alone.

On this page