Files
suaveplan/MIGRATION.md
2026-05-27 23:29:53 +01:00

467 lines
20 KiB
Markdown

# 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/drive` and `@suaveplan/storage-*`
- **Vault orchestration layer** — the `@suaveplan/vault` package 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-codes` detects 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.credentials` for browser extensions.
- **Dependencies:** `@suaveplan/crypto-curve`, `@suaveplan/crypto-hash`, `@suaveplan/cbor`
- **Note:** `@suaveplan/crypto-browser` provides 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 `NSExtensionContext` behind 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-codes` has 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/qrcode` is **generation-only**. This adds the decode/scan subpath.
- **Decision:** Either extend `@suaveplan/qrcode` with `/scan` subpath 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/random` has `randomString` but no password-specific logic.
### 5.2 `@suaveplan/vault-sync` — Domain-Specific Sync & Conflict Resolution
- Build on top of `@suaveplan/crdt` and `@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:
```text
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:
```typescript
// @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:
```typescript
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:
```typescript
// @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:
1. **Key material management** (keypair generation, validation, keychain, chain signing) — correct scope
2. **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()`
- `Keychain` class and adapters (`MemoryKeychainAdapter`, `SerializedKeychainAdapter`)
- `ChainedKeySigner`
- Key-type definitions (`MasterKey`, `EncryptKey`, `SignKey`, `KxKey`)
**Moves OUT of `@suaveplan/keys` → `@suaveplan/vault`:**
- `VaultManager` → `Vault`
- `VaultFactory` → `VaultFactory`
- `packVault()` / `unpackVault()` → `Vault.serialize()` / `Vault.deserialize()`
- `createRootKeys()` / `createServerKeys()` → `@suaveplan/crypto-vault` (or `vault`)
### 7.4 Vault API
```typescript
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
```typescript
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)
1. Enhance `AbstractStorage` with `getPartial()`, `getItemSize()`, streaming
2. Implement in `storage-file`, `storage-browser`, `storage-opfs`
3. Create `StorageProviderAdapter` in `drive`
4. Wire auto-registration so storage providers are available to drive
5. Test: `configureDrive({ storage: { type: 'file', ... }})` works end-to-end
### Phase 2: Vault Rearchitecture (4-5 hours)
1. Strip vault logic from `@suaveplan/keys` (move to `@suaveplan/vault`)
2. Ensure `@suaveplan/crypto-vault` owns key hierarchy + 2SKD
3. Scaffold `@suaveplan/vault` with `Vault.open()`, `unlock()`, `lock()`
4. Wire `vault.items` → `db-sqlite`, `vault.attachments` → `drive`
### Phase 3: Critical Crypto Packages (5-6 hours)
1. `@suaveplan/cbor` — encoder/decoder
2. `@suaveplan/srp` — SRP-6a client
3. `@suaveplan/otp` — TOTP/HOTP generation
4. `@suaveplan/webauthn` — WebAuthn client (depends on CBOR)
### Phase 4: Extension Infrastructure (3-4 hours)
1. `@suaveplan/native-messaging` — encrypted transport
2. `@suaveplan/form-detection` — DOM heuristics
3. `@suaveplan/password-strength` — heuristic + dictionary scorer
### Phase 5: Utilities (2-3 hours)
1. `@suaveplan/site-icons` — favicon fetch/cache
2. `@suaveplan/hibp` — breach monitoring client
3. `@suaveplan/qrcode-scan` — QR decode subpath
4. `@suaveplan/biometrics` — universal biometric facade
5. `@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:
1. **Key material never leaves `@suaveplan/crypto-vault` / `@suaveplan/keys` memory space.** `vault`, `drive`, `db-sqlite` only receive derived encryption keys, not the AUK or mnemonic.
2. **All key material is zeroized on `vault.lock()`.** Use `@suaveplan/crypto-utils` `zeroize()` on `Uint8Array` instances.
3. **Decryption happens exclusively on the client.** The sync server only ever sees encrypted blobs.
4. **Biometric data never leaves the hardware secure enclave.** Platform APIs return boolean only.
5. **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 status
- `SUMMARY.md` — Per-package summaries (completed / pending / genesis-original)