Browser integration
Create a type-safe vault transport, initialize the browser client, persist ciphertext, and handle local recovery flows.
Create a VaultTransport that authenticates each call using the host application’s session.
The transport must ignore client-supplied user identity and derive ownership server-side.
Create a browser client with that transport, call initialize(), and render the returned
discriminated state. Fallible methods return Promise<Result<T, OwnfoldError>>.
For production browser deployments, configure the optional dedicated-worker engine so root-key bytes and cryptographic execution remain off the UI thread. See Web Worker key isolation for the worker entry, lifecycle, CSP, and failure contract.
Store EncryptedEnvelopeValue through the application’s normal record repository. New writes use
V2 while V1 remains readable and locally migratable. Never add
plaintext fields as a fallback. On reads, pass the stored envelope and the expected context
to decryptJson or decryptBinary.
At the application API boundary, call vaultServer.validateEncryptedRecordWrite(). It accepts only
an object containing encryptedPayload, rejects plaintext sibling fields, and verifies the
authenticated user’s active vault ID and key version plus the independently supplied namespace,
record ID, and owner ID. The returned value is safe to pass to a ciphertext-only repository; the
method authenticates metadata but never decrypts record content.
For root-key rotation, implement RotationRecordStore over those encrypted records. Batch reads
must return stable checkpoints and absolute progress; batch writes must be atomic and idempotent.
Ownfold changes only wrapped data keys and never sends record content to the coordination server.
Recovery Kit creation, download, import, verification, and restoration happen locally. The server stores the active kit identifier and verification status, not Recovery Kit ciphertext or its password.
Standalone replacement changes only the active kit identifier through an optimistic server write; the root key and encrypted records remain unchanged. See Recovery Kits.
Applications may accept a strong user-created password or call RecoveryCode.generate() for a
192-bit browser-generated secret. The generated code must be saved separately: it is intentionally
not stored in the Recovery Kit, browser storage, or server metadata.
Operational hooks
The host server owns rate limiting and audit delivery. Configure both when creating the server:
import { RateLimitExceededError } from "@ownfold/core"
import { createVaultServer } from "@ownfold/server"
import { Result } from "better-result"
const vaultServer = createVaultServer({
adapter,
getUserId,
checkRateLimit: async ({ operation, request, userId }) => {
const allowed = await limiter.check({ operation, request, userId })
return allowed
? Result.ok(undefined)
: Result.err(
new RateLimitExceededError({
code: "RATE_LIMIT_EXCEEDED",
message: "Too many vault operations. Retry later.",
}),
)
},
onAuditEvent: (event) => auditSink.enqueue(event),
onAuditDeliveryError: ({ event }) => metrics.increment("ownfold.audit_delivery_failed", event),
})
Rate-limit checks run after host authentication and before adapter access. A thrown rate-limit hook
fails closed with RATE_LIMIT_CHECK_FAILED; fetch maps an explicit limit to HTTP 429 and a limiter
outage to HTTP 503. Audit events record authorization decisions, not storage completion. They never
contain request bodies, Recovery Kits, passwords, device envelopes, ciphertext, or plaintext.
Audit-sink failure does not turn an already allowed or safely rejected operation into an ambiguous
client response; onAuditDeliveryError is the host’s alerting path.