fix(server): stop malformed WebSocket frames from crashing the process - #2108
Conversation
`ws` sockets are EventEmitters and throw when an `error` event has no listener, so a single malformed frame from any client (invalid UTF-8, unexpected RSV bits, ...) crashed the Node process. `upgrade()` now absorbs `error`; `ws` still closes the connection and the existing `close` listener handles cleanup.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/experimental-msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Absorb
errorevents on upgraded sockets —WebSocketHandler.upgrade()now attaches a no-operrorlistener alongsidemessage/close, so an EventEmitter-based transport such aswsno longer throws an uncaught error (and crashes the process) when a client sends a malformed frame.closestill drives the existing peer cleanup. - Regression test — spins up a real
wsserver/client, sends an invalid UTF-8 text frame, and asserts the connection closes with code1007. wsdevDependency — added at^8.21.3to@orpc/server, with the matching lockfile importer entry.
I verified the fix and the test locally: with the change the new test passes; reverting the listener leaves the file at 10/10 passing but vitest records the unhandled WS_ERR_INVALID_UTF8 and exits 1, so the regression signal is real. pnpm --filter @orpc/server type:check and eslint on both changed files are clean, and the lockfile already contained the ws@8.21.3 package snapshot. The no-op listener is harmless on DOM, Bun, Cloudflare, and MessagePort sockets, so no behavior change there.
The silent swallow is consistent with the server package, which does no logging anywhere; the v1 backport and the client RPCLink gap are already acknowledged as follow-ups in the PR description.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…nk (#2111) A client using the WebSocket `RPCLink` with Node's `ws` library no longer crashes when the socket emits `error`. `ws` throws when `error` has no listener, and the link transport only listened for `open`, `message` and `close`, so a refused connection or a malformed frame from the server took the whole client process down. The transport now absorbs `error`, and the `close` event that always follows rejects pending calls or drives reconnection. This is the client-side counterpart to #2108. ## Fixes - A refused connection rejects the call with `WebSocket closed (code 1006: )` instead of crashing and leaving the call hanging. - With `reconnect` enabled, refused attempts while the server is down go through the normal retry path (including `maxAttempt` and proactive `onClose` reconnects) instead of crashing on every attempt. - A malformed frame from the server rejects pending calls instead of throwing an uncaught `RangeError`. - Browser, Deno, Bun and Cloudflare sockets behave as before. ## Testing - New regression tests with a real `ws` server and client cover a refused connection, reconnecting after one, and a malformed server frame. Run alone without the fix, each exits 1 on the uncaught error. - `ws` and `@types/ws` are now devDependencies of `@orpc/client`.

Any WebSocket client could crash a Node server that uses the
wslibrary.wsthrows when anerrorevent has no listener, andWebSocketHandler.upgrade()only listened formessageandclose, so one malformed frame (invalid UTF-8, unexpected RSV bits, ...) took the whole process down.upgrade()now absorbs theerrorevent: the offending connection is closed with the proper status code and the server keeps running.Fixes
Testing
wsserver and client. Without the fix, vitest reports the uncaught error and exits 1.wsis now a devDependency of@orpc/server; its types come from the root, likesupertest.Follow-ups
wsandwebsocketadapters have the same gap and need a backport to1.x.RPCLinkoverwshas the same gap (a refused connection crashes the client) and is being fixed separately.