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 commonTransportinterface. - 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/biometricswill 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-detectionand 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, anderror_code; never includes payloads or keys. - Error telemetry: automatic capture of
FrameCorrupted,HandshakeFailed, andTransportClosedwith stack-traces scrubbed of PII. - Health checks:
NativeMessagingHostexposes an internalhealth()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
HandshakeFailedorTransportClosederror. 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:
ReconnectPolicyprovides 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
BackpressureExceededrather than allowing unbounded memory growth. - Version compatibility: Major-version negotiation during handshake; incompatible versions terminate immediately with a typed
VersionMismatcherror.
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,instanceofbranches, 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-curveand@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).