Skip to content

ERPC

v0.8.0 Security

This release includes 8 security fixes for security teams reviewing exposed deployments.

Published 10d Network Security
✓ No known CVEs patched
Read the diff → Tool health → What is this tool? →
This release patches 8 known CVEs

Topics

aead cryptography e2e-encryption encrypion messagechannel messaging
+6 more
rpc saferpc secure-channel secure-communication webrtc websocket

Affected surfaces

auth breaking_upgrade crypto_tls

ReleasePort's take

Moderate signal
editorial:auto 10d

Auto‑retry is removed; timeouts now raise RPCAbortedError. The default sendTimeout drops from 10 s to 3 s, and Node.js must be ≥20.19.0.

Why it matters: All breaking changes affect API clients, session handling, configuration defaults, runtime requirements (Node ≥20.19.0), auth utilities removal, and add security hardening for zero secrets; severity scores range 70‑80 across six facts.

Summary

AI summary

Broad release touches Auth payloads, Handshake, Input validation, and Verification.

Changes in this release

Security High

All‑zero secret of any length now rejected during session derivation.

All‑zero secret of any length now rejected during session derivation.

Source: llm_adapter@2026-07-16

Confidence: high

Security High

Candidate handshake phase now has its own absolute deadline to prevent promotion after confirmation window.

Candidate handshake phase now has its own absolute deadline to prevent promotion after confirmation window.

Source: llm_adapter@2026-07-16

Confidence: high

Security High

Synchronous auth callbacks now respect handshakeTimeout deadline.

Synchronous auth callbacks now respect handshakeTimeout deadline.

Source: llm_adapter@2026-07-16

Confidence: high

Breaking High

Auto-retry removed; timeouts now raise RPCAbortedError.

Auto-retry removed; timeouts now raise RPCAbortedError.

Source: llm_adapter@2026-07-16

Confidence: high

Breaking High

Deferred session reset only on RPCAbortedError("TIMEOUT").

Deferred session reset only on RPCAbortedError("TIMEOUT").

Source: llm_adapter@2026-07-16

Confidence: high

Breaking High

Default sendTimeout reduced from 10s to 3s.

Default sendTimeout reduced from 10s to 3s.

Source: llm_adapter@2026-07-16

Confidence: high

Breaking High

Node version floor raised to >=20.19.0; earlier versions unsupported.

Node version floor raised to >=20.19.0; earlier versions unsupported.

Source: llm_adapter@2026-07-16

Confidence: high

Breaking High

Certificate and multifactor auth helpers removed.

Certificate and multifactor auth helpers removed.

Source: llm_adapter@2026-07-16

Confidence: high

Feature Medium

Normative auth profiles added: JWT, Ed25519, ECDSA (P‑256).

Normative auth profiles added: JWT, Ed25519, ECDSA (P‑256).

Source: llm_adapter@2026-07-16

Confidence: high

Dependency Low

`puppeteer` moved from `optionalDependencies` to `devDependencies`; production audit now clean.

`puppeteer` moved from `optionalDependencies` to `devDependencies`; production audit now clean.

Source: llm_adapter@2026-07-16

Confidence: high

Full changelog

2026-07-16 — v0.8.0: client/transport rework + handshake hardening, auth profiles, cert/MFA removal

The 0.7 and 0.8 work, read as one set of changes. 0.7.x was cut in a hurry to
unblock an external review and was never checked against the spec end to end —
treat those tags as pre-release. 0.8.0 is the first release to pass a full
conformance pass against spec/protocol.md
; the hardening landed across several
independent review rounds (auth-surface design review, cross-language port audit,
two neutral security passes), every finding re-verified in shipped code and pinned
by a regression test.

Do not assume wire compatibility with pre-0.8.0 tags — only 0.8.0 is
spec-conformant. Test suite grown to 298.

Client & transport semantics (0.7)

