20 KiB
Vaulted Ecosystem — Enterprise Migration Guide
Status: Future reference — no active OpenSpec changes. This document is planning for the Vaulted password-manager project, not the current genesis enterprise library migration. OpenSpec changes will be created when these packages are scheduled.
Consolidated architecture and migration plan for building a 1Password-equivalent password manager using the Suaveplan enterprise library stack.
1. Executive Summary
This document consolidates all architectural decisions, new package requirements, and migration steps for building Vaulted — a cross-platform password manager (desktop, mobile, browser extensions) on top of the Suaveplan enterprise library ecosystem. It covers:
- Technology stack per platform (Electrobun, Capacitor+React, Bun, WebExtension MV2/MV3)
- New
@suaveplan/*packages required to fill critical gaps - Storage architecture — unifying
@suaveplan/driveand@suaveplan/storage-* - Vault orchestration layer — the
@suaveplan/vaultpackage that ties everything together - Migration sequencing for tonight's library migration session
2. Platform Architecture Summary
| Platform | Runtime | UI | Build Tool | Native Messaging |
|---|---|---|---|---|
| Desktop | Electrobun | React 19 + Tailwind | Bun + Turborepo | Unix socket / named pipe |
| Mobile | Capacitor (WKWebView/WebView) | React 19 + Tailwind | Bun + Vite | In-process (Capacitor bridge) |
| Chrome | Manifest V3 service worker | React 19 + Tailwind | Bun + Vite | runtime.connectNative → local socket |
| Firefox | Manifest V2 persistent background | React 19 + Tailwind | Bun + Vite | runtime.connectNative → local socket |
| Safari | Safari App Extension (embedded in macOS app) | React 19 + Tailwind | Bun + Vite + Xcode | In-process NSExtensionContext |
Icon generation for all platforms is handled by @suaveplan/iconz (migrated from npm iconz), generating app/extension icons from a single source SVG.
3. Critical New Packages — Build These First
These packages have no viable external alternative and unblock core authentication, 2FA, passkeys, and extension communication.
3.1 @suaveplan/srp — Secure Remote Password (SRP-6a)
- Purpose: Zero-knowledge password authentication. Core to 1Password's auth model.
- Dependencies:
@suaveplan/crypto-curve,@suaveplan/crypto-hash - Scope: Client-side SRP implementation for signup, signin, session key agreement.
- Runtime: Bun (desktop), browser (web), Capacitor (mobile)
- Estimate: ~2-3K lines of big-integer math
3.2 @suaveplan/otp — TOTP/HOTP Generation & Verification
- Purpose: 2FA code generation for vault items (RFC 6238/4226).
- Dependencies:
@suaveplan/crypto-hash(HMAC-SHA1),@suaveplan/bytes(base32) - Note:
@suaveplan/security-codesdetects OTP secrets in strings (leak detection) but cannot generate time-based codes. - Estimate: ~500-800 lines
3.3 @suaveplan/cbor — Concise Binary Object Representation
- Purpose: CBOR encoder/decoder (RFC 8949). Required for WebAuthn/passkey support.
- Dependencies: None
- Estimate: ~500-800 lines, tree-shakeable
3.4 @suaveplan/webauthn — WebAuthn / Passkey Client Library
- Purpose: CBOR attestation parsing, client data construction, COSE key parsing, assertion formatting. Monkey-patches
navigator.credentialsfor browser extensions. - Dependencies:
@suaveplan/crypto-curve,@suaveplan/crypto-hash,@suaveplan/cbor - Note:
@suaveplan/crypto-browserprovides non-extractable keys but no WebAuthn client logic. - Estimate: ~2-3K lines
3.5 @suaveplan/native-messaging — Encrypted Extension↔Desktop Protocol
- Purpose: Typed, encrypted transport between browser extensions and the desktop app.
- Protocol: X25519 handshake + ChaCha20-Poly1305 framing. Abstracts Chrome stdio, Firefox stdio, and Safari
NSExtensionContextbehind one interface. - Dependencies:
@suaveplan/crypto-curve,@suaveplan/crypto-cipher - Estimate: ~1K lines
4. High-Priority New Packages
4.1 @suaveplan/password-strength — Entropy & Crack-Time Estimation
- Purpose: zxcvbn-style password strength scoring.
- Architecture: Two-tier — lightweight heuristic core (~5KB) + optional lazy-loaded dictionary (~200KB).
- Note:
@suaveplan/security-codeshas Shannon entropy detection for leaked secrets, not crack-time estimation.
4.2 @suaveplan/form-detection — DOM Form Heuristics for Autofill
- Purpose: Detect login, registration, and password-change forms with confidence scoring.
- Runtime: Browser extension content scripts (Chrome, Firefox, Safari).
- Note: No good external standalone library exists. Bitwarden and 1Password keep this proprietary.
- Estimate: ~10KB tree-shakeable engine
4.3 @suaveplan/site-icons — Favicon Fetching & Caching
- Purpose: Fetch and cache website icons (favicon.ico, Apple touch icons, manifest icons, Open Graph images) for display next to vault items.
- Dependencies:
@suaveplan/domain-normalizer,@suaveplan/cache-browser,@suaveplan/mime - Note:
iconz(npm) is for build-time app icon generation, not runtime site icon fetching.
4.4 @suaveplan/hibp — Have I Been Pwned Client
- Purpose: Watchtower-style breach monitoring via HIBP k-anonymity API.
- Dependencies:
@suaveplan/crypto-hash(SHA-1 prefix),@suaveplan/retry
4.5 @suaveplan/qrcode-scan — QR Code Decoding / Scanning
- Purpose: Import TOTP secrets by scanning QR codes (camera or image upload).
- Note:
@suaveplan/qrcodeis generation-only. This adds the decode/scan subpath. - Decision: Either extend
@suaveplan/qrcodewith/scansubpath or create standalone package.
4.6 @suaveplan/biometrics — Universal Biometric Auth Abstraction
- Purpose: Abstract Touch ID / Face ID / Windows Hello / Android BiometricPrompt.
- Delegates: Capacitor plugins (mobile), Electrobun native bridge (desktop),
@suaveplan/macos-keychain(macOS) - Estimate: Thin facade (~300 lines)
5. Medium-Priority New Packages
5.1 @suaveplan/password-generator
- Pronounceable passwords (diceware), memorable passphrases, PINs, charset-constrained generation with entropy estimation.
@suaveplan/randomhasrandomStringbut no password-specific logic.
5.2 @suaveplan/vault-sync — Domain-Specific Sync & Conflict Resolution
- Build on top of
@suaveplan/crdtand@suaveplan/diff(JSON Patch). - Vault item merge strategies: last-write-wins for passwords, append-only for history, atomic for moves.
5.3 @suaveplan/vault — Vault Orchestration Layer (see Section 7)
6. Storage Architecture — Unifying Drive and Storage
6.1 Current Problem
@suaveplan/drive (~7,300 LOC, 127 files) is a mature virtual filesystem with encryption transforms, chunking, deduplication, snapshots, COW, WAL, and journaling. But it only ships with MemoryStorage.
@suaveplan/storage is a lightweight key-value abstraction with actual persistence backends:
@suaveplan/storage-file(Node.js/Bun filesystem)@suaveplan/storage-browser(IndexedDB + localStorage)@suaveplan/storage-browser-opfs(Origin Private File System)@suaveplan/storage-s3(S3-compatible object storage)
The two ecosystems are disconnected. Drive has its own StorageAdapter interface that is incompatible with storage's AbstractStorage.
6.2 The Fix: Layered Architecture
Do not make storage subservient to drive. They serve different abstraction levels:
Layer 3: @suaveplan/vault (orchestration)
Layer 2: @suaveplan/drive (filesystem: encryption, chunking, snapshots, POSIX API)
Layer 1: @suaveplan/storage-* (persistence backends: file, S3, IndexedDB, OPFS)
Drive sits on top of storage backends. Storage is the raw persistence substrate.
6.3 Required Enhancements to Storage Providers
Drive's StorageAdapter needs capabilities that AbstractStorage doesn't currently expose:
| Capability | Status | Implementation |
|---|---|---|
get(key) |
✅ Exists | — |
set(key, value) |
✅ Exists | — |
delete(key) |
✅ Exists | — |
exists(key) |
✅ Exists | — |
list({ prefix }) |
✅ Exists | — |
getPartial(key, offset, len) |
❌ Missing | Add to AbstractStorage; emulate via get + slice for providers that can't do native partial reads |
getItemSize(key) |
❌ Missing | Add to AbstractStorage; store size in metadata or compute on demand |
createReadStream(key) |
❌ Missing | Add to AbstractStorage; emulate for providers without native streaming |
createWriteStream(key) |
❌ Missing | Add to AbstractStorage; emulate via buffering |
Effort: ~20-50 lines per provider. file and opfs can implement natively. indexeddb, s3, localstorage can emulate.
6.4 The Universal Drive Adapter
Create one adapter in drive that wraps any storage provider:
// @suaveplan/drive/src/adapters/storage-adapter.ts
export class StorageProviderAdapter extends StorageAdapter<StorageProviderConfig> {
private storage: AbstractStorage;
async connect() {
this.storage = StorageFactory.create(this.config.provider);
this._connected = true;
}
async read(key: string): Promise<Uint8Array> {
const data = await this.storage.get(key);
if (!data) throw new Error(`Key not found: ${key}`);
return data;
}
async readPartial(key: string, offset: number, length: number): Promise<Uint8Array> {
if ('getPartial' in this.storage) {
return (this.storage as any).getPartial(key, offset, length);
}
const full = await this.read(key);
return full.slice(offset, offset + length);
}
async write(key: string, data: BufferLike): Promise<void> {
await this.storage.set(key, toUint8Array(data));
}
async size(key: string): Promise<number> {
if ('getItemSize' in this.storage) {
return (this.storage as any).getItemSize(key);
}
const data = await this.storage.get(key);
return data?.length ?? 0;
}
// ... exists, delete, list, streaming
}
Then configureDrive() accepts storage provider configs directly:
const drive = await configureDrive({
storage: {
type: 'storage-provider',
provider: { type: 'file', config: { basePath: './data' } }
},
encryption: { keyLength: 256 },
});
6.5 Factory Pattern Unification
Backport storage's module-augmentation factory pattern into drive's adapter registry:
// @suaveplan/storage-file/index.ts
import { StorageFactory } from '@suaveplan/storage';
import { DriveAdapterFactory } from '@suaveplan/drive';
StorageFactory.register('file', FileSystem);
DriveAdapterFactory.register('file', (config) => new StorageProviderAdapter(config));
Installing @suaveplan/storage-file automatically makes type: 'file' available to both storage and drive.
6.6 Migration Checklist for Storage
| Step | Action | Effort |
|---|---|---|
| 1 | Add getPartial(), getItemSize(), createReadStream(), createWriteStream() to AbstractStorage |
~1h |
| 2 | Implement enhanced methods in storage-file, storage-browser, storage-opfs |
~2h |
| 3 | Create StorageProviderAdapter in drive |
~1h |
| 4 | Wire StorageFactory auto-registration into drive's config |
~1h |
| 5 | Backport module-augmentation pattern to drive adapter registry | ~1h |
| 6 | Deprecate drive's standalone MemoryStorage in favor of adapter wrapping storage memory provider |
~30m |
Total: ~6-7 hours.
7. @suaveplan/vault — The Orchestration Layer
7.1 The Problem
@suaveplan/keys currently wears two hats:
- Key material management (keypair generation, validation, keychain, chain signing) — correct scope
- Vault container management (VaultFactory, VaultManager, packVault, unpackVault, password-based encryption) — scope creep
7.2 Proposed Boundaries
| Package | Scope |
|---|---|
@suaveplan/keys |
Keypair generation, validation, keychain storage, chain signing. No vault logic. |
@suaveplan/crypto-vault |
BIP39 mnemonics, Argon2id HD key derivation, 2SKD (password + Secret Key → AUK), SRP-6a, key hierarchy (AUK → personal keyset → vault key → item key) |
@suaveplan/vault (new) |
Orchestrates crypto-vault + keys + db-sqlite + drive + drive-sharing into a single "vault" abstraction |
7.3 What Moves Where
Stays in @suaveplan/keys:
generateKeypair(),validateKeypair(),assertValidKeypair()Keychainclass and adapters (MemoryKeychainAdapter,SerializedKeychainAdapter)ChainedKeySigner- Key-type definitions (
MasterKey,EncryptKey,SignKey,KxKey)
Moves OUT of @suaveplan/keys → @suaveplan/vault:
VaultManager→VaultVaultFactory→VaultFactorypackVault()/unpackVault()→Vault.serialize()/Vault.deserialize()createRootKeys()/createServerKeys()→@suaveplan/crypto-vault(orvault)
7.4 Vault API
import { Vault } from '@suaveplan/vault';
const vault = await Vault.open({
path: '/Users/alice/.vaulted/vault',
password: '...',
secretKey: 'A3-XXXXXX-...',
email: 'alice@example.com',
});
await vault.unlock();
// Item CRUD (delegates to db-sqlite)
const login = await vault.items.create({
category: 'login',
title: 'GitHub',
fields: [
{ type: 'username', value: 'alice' },
{ type: 'password', value: '...', concealed: true },
{ type: 'url', value: 'https://github.com' },
],
});
// Search (delegates to db-sqlite FTS5)
const results = await vault.items.search({ query: 'github' });
// Attachments (delegates to drive)
await vault.attachments.upload(login.id, '/path/to/ssh-key.pem');
// Sharing (delegates to drive-sharing)
const link = await vault.shares.createLink({
itemId: login.id,
expiresIn: '7d',
maxViews: 5,
});
// Sync (delegates to sync engine)
await vault.sync.pull();
await vault.sync.push();
// Lock (zeroizes keys, closes DB)
await vault.lock();
7.5 Internal Structure
class Vault {
private keyVault: MasterKeyVault; // from @suaveplan/crypto-vault
private itemRepo: ItemRepository; // uses @suaveplan/db-sqlite
private fileManager: FileManager; // uses @suaveplan/drive
private shareManager: ShareManager; // uses @suaveplan/drive-sharing
private syncEngine: SyncEngine; // new, uses @suaveplan/api + ws
async unlock(password: string, secretKey: string) {
// 1. Derive AUK via crypto-vault (2SKD)
// 2. Decrypt personal keyset
// 3. Decrypt vault keys
// 4. Open SQLite DB with vault key
// 5. Open drive with vault key
}
async lock() {
// 1. Close SQLite
// 2. Destroy drive session keys
// 3. Zeroize all key material in crypto-vault
}
}
7.6 @suaveplan/drive-sharing Complementarity
drive-sharing is not replaced by vault. They layer:
| Package | Scope |
|---|---|
drive-sharing |
Primitives: envelope encryption, partition stores, grant tokens, share links |
vault |
Orchestration: "share this login item" → extracts from DB → encrypts via drive-sharing → uploads via drive |
vault is the password-manager domain layer. drive-sharing is the secure-sharing primitive layer.
8. External Dependencies — Keep vs Build
| Need | Verdict | External | Rationale |
|---|---|---|---|
| SRP-6a | Build | None good | Critical, well-defined protocol |
| TOTP/HOTP | Build | otpauth pulls own crypto |
Small spec, reuse our hash primitives |
| WebAuthn client | Build | @simplewebauthn has deps |
Needs our crypto + new CBOR package |
| CBOR | Build | cbor-x is heavy |
Small spec, unlocks WebAuthn |
| Password strength | Build | zxcvbn is 800KB |
Two-tier: light core + optional dictionaries |
| Form detection | Build | None standalone | Proprietary in competing products |
| Site icons | Build | None good | Domain-specific caching + fetch logic |
| HIBP client | Build | None maintained | Small, privacy-critical wrapper |
| QR scan | Build/extend | jsQR is decent |
Extend existing @suaveplan/qrcode |
| WebExtension polyfill | Keep external | webextension-polyfill |
Mozilla-maintained, 15KB, excellent |
| PDF generation | Wrap external | pdf-lib |
Universal, tree-shakeable, ~100KB |
| Image compression | Keep external | sharp (Node), Capacitor plugins |
Native bindings are hard to beat |
| SQLCipher | Keep external | sqlcipher native |
C library, hard to replace in pure TS |
| Time sync (NTP) | Inline | None needed | ~50 lines of UDP code |
| YubiKey/WebHID | Defer | Platform-specific | Hardware integration, post-MVP |
9. Migration Sequencing for Tonight
Phase 1: Storage Bridge (6-7 hours)
- Enhance
AbstractStoragewithgetPartial(),getItemSize(), streaming - Implement in
storage-file,storage-browser,storage-opfs - Create
StorageProviderAdapterindrive - Wire auto-registration so storage providers are available to drive
- Test:
configureDrive({ storage: { type: 'file', ... }})works end-to-end
Phase 2: Vault Rearchitecture (4-5 hours)
- Strip vault logic from
@suaveplan/keys(move to@suaveplan/vault) - Ensure
@suaveplan/crypto-vaultowns key hierarchy + 2SKD - Scaffold
@suaveplan/vaultwithVault.open(),unlock(),lock() - Wire
vault.items→db-sqlite,vault.attachments→drive
Phase 3: Critical Crypto Packages (5-6 hours)
@suaveplan/cbor— encoder/decoder@suaveplan/srp— SRP-6a client@suaveplan/otp— TOTP/HOTP generation@suaveplan/webauthn— WebAuthn client (depends on CBOR)
Phase 4: Extension Infrastructure (3-4 hours)
@suaveplan/native-messaging— encrypted transport@suaveplan/form-detection— DOM heuristics@suaveplan/password-strength— heuristic + dictionary scorer
Phase 5: Utilities (2-3 hours)
@suaveplan/site-icons— favicon fetch/cache@suaveplan/hibp— breach monitoring client@suaveplan/qrcode-scan— QR decode subpath@suaveplan/biometrics— universal biometric facade@suaveplan/iconz— migrate from npm, integrate into build pipelines
Total estimate: 20-25 hours across all phases. Not a single night, but Phase 1+2 (storage bridge + vault rearchitecture) is the critical foundation and can be done in one focused session.
10. Security Invariants
These must hold across all packages:
- Key material never leaves
@suaveplan/crypto-vault/@suaveplan/keysmemory space.vault,drive,db-sqliteonly receive derived encryption keys, not the AUK or mnemonic. - All key material is zeroized on
vault.lock(). Use@suaveplan/crypto-utilszeroize()onUint8Arrayinstances. - Decryption happens exclusively on the client. The sync server only ever sees encrypted blobs.
- Biometric data never leaves the hardware secure enclave. Platform APIs return boolean only.
- Extension crypto is delegated to the desktop app. Browser extensions never hold vault keys.
11. References
../vaulted/REPORT.md— 1Password architecture analysis../vaulted/apps/desktop/REPORT.md— Electrobun desktop build spec../vaulted/apps/mobile/REPORT.md— Capacitor mobile build spec../vaulted/plugins/chrome/REPORT.md— Chrome MV3 extension spec../vaulted/plugins/firefox/REPORT.md— Firefox MV2 extension spec../vaulted/plugins/safari/REPORT.md— Safari App Extension spec../suaveplan/README.md— Full package inventory (356 packages)STATUS.md— Dashboard table of every package's migration statusSUMMARY.md— Per-package summaries (completed / pending / genesis-original)