Web Worker key isolation
Run Ownfold cryptographic operations behind a dedicated worker while keeping only opaque root-key handles on the UI thread.
@ownfold/browser can run its complete CryptoEngine behind a dedicated Web Worker. Root-key
bytes are created, opened, compared, used, and destroyed inside the worker. The UI thread receives
only an opaque RootKeyHandle; it cannot export key bytes through Ownfold’s public API.
Create a worker entry owned and bundled by the host application:
import { exposeOwnfoldCryptoWorker } from "@ownfold/browser/worker"
exposeOwnfoldCryptoWorker()
Pass that worker to the browser client:
import { createVaultClient, createWorkerCryptoEngine } from "@ownfold/browser"
const worker = new Worker(new URL("./ownfold.worker.ts", import.meta.url), {
name: "ownfold-crypto",
type: "module",
})
const cryptoEngine = createWorkerCryptoEngine({
worker,
operationTimeoutMs: 60_000,
})
export const vault = createVaultClient({
transport,
cryptoEngine,
})
Vite and other worker-aware bundlers compile the new URL(..., import.meta.url) entry as a
separate asset. Confirm equivalent behavior in the host build tool instead of copying a generated
worker filename into application code. Serve the worker from the application origin and allow it
with an explicit worker-src 'self' Content Security Policy directive.
The operation timeout defaults to 60 seconds and must be a positive safe integer. Size it above the slowest tested Recovery Kit KDF operation on supported devices. A timeout is terminal for that engine instance because Ownfold cannot prove the state of an unresponsive worker.
Lifecycle and failures
Locking, local-state destruction, device revocation, and VaultClient.dispose() send root-key
destruction to the worker. At final application teardown, call cryptoEngine.dispose() after
disposing every client that uses it; the default behavior terminates the worker and fails pending
operations with CRYPTO_WORKER_DISPOSED. A worker belongs to one engine and is not shared.
Malformed messages are rejected at the worker boundary. Worker crashes, message-cloning failures,
invalid responses, and operation timeouts become typed CryptoEngineError values. Each of these is
terminal for that engine: Ownfold terminates the worker, settles every pending operation, rejects
later operations without posting them, and never falls back to main-thread cryptography. Create a
new worker and unlock again before retrying. The adversarial suite verifies crash fan-out, timeout,
malformed-response, post-failure, disposal, and late-event behavior through the exported narrow
CryptoWorkerPort contract. Previously persisted ciphertext and local encrypted key material
remain unchanged.
Ownfold copies caller-owned plaintext and recovery-secret buffers before transferring those copies, so application buffers are not detached. Decrypted plaintext necessarily returns to the UI thread for rendering. Non-extractable device-key material is structured-cloned between IndexedDB, the UI thread, and the worker when opening a device envelope.
Security boundary
Worker isolation reduces accidental root-key exposure from UI state, debugging tools, and ordinary main-thread code. It does not protect against malicious same-origin JavaScript: such code can invoke the unlocked client, intercept plaintext before encryption or after decryption, replace the worker bundle, or alter the application deployment. It also does not protect against a compromised device, browser extension, or operating system.
Use worker isolation together with automatic locking, a strict CSP, Trusted Types where supported, minimal third-party JavaScript, dependency pinning, and plaintext-free observability. Review the dedicated-worker ADR, threat model, and host security guidance before deployment.