Behavioural rework of the client and channel layer — separate from the 0.8
security pass below.

  • BREAKING — auto-retry removed. The client no longer silently re-sends a
    call. A sent request that times out rejects with RPCAbortedError and leaves
    the outcome UNKNOWN — the caller decides whether replaying is safe. A plain
    local RPCError means the request provably never left.
  • BREAKING — deferred reset. The session resets only on a sent call that
    gets RPCAbortedError("TIMEOUT"); guardrail rejections (CHANNEL, ABORTED,
    queued-frame failures) no longer tear down and re-handshake the session.
  • Replay window. Server keeps a bounded seen-nonce set (configurable
    replayWindow, default 4096) so a captured frame can't be replayed within the
    window.
  • abortPending. New client API to abort all in-flight calls at once (each
    rejects by its sent-status class), on top of per-call AbortSignal.
  • Make-before-break session continuity. A new handshake is negotiated as a
    candidate while the live session keeps serving; the old session is only
    replaced once the candidate is confirmed, so a re-handshake doesn't drop
    in-flight work. Handshake states are resilient across transient channel gaps.
  • Reconnecting channel adapters. WebSocket and socket adapters reconnect and
    the session survives transport loss — a call issued while the channel is down
    completes after recovery without a re-handshake.
  • BREAKING — default sendTimeout 10s → 3s for faster failure detection of an
    unsent frame.
  • Port-complete spec + first KAT vectors. spec/protocol.md,
    spec/security.md assessment, and the initial known-answer test vectors landed
    here — the groundwork the 0.8.0 conformance pass later verified end to end.

Security

Handshake

  • High — candidate phase now has its own absolute deadline. A candidate
    session could be promoted after its confirmation window elapsed. Server stamps
    candidateDeadline on install and checks it before promoteCandidate();
    companion fix drops a double-onError report on one failed attempt (reported
    flag).
  • Synchronous auth callbacks can no longer overrun handshakeTimeout. Timer +
    boolean let a synchronous verify/secret/sign win the race (post-await
    continuation is a microtask, runs before the timer). Now an absolute Date.now()
    deadline is checked after every auth callback and before candidate install,
    session publish, and reply send — both peers.
  • Candidate promotion split from payload decoding. An AEAD-authenticated frame
    with malformed inner msgpack used to fail the whole decrypt — candidate not
    promoted, timer not cleared, nonce not recorded — stalling a valid peer to
    timeout. AEAD opening (createAeadOpener) is now separate from
    decodePlaintext; candidate is promoted and nonce recorded the moment Poly1305
    verifies, regardless of inner payload shape.
  • maxPendingHandshakes caps concurrent in-flight handshakes so a peer with
    never-settling auth callbacks can't accumulate unbounded pending work.
  • Failed handshake-reply send drops the candidate immediately (when
    candidateEpoch still matches) instead of lingering to its timer; exactly one
    error reported.
  • Low — handshake epoch exhaustion is a terminal client error. No ceiling
    guard on the client epoch counter; past 0xffffffff every hello would be
    silently dropped server-side as Invalid epoch, surfacing only as opaque
    timeouts. startHandshake now rejects with RPCError("CLIENT", "Handshake epoch exhausted; destroy and recreate client"). Unreachable in practice
    (~4.3 × 10⁹ handshakes); no wire/reuse impact.

Crypto / secrets

  • Medium-High — all-zero secret rejected at any length. Guard only covered 32
    bytes. isEmptySecret now rejects any-length all-zero buffers;
    deriveSessionSecret throws TypeError("secret must not be all-zero").

Replay / framing

  • Reflected t: 2 frames no longer consume replay slots. A direction guard
    runs before the nonce is recorded, closing a replay-window-narrowing reflection.
  • Response-framing size limits tightened in the same pass.

Input validation

  • Bigints beyond 64 bits rejected (INVALID_DATA) instead of round-tripping a
    corrupted value; BIGINT_MIN / BIGINT_MAX exported.
  • Cyclic outbound graphs rejected (WeakSet detection) instead of recursing to
    a stack overflow; non-cyclic repeated references still rebuilt independently.
  • NaN / Infinity no longer disable limits. maxPending,
    maxMessageBytes, JWT maxAge validated as finite integers at construction.
    maxMessageBytes: NaN used to make len > max always false — trivially reached
    via Number(process.env.X).

Middleware

  • Middleware returning without calling next() fails closed (RPCError ("MIDDLEWARE", ...)) — previously skipped the handler but returned success.
    Completion guarded on synchronous throw and bare return.
  • Fire-and-forget next() is no longer a supported pattern — a middleware
    that completes while its next() promise is still pending fails closed with
    RPCError("MIDDLEWARE", ...) instead of replying with the middleware's own
    value while the handler outcome is silently dropped (inspired by tRPC's
    return-next guard — "did you forget to return next()?" — which uses a
    runtime envelope/marker rather than settlement tracking). A synchronous
    downstream throw is normalized into a rejected next() promise so a
    catch-fallback middleware is not misclassified. The detached downstream
    promise is still observed internally, so its rejection can never surface as
    an unhandledRejection and terminate the process.

