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

6.8 KiB

OpenSpec Change: qrcode-scan

Package: @suaveplan/qrcode (adds /scan subpath) Status: PROPOSED Type: feature

Why

@suaveplan/qrcode is currently generation-only. Vaulted needs to import TOTP secrets by scanning QR codes from camera streams or image uploads. Rather than creating a standalone package that would duplicate QR matrix data structures, the decision is to extend the existing @suaveplan/qrcode package with a tree-shakeable /scan subpath.

What changes

  • ADDED: src/scan/scan.ts — scanFromImageData(imageData) decodes a QR code from ImageData, Uint8Array, Blob, or File
  • ADDED: src/scan/scan-camera.ts — scanFromCameraStream(stream, options?) extracts frames from a MediaStream and attempts decode
  • ADDED: src/scan/totp-extract.ts — extractTotpUri(data) parses otpauth://totp/ URIs into structured config
  • ADDED: src/scan/totp-types.ts — TotpConfig, TotpAlgorithm, OtpAuthUri
  • ADDED: Subpath export ./scan/scan in package.json
  • ADDED: QrScanError extends QrError (code QRCODE.DECODE_FAILED)
  • ADDED: CameraAccessError extends QrScanError (code QRCODE.CAMERA_ACCESS_DENIED)
  • ADDED: TotpUriParseError extends QrScanError (code QRCODE.TOTP_PARSE_ERROR)
  • ADDED: Injectable clock?: Clock in scanFromCameraStream options for deterministic frame-interval timing in tests
  • ADDED: Zod schemas for TotpConfig and camera options
  • ADDED: OTel counter qrcode.scan.total (by source, outcome) and histogram qrcode.scan.duration_ms

Out of scope

  • Real-time continuous scanning UI components (Vaulted app concern, not library)
  • Barcode formats other than QR (use @suaveplan/barcode)
  • Video recording, snapshot capture, or <canvas> rendering helpers (consume browser APIs directly)
  • Native camera integration on mobile (Capacitor Camera plugin is a separate abstraction layer)

Gap analysis: sub-standard → enterprise

Most QR scanning libraries in the npm ecosystem pull in heavy WebAssembly binaries or camera UI widgets that bloat bundles and tie the code to browser-only environments. They often lack frame-rate throttling, leak MediaStream resources on unmount, and provide no structured extraction for TOTP URIs. Enterprise-grade code must keep the decoder lightweight and tree-shakeable, manage camera lifecycle explicitly, and return strongly-typed TOTP config rather than raw strings.

Security model

  • Threat model: Malicious QR payloads exploiting URI parser bugs to inject JavaScript or redirect to phishing domains; camera stream frames intercepted by compromised browser extensions; resource exhaustion via high-frequency decode loops.
  • Privacy guarantees: Camera frames are processed in-memory and never persisted to disk or transmitted. TOTP secrets extracted from QR codes are returned to the consumer without logging or retention.
  • Input validation: otpauth:// URIs validated against strict Zod schema before returning TotpConfig; unknown URI schemes rejected explicitly. Rejects javascript: and other non-otpauth: schemes in parsed URIs.
  • Side-channel resistance: Decode timing is not correlated with secret content; constant-time URI component extraction where feasible.
  • Key material handling: Extracted TOTP secrets are handled as opaque strings and passed immediately to the consumer; no key derivation or storage occurs in this package.

Performance SLOs

  • Latency: P95 single-frame decode < 100ms on a mid-tier laptop; camera stream first detection < 2s.
  • Throughput: Sustained 30 fps frame sampling without dropping frames or blocking the main thread.
  • Memory bounds: Peak memory per decode < 2 MB; camera stream buffer bounded by frame queue depth.
  • Bundle size budget: Scan subpath < 25 kB gzipped (decoder + TOTP extractor); no WASM or heavy CV dependencies.

Observability plan

  • OTel spans/metrics: Counter qrcode.scan.total by source and outcome; histogram qrcode.scan.duration_ms; gauge for camera stream active duration.
  • Structured logging: JSON logs at warn for camera permission denial, error for decode failures, debug for successful scans.
  • Error telemetry: Distinct codes (CAMERA_ACCESS_DENIED, TOTP_PARSE_ERROR) with platform context and frame source.
  • Health checks: Expose scanFromCameraStream.isHealthy() returning false after repeated decode failures or stream errors.

Dependency audit

  • Runtime deps: @suaveplan/qrcode (shared matrix types), @suaveplan/errors, @suaveplan/otel; decoder is pure TypeScript with no heavy CV libraries.
  • Supply-chain risk: No WASM binaries or native modules; all dependencies are first-party or platform APIs.
  • Tree-shakeability: scanFromImageData, scanFromCameraStream, and extractTotpUri are individually tree-shakeable.
  • License: All code MIT-licensed; no copyleft dependencies.

Operational readiness

  • Graceful degradation: If camera access is denied, the consumer can fall back to file upload. If decode fails, return a typed error rather than throwing.
  • Resource limits: Frame extraction rate limited by configurable throttle; max decode attempts per stream bounded.
  • Error recovery: Automatic MediaStream cleanup on unmount or error; retry with backoff on transient camera glitches.
  • Backpressure: Frame queue drops oldest frames when decoder is slower than capture rate; prevents unbounded memory growth.
  • Version compatibility: Public API frozen after v1; additive-only changes; deprecated APIs supported for two major versions.

Verification approach

  • 100% coverage target: All exported functions, branches, and error paths instrumented in test.
  • Property-based tests: QR payload generation, URI parsing edge cases, and frame buffer backpressure tested with fast-check invariants.
  • Load/stress criteria: Sustained 30 fps decode for 60s without memory growth > 10% or frame drops > 1%.
  • Security audit checklist: Malformed URI injection, oversized image payload handling, camera stream cleanup on error, secret leakage in logs.
  • src/scan/scan.test.ts — covers decode from ImageData, Uint8Array, File, no-QR error
  • src/scan/scan-camera.test.ts — covers frame extraction, permission denial, stream ended
  • src/scan/totp-extract.test.ts — covers valid otpauth URI, missing secret, malformed URI
  • src/scan/scan.spec-conformance.test.ts — pins type-level contracts, strict-mode behaviors, OTel emission
  • tests/dep-graph.test.ts — pins dependency invariants

Migration impact

None — additive only. Existing @suaveplan/qrcode consumers are unaffected because all new code is behind the ./scan/scan subpath export.

Impact

Downstream consumers

Run bunx turbo ls --filter=...@suaveplan/qrcode to enumerate.

Breaking changes

None — additive only.

Workspace dep prerequisites

  • @suaveplan/errors
  • @suaveplan/otel