7.9 KiB
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:
BiometricAuthclass — universal facade with auto-detected or injected delegate - ADDED:
authenticate(reason)— returnsPromise<boolean>(never biometric data) - ADDED:
getAvailability()— returnsBiometricAvailability - ADDED:
BiometricDelegateinterface — 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-keychainbiometric APIs - ADDED:
NoOpBiometricDelegate— always returnsfalse; 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:
BiometricErrorextendsSuaveplanError - ADDED:
BiometricNotAvailableErrorextendsBiometricError(codeBIOMETRIC.NOT_AVAILABLE) - ADDED:
BiometricHardwareErrorextendsBiometricError(codeBIOMETRIC.HARDWARE_ERROR) - ADDED: Injectable
clock?: ClockinBiometricAuthoptions for deterministic timeout testing - ADDED: Zod schemas for availability and options
- ADDED: OTel counter
biometric.authenticate.total(byoutcome) and histogrambiometric.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-browserand 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:
reasonstring length bounded and sanitized; delegate implementations validated at injection time against theBiometricDelegateinterface 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-browserand platform plugins.
Performance SLOs
- Latency: P95
authenticateprompt-to-result < 2s on supported hardware; P95getAvailability< 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.totalbyoutcome; histogrambiometric.authenticate.duration_ms; gauge for availability state transitions. - Structured logging: JSON logs at
infofor authentication attempts,warnfor unavailability,errorfor 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:
NoOpBiometricDelegateensures unsupported platforms receivefalserather 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 toNoOpBiometricDelegateto avoid crashes in browser environments withoutprocess.
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
BiometricDelegateinterface 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— coversauthenticatesuccess, failure, cancellation, delegate injectionsrc/platform/platform.test.ts— covers platform auto-detection, unsupported platform fallbacksrc/delegate/delegate.test.ts— covers every delegate implementation contractsrc/availability/availability.test.ts— coversgetAvailabilitymappingsrc/errors/errors.spec-conformance.test.ts— pins error hierarchies and strict-mode behaviorstests/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