Auth payloads

  • Auth payloads pass the full sanitize() gate and are version-checked; malformed
    or unversioned payloads rejected as UNAUTHORIZED before reaching app verify.

Added

  • Normative auth profiles: JWT, Ed25519, ECDSA (P-256). Each has a versioned
    wire schema (v field, unknown version rejected server-side) and published KAT
    vectors in spec/protocol.md / test/unit/vectors.test.ts — cross-language
    ports interoperate byte-for-byte.
  • maxPendingHandshakes server option.
  • BIGINT_MIN / BIGINT_MAX exports.
  • Port-complete spec + KAT vectors — 44-item implementation checklist and full
    vectors (keys, proof, both transcripts, three auth-profile payloads) cross-checked
    spec ↔ tests ↔ implementation.

Changed

  • BREAKING — Node floor >=20.19.0. package.json declared a range the build
    and @noble/hashes 2.x never supported; declaration now matches reality. Node
    < 20.19.0 unsupported, not CI-tested.
  • Spec/docs realigned with code: JWT credential visibility in the opening frame
    called out, asymmetric examples authenticate both directions, protocol.md
    context-error contract matches server.ts, malformed-envelope /
    stale-and-unknown-response-id handling pinned, sent-boundary for async adapters
    clarified.

Removed

  • BREAKING — certificate + multifactor auth helpers deleted
    (createCertificateServerAuth, createMultifactorServerAuth) with tests. The
    certificate helper was ECDSA verification under a name promising
    chain/expiry/revocation it never did (all in the app callback). The multifactor
    helper couldn't enforce same-principal binding — default { ...primary, ...secondary } merge passed two different principals as MFA, and spread order
    could let an issuer-signed JWT claim overwrite a cryptographically verified
    field. Use JWT/Ed25519/ECDSA profiles or a custom verify; spec/security.md
    documents certificate and two-factor-with-binding recipes.

Fixed

  • Nested undefined in outbound values no longer diverges from the declared type
    contract.
  • Reserved route/middleware names can no longer be registered.
  • Malformed RPC responses (bad ok, absent/garbage c/m) dropped or coerced
    strictly per spec.

Dependencies

  • puppeteer moved to devDependencies — was in optionalDependencies
    (installs by default for consumers), dragging ws (high/moderate) and js-yaml
    (moderate) into production npm audit. Production audit now clean.
  • ws bumped to ^8.21.0.

Verification

  • Full gate green: 39 files, 298 tests; lint, typecheck, ESM+CJS build clean
    (Node 22.22.1).
  • Every finding re-verified in shipped code (not on commit-message trust) and
    mapped to a regression test; KAT vectors byte-identical across spec/tests/impl.
  • Full audit trail: review/0.8/review-release-verdict-0.8.0.md.

Published to npm: https://www.npmjs.com/package/@dotex/saferpc/v/0.8.0

Breaking Changes

  • Auto‑retry removed; timed‑out calls now reject with RPCAbortedError and leave outcome UNKNOWN.
  • Deferred session reset: only a sent call that times out (RPCAbortedError("TIMEOUT")) resets the session; other guardrail rejections no longer trigger a reset.
  • Default sendTimeout reduced from 10s to 3s for faster failure detection.
  • Certificate and multifactor auth helpers (`createCertificateServerAuth`, `createMultifactorServerAuth`) removed.
  • Minimum Node.js version raised to >=20.19.0.

Security Fixes

  • Handshake candidate promotion now checks an absolute deadline, preventing promotion after confirmation window expiry (high severity).
  • Synchronous auth callbacks can no longer overrun `handshakeTimeout`; each callback enforces a hard date check.
  • Candidate promotion split from payload decoding; AEAD verification promotes the candidate even if inner msgpack is malformed, avoiding stalls.
  • All‑zero secret of any length rejected at derivation (medium‑high severity).
  • Bigint values beyond 64 bits now rejected (`INVALID_DATA`).
  • Cyclic outbound graphs detected and rejected via `WeakSet` to avoid stack overflow.
  • Middleware returning without calling `next()` fails closed with RPCError("MIDDLEWARE").
  • Fire‑and‑forget `next()` pattern disallowed; such middleware fails closed.

Weekly OSS security release digest.

The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.

No spam, unsubscribe anytime.

Share this release

Track ERPC

Get notified when new releases ship.

Sign up free

Related context

Beta — feedback welcome: [email protected]