# OpenSpec Change: biometrics **Package**: `@suaveplan/biometrics` **Status**: PROPOSED **Type**: new-package ## Why Vaulted needs biometric authentication across desktop (Windows Hello, Touch ID via Electrobun), mobile (Face ID, Android BiometricPrompt via Capacitor), and macOS (Touch ID via `@suaveplan/macos-keychain`). Platform APIs are fragmented and inconsistent; a thin universal facade ensures the Vaulted app and extensions use a single interface regardless of runtime. ## What changes - ADDED: `BiometricAuth` class — universal facade with auto-detected or injected delegate - ADDED: `authenticate(reason)` — returns `Promise` (never biometric data) - ADDED: `getAvailability()` — returns `BiometricAvailability` - ADDED: `BiometricDelegate` interface — platform implementers implement this - ADDED: `CapacitorBiometricDelegate` — delegates to Capacitor Local Authentication plugin - ADDED: `ElectrobunBiometricDelegate` — delegates to Electrobun native bridge - ADDED: `MacosKeychainBiometricDelegate` — delegates to `@suaveplan/macos-keychain` biometric APIs - ADDED: `NoOpBiometricDelegate` — always returns `false`; used on unsupported platforms - ADDED: `BiometricAvailability` — `{ available: boolean; type?: BiometricType; reason?: UnavailabilityReason }` - ADDED: `BiometricType` — `'touchId' | 'faceId' | 'fingerprint' | 'iris' | 'unknown'` - ADDED: `UnavailabilityReason` — `'noHardware' | 'notEnrolled' | 'notSupported' | 'permissionDenied' | 'unknown'` - ADDED: `BiometricError` extends `SuaveplanError` - ADDED: `BiometricNotAvailableError` extends `BiometricError` (code `BIOMETRIC.NOT_AVAILABLE`) - ADDED: `BiometricHardwareError` extends `BiometricError` (code `BIOMETRIC.HARDWARE_ERROR`) - ADDED: Injectable `clock?: Clock` in `BiometricAuth` options for deterministic timeout testing - ADDED: Zod schemas for availability and options - ADDED: OTel counter `biometric.authenticate.total` (by `outcome`) and histogram `biometric.authenticate.duration_ms` ## Out of scope - Biometric enrollment — OS-level concern, not a library responsibility - Secure enclave key generation or keypair creation — defer to `@suaveplan/crypto-browser` and platform plugins - Fallback to device PIN / password — platform-specific behavior, deferred to consumer - WebAuthn / passkey biometric flows — handled by `@suaveplan/webauthn` ## Gap analysis: sub-standard → enterprise Consumer-grade biometric libraries often capture, buffer, or even persist raw biometric data (fingerprints, face geometry) in application memory, violating platform security guidelines and creating catastrophic privacy risk if the device is compromised. They also fail to provide a structured fallback when hardware is unavailable or not enrolled, leaving apps to crash or hang. Enterprise-grade code must never touch raw biometric templates; it must delegate entirely to OS secure enclaves, surface clear availability signals, and offer a deterministic `NoOp` fallback for unsupported platforms. ## Security model - **Threat model**: Compromised host attempting to replay biometric authentication signals; malicious delegates intercepting or spoofing platform responses; side-channel leakage via timing of availability checks. - **Privacy guarantees**: Raw biometric data is never read, stored, or transmitted by this library. All biometric matching occurs inside the platform secure enclave / TEE. Only a boolean result is returned to the application. - **Input validation**: `reason` string length bounded and sanitized; delegate implementations validated at injection time against the `BiometricDelegate` interface contract. - **Side-channel resistance**: Availability checks and authentication calls use constant-time paths where feasible; no correlation between vault item access and biometric prompt timing. - **Key material handling**: No cryptographic keys are derived from biometric data in this package. Key generation is explicitly out of scope and deferred to `@suaveplan/crypto-browser` and platform plugins. ## Performance SLOs - **Latency**: P95 `authenticate` prompt-to-result < 2s on supported hardware; P95 `getAvailability` < 50ms. - **Throughput**: Serial by design — biometric prompts are user-interactive and never batched. - **Memory bounds**: Peak memory < 256 KB per authentication attempt; no image or template buffering. - **Bundle size budget**: Core facade + delegates < 10 kB gzipped; platform-specific delegates tree-shakeable. ## Observability plan - **OTel spans/metrics**: Counter `biometric.authenticate.total` by `outcome`; histogram `biometric.authenticate.duration_ms`; gauge for availability state transitions. - **Structured logging**: JSON logs at `info` for authentication attempts, `warn` for unavailability, `error` for delegate failures; never logs biometric type as a personal identifier. - **Error telemetry**: Distinct codes (`NOT_AVAILABLE`) with platform context and delegate name (anonymized where required). - **Health checks**: Expose `BiometricAuth.isHealthy()` returning false if the active delegate throws repeatedly. ## Dependency audit - **Runtime deps**: `@suaveplan/errors`, `@suaveplan/otel`, and platform-specific peer dependencies (Capacitor, Electrobun, macOS keychain) that are injected, not bundled. - **Supply-chain risk**: No biometric SDKs from unvetted vendors; all delegates are first-party wrappers around platform APIs. - **Tree-shakeability**: `BiometricAuth`, each delegate, and availability utilities are individually tree-shakeable. - **License**: All first-party code MIT-licensed; platform plugins retain their original licenses and are peer dependencies. ## Operational readiness - **Graceful degradation**: `NoOpBiometricDelegate` ensures unsupported platforms receive `false` rather than exceptions; consumers can cascade to PIN/password. - **Resource limits**: Authentication timeout enforced by platform delegate; internal timeout as a failsafe. - **Error recovery**: Delegate exceptions are caught and mapped to `BiometricError`; repeated failures trigger health-check degradation. - **Backpressure**: Not applicable — biometric operations are user-serialized; concurrent calls are queued and deduplicated. - **Version compatibility**: Public API frozen after v1; additive-only changes; deprecated delegates supported for two major versions. - **Platform safety**: Runtime platform detection uses `typeof process !== 'undefined' && process.platform === 'darwin'` with safe fallback to `NoOpBiometricDelegate` to avoid crashes in browser environments without `process`. ## Verification approach - **100% coverage target**: All exported functions, branches, and error paths instrumented in test. - **Property-based tests**: Availability state machine transitions, delegate injection contracts, and error code coverage tested with fast-check invariants. - **Load/stress criteria**: 100 sequential authenticate calls without memory growth > 5%; no handle leaks in test doubles implementing the `BiometricDelegate` interface with deterministic responses. - **Security audit checklist**: Verify no raw biometric data in logs or memory; confirm delegate sandboxing; validate timeout and cancellation paths. - `src/auth/auth.test.ts` — covers `authenticate` success, failure, cancellation, delegate injection - `src/platform/platform.test.ts` — covers platform auto-detection, unsupported platform fallback - `src/delegate/delegate.test.ts` — covers every delegate implementation contract - `src/availability/availability.test.ts` — covers `getAvailability` mapping - `src/errors/errors.spec-conformance.test.ts` — pins error hierarchies and strict-mode behaviors - `tests/dep-graph.test.ts` — pins dependency invariants ## Migration impact None — additive only. ## Impact ### Downstream consumers No dependents (verified by `bunx turbo ls --filter=...@suaveplan/biometrics`). ### Breaking changes None — additive only. ### Workspace dep prerequisites - `@suaveplan/errors` - `@suaveplan/otel`