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

8.3 KiB

OpenSpec Change: native-messaging

  • Change ID: native-messaging
  • Package: @suaveplan/native-messaging
  • Status: PROPOSED
  • Date: 2026-04-29

Summary

Create @suaveplan/native-messaging, a typed, encrypted transport layer between browser extensions and the desktop vault application. The package abstracts Chrome stdio, Firefox stdio, and Safari NSExtensionContext behind a single interface, using X25519 key exchange for handshake and ChaCha20-Poly1305 for per-frame encryption.

Why

The Vaulted password-manager architecture requires browser extensions to communicate with the desktop app without exposing vault keys inside the extension runtime. No existing Suaveplan package provides a cross-browser native-messaging abstraction with built-in encryption. External libraries either lack typed message framing or bundle their own crypto primitives, which violates the monorepo's policy of reusing @suaveplan/crypto-curve and @suaveplan/crypto-cipher.

What changes

  • ADDED: NativeMessagingClient — platform-agnostic encrypted channel client used by browser extension background scripts.
  • ADDED: NativeMessagingHost — desktop-side encrypted channel host accepting connections from extensions.
  • ADDED: Handshake — X25519 ephemeral key exchange producing a shared secret and session keys.
  • ADDED: FrameEncoder / FrameDecoder — length-prefixed, ChaCha20-Poly1305-encrypted message framing with AAD.
  • ADDED: ChromeTransport, FirefoxTransport, SafariTransport — platform-specific transport adapters behind a common Transport interface.
  • ADDED: TypedMessage<T> — discriminated-union envelope for request/response/error messages with Zod schema validation at boundaries.
  • ADDED: ReconnectPolicy — configurable exponential-backoff reconnection with jitter and max-retry caps.
  • ADDED: NativeMessagingError — typed error hierarchy (HandshakeFailed, FrameCorrupted, TransportClosed, Timeout) with error codes.

Out of scope

  • Mobile Capacitor bridge communication — mobile uses in-process bridging, not native messaging.
  • Biometric unlock flow integration — @suaveplan/biometrics will orchestrate that layer.
  • Vault item CRUD logic — belongs in @suaveplan/vault.
  • WebSocket or WebRTC fallbacks — separate transport packages already exist in the monorepo.
  • Extension content-script injection — handled by @suaveplan/form-detection and extension build pipelines.

Gap analysis: sub-standard → enterprise

Most browser-extension ↔ native-application bridges ship plaintext JSON over stdio with no authentication, no replay protection, and no message-level typing. This leaves them vulnerable to man-in-the-browser attacks, host-process impersonation, and trivial message injection. Enterprise deployments require encrypted, authenticated, typed, and recoverable channels. This proposal closes those gaps by mandating X25519 key exchange, ChaCha20-Poly1305 per-frame AEAD, Zod-validated discriminated-union messages, and configurable reconnection with session isolation.

Security model

  • Threat model: Malicious extension impersonation, compromised host process, man-in-the-browser, replay attacks, and ephemeral-key exfiltration.
  • Encryption boundaries: X25519 ephemeral key exchange for handshake; ChaCha20-Poly1305 for every frame with AAD binding the message type and sequence number.
  • Input validation: Zod schemas at all public boundaries (TypedMessage, HandshakePayload, FrameHeader). Any validation failure terminates the session.
  • Side-channel resistance: Constant-time shared-secret comparison during handshake; nonce generation via monorepo CSPRNG (@suaveplan/crypto-curve).
  • Key zeroization: Session keys and ephemeral private keys are explicitly overwritten with zero-filled buffers immediately after handshake completion or transport disconnect.
  • Privacy guarantees: No vault plaintext, no passwords, and no biometric data traverse the channel or appear in logs. Logs contain only error codes, connection IDs, and frame sequence numbers.

Performance SLOs

  • Handshake latency: < 50 ms p99 on target hardware.
  • Per-frame crypto: < 1 ms for encryption or decryption p99.
  • Throughput: ≥ 1 000 messages/sec per active channel.
  • Memory: < 2 MB per active connection (keys, buffers, pending queue).
  • Bundle size: client core < 10 KB gzipped; host core < 15 KB gzipped; tree-shakeable by transport adapter.

Observability plan

  • OTel spans: native_messaging.handshake, native_messaging.frame.encode, native_messaging.frame.decode, native_messaging.transport.send, native_messaging.transport.receive.
  • Metrics: active connection gauge, reconnection rate counter, frame error rate counter, handshake duration histogram.
  • Structured logging: JSON-only, with trace_id, connection_id, and error_code; never includes payloads or keys.
  • Error telemetry: automatic capture of FrameCorrupted, HandshakeFailed, and TransportClosed with stack-traces scrubbed of PII.
  • Health checks: NativeMessagingHost exposes an internal health() returning active connection count, last error timestamp, and handshake success rate over the last 60 s.

Dependency audit

  • @suaveplan/crypto-curve — X25519 primitives. Justified: monorepo policy, audited, no external WebCrypto leakage.
  • @suaveplan/crypto-cipher — ChaCha20-Poly1305. Justified: monorepo policy, audited, constant-time where applicable.
  • zod — runtime schema validation. Justified: existing monorepo standard, MIT license, fully tree-shakeable.
  • Zero additional runtime dependencies. Supply-chain risk is minimized via lockfile pinning and provenance manifest generation. All listed deps are tree-shakeable and carry permissive licenses.

Operational readiness

  • Graceful degradation: If the handshake fails or the transport closes unexpectedly, the client surfaces a typed HandshakeFailed or TransportClosed error. There is no silent fallback to plaintext.
  • Resource limits: Max 64 concurrent connections per host; max frame size 1 MB; max pending send queue depth 256 messages.
  • Error recovery and reconnection: ReconnectPolicy provides exponential backoff with full jitter, capped retry count, and per-session isolation so a poisoned session cannot corrupt a new one.
  • Backpressure: Bounded send buffer; exceeding the bound throws BackpressureExceeded rather than allowing unbounded memory growth.
  • Version compatibility: Major-version negotiation during handshake; incompatible versions terminate immediately with a typed VersionMismatch error.

Verification approach

  • src/handshake/handshake.test.ts — covers X25519 key generation, shared-secret derivation, and session-key split.
  • src/frame/frame.test.ts — covers encryption, decryption, AAD integrity, tampered-frame rejection, and nonce sequencing.
  • src/transport/transport.test.ts — covers Chrome/Firefox/Safari adapter lifecycle, send, receive, close, and event emission.
  • src/client/client.test.ts — covers typed request/response round-trips, timeout handling, and error propagation.
  • src/host/host.test.ts — covers host accept/reject, concurrent connections, and session isolation.
  • src/reconnect/reconnect.test.ts — covers backoff timing, jitter distribution, and max-retry exhaustion.
  • src/error/error.test.ts — covers error code uniqueness, instanceof branches, and serialization round-trip.
  • src/spec-conformance.test.ts — pins no-internal-CSPRNG invariant, subpath-export count, and sibling-doc presence.
  • tests/dep-graph.test.ts — pins dependency invariants (only @suaveplan/crypto-curve and @suaveplan/crypto-cipher).
  • 100 % coverage target for all non-test source files.
  • Property-based tests (fast-check): frame round-trip invariants, nonce uniqueness across 10 000 iterations, backoff monotonicity and jitter bounds.
  • Load / stress criteria: 10 000 concurrent handshakes without memory growth; 1 000 000 frame round-trips with < 0.01 % corruption; 24-hour churn test with no handle leaks.
  • Security audit checklist: key zeroization verified in heap dumps; replay-attack simulation rejected by AAD; tampered-frame rejection rate 100 %; CSPRNG bypass attempts blocked.

Migration impact

None — additive only.

Impact

Affected dependents

No dependents (verified by bunx turbo ls --filter=...@suaveplan/native-messaging).