ADR 0009: Dedicated-worker root-key isolation
Architecture decision for keeping Ownfold root-key bytes inside a dedicated Web Worker while exposing only opaque handles to the application UI thread.
Status: accepted.
Context
The original sodium engine already kept root bytes in a private WeakMap, but the map and all
cryptographic execution lived on the application UI thread. That prevents supported key export but
does not isolate root bytes from accidental main-thread inspection or application-state mistakes.
Ownfold’s architecture reserved Web Worker integration as a defense-in-depth control.
Decision
Provide WorkerCryptoEngine, an optional implementation of the stable CryptoEngine contract.
The host application owns a dedicated module-worker entry and calls
exposeOwnfoldCryptoWorker() from @ownfold/browser/worker.
- Actual
RootKeyHandleinstances and root bytes remain in a worker-local map. - The UI thread holds an engine-bound opaque handle mapped to an unguessable request reference.
- Every message is a closed discriminated operation and is validated before dispatch.
- Recovery secrets and plaintext use transferred copies; caller buffers are not detached.
- Errors cross the boundary as a fixed typed representation and are reconstructed without secret fields or stack traces.
- A worker failure is terminal for that engine instance and never triggers an implicit main-thread fallback.
- Root-key destruction preserves message order with earlier operations; worker termination clears all remaining process memory.
The default in-process sodium engine remains available for environments without Worker support. The application must choose that downgrade explicitly.
Consequences
Production browser integrations can keep root bytes off the UI thread without changing vault state, persisted formats, transports, or framework bindings. Worker-aware bundling and CSP configuration become host responsibilities, and the sodium runtime is loaded in a separate bundle.
This is defense in depth, not protection from hostile same-origin code. Plaintext still crosses the worker boundary for application use, and a malicious deployment can replace or invoke the worker. The browser matrix must execute the complete recovery flow through the real worker entry.