/** * Dev-mode persistent unlock — * * Solves the developer inner-loop friction: hot-reload destroys the session * (page navigation semantics), forcing a passphrase re-entry every refresh. * * This module provides an opt-in, deliberately-named escape hatch that lets * developers store the keyring payload in sessionStorage or localStorage so * the vault auto-unlocks on every page load — without a passphrase, * without a biometric prompt, without any OIDC flow. * * ⚠️ WARNING — this is a loaded footgun ⚠️ * ───────────────────────────────────────── * The keyring payload stored by this module contains the DEKs. Whoever has * access to sessionStorage/localStorage has access to the DEKs. On a shared * development machine, a compromised browser extension, or a mis-configured * origin, this is a complete key exposure. * * This module is ONLY safe for local development. It must NEVER be active * in production builds. * * Guardrails (all enforced by the module, not by the caller) * ────────────────────────────────────────────────────────── * 1. **Production guard:** `enableDevUnlock()` throws immediately if * `process.env.NODE_ENV === 'production'` or if `import.meta.env?.PROD === true` * (Vite convention). Also throws if the hostname is NOT localhost or 127.0.0.1. * * 2. **Explicit acknowledgement string:** the caller must pass * `acknowledge: 'I-UNDERSTAND-THIS-DISABLES-UNLOCK-SECURITY'` or the call * throws. This string appears in every grep for `devUnlock` in the codebase, * making it impossible to enable this feature accidentally. * * 3. **Scope is vault + userId:** the storage key includes both the * vault name and the userId, so dev-unlock for vault-A does * NOT auto-unlock vault-B. * * 4. **Storage scope:** default is `sessionStorage` (cleared on tab close). * `localStorage` is opt-in and requires an additional * `persistAcrossTabs: true` flag in the options. * * 5. **Clear method:** `clearDevUnlock()` removes the stored payload. Wire * this to a dev toolbar button or `Ctrl+Shift+L` so clearing is one action. * * 6. **Console banner:** on first enable, a highly visible console warning * fires. Cannot be suppressed. * * Usage * ───── * ```ts * // In your dev entry point only (guarded by import.meta.env.DEV): * if (import.meta.env.DEV) { * const { enableDevUnlock, loadDevUnlock } = await import('@noy-db/hub') * enableDevUnlock('my-compartment', 'alice', keyring, { * acknowledge: 'I-UNDERSTAND-THIS-DISABLES-UNLOCK-SECURITY', * }) * } * * // On page load: * if (import.meta.env.DEV) { * const keyring = await loadDevUnlock('my-compartment', 'alice') * if (keyring) { * // Skip unlock prompt, use keyring directly * } * } * ``` */ import type { UnlockedKeyring } from '../team/keyring.js'; export interface DevUnlockOptions { /** * Required: the exact string 'I-UNDERSTAND-THIS-DISABLES-UNLOCK-SECURITY'. * Any other value causes `enableDevUnlock()` to throw. */ acknowledge: string; /** * If `true`, stores in localStorage (persists across tabs and browser restarts). * If `false` (default), stores in sessionStorage (cleared on tab close). */ persistAcrossTabs?: boolean; } /** * Serialize and store a keyring to browser storage for dev-mode auto-unlock. * * Throws immediately if: * - The acknowledge string is wrong. * - Running in a production environment (NODE_ENV=production). * - Running on a non-localhost hostname. * * Emits a highly visible console warning that cannot be suppressed. * * @param vault - The vault name. * @param userId - The user ID. * @param keyring - The unlocked keyring to persist. * @param options - Options including the required acknowledge string. */ export declare function enableDevUnlock(vault: string, userId: string, keyring: UnlockedKeyring, options: DevUnlockOptions): Promise; /** * Load a dev-mode keyring from browser storage. * * Returns `null` if no dev-unlock state is stored for this vault + user, * or if the stored payload is malformed. * * Does NOT perform the production environment check — it's safe to CALL * `loadDevUnlock` in production (it will simply return `null` because no * dev-unlock state was ever written). The guard only fires on `enableDevUnlock`. * * @param vault - The vault name. * @param userId - The user ID. * @param options - Optional storage override. */ export declare function loadDevUnlock(vault: string, userId: string, options?: { persistAcrossTabs?: boolean; }): Promise; /** * Remove dev-unlock state from browser storage. * * Safe to call in production (no-op if no dev state exists). */ export declare function clearDevUnlock(vault: string, userId: string, options?: { persistAcrossTabs?: boolean; }): void; /** * Check if dev-unlock state exists for this vault + user. * * Safe to call in production (returns false if nothing is stored). */ export declare function isDevUnlockActive(vault: string, userId: string, options?: { persistAcrossTabs?: boolean; }): boolean;