Files
2026-05-27 23:29:53 +01:00

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: BiometricAuth class — universal facade with auto-detected or injected delegate
  • ADDED: authenticate(reason) — returns Promise<boolean> (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