feat(server): implement xumux v0.2 binary transport#7
Conversation
|
@visionik is attempting to deploy a commit to the Million Team on Vercel. A member of the Team first needs to authorize it. |
|
|
||
| private handleHello(payload: Uint8Array): void { | ||
| if (this.handshakeComplete) return; | ||
| if (payload.length >= 1 && payload[0] !== XUMUX_VERSION) { |
There was a problem hiding this comment.
Empty HELLO payload bypasses version negotiation
Medium Severity
The condition payload.length >= 1 && payload[0] !== XUMUX_VERSION uses short-circuit evaluation, so when the HELLO payload is empty (0 bytes), the version check is skipped entirely and the handshake succeeds. A client sending a HELLO with no version byte completes the handshake without declaring its protocol version, bypassing version negotiation. The condition likely needs to be payload.length < 1 || payload[0] !== XUMUX_VERSION to reject empty payloads.
Reviewed by Cursor Bugbot for commit 3ce56b7. Configure here.
| } else if (event.type === TERMINAL_MSG_TYPE.RESIZE) { | ||
| const dims = decodeResize(event.payload); | ||
| if (dims) session.resize(dims.cols, dims.rows); | ||
| } |
There was a problem hiding this comment.
Binary xumux path lacks input size validation
Medium Severity
The /xumux binary onChannelMessage handler passes decodeInput and decodeResize results directly to session.write() and session.resize() without any bounds validation. The JSON /ws path enforces MAX_INPUT_BYTES (64 KB), MAX_COLS (1000), and MAX_ROWS (1000) via zod schemas. The binary path allows arbitrarily large input payloads and resize dimensions up to 65535 (uint16 max), creating an inconsistent security posture between the two transport paths.
Reviewed by Cursor Bugbot for commit 3ce56b7. Configure here.
| if (!raw || typeof raw !== "object") return 0; | ||
| const candidate = Reflect.get(raw, "bufferedAmount"); | ||
| return typeof candidate === "number" ? candidate : 0; | ||
| }; |
There was a problem hiding this comment.
Duplicated getRawBufferedAmount in adapter and index
Low Severity
The getRawBufferedAmount function in websocket-adapter.ts duplicates nearly identical logic already present in index.ts. Both extract a bufferedAmount number from a raw WebSocket object using Reflect.get. Consolidating into a single shared utility would reduce maintenance burden and risk of divergent bug fixes.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 3ce56b7. Configure here.
- Add xumux v0.2 protocol implementation (XumuxServer, WebSocketAdapter, frame codec) - Add binary codec for terminal message types (INPUT, OUTPUT, RESIZE, EXIT, TITLE, SESSION_INFO) - Add /xumux WebSocket endpoint with HELLO/WELCOME handshake and channel management - Update SessionRegistry to Map<channelId, Session> with register/unregister/getByChannelId - Enforce MAX_CONCURRENT_SESSIONS (64) and loopback security on /xumux - Add backpressure: close channel when bufferedAmount exceeds threshold - Add unit tests for protocol codec, xumux server, WebSocketAdapter, SessionRegistry - Add integration tests for full session lifecycle via mock transport - Add CHANGELOG.md entry under [Unreleased]
- fix: change exit-code null sentinel from -1 to INT32_MIN (-2147483648) so real exit code -1 is not silently corrupted - fix: decodeResize returns null on short payload instead of throwing, preventing unhandled exception crash in onChannelMessage handler - fix: replace global SessionRegistry keyed by channelId with per-connection local Map so concurrent xumux connections sharing channel 1 do not collide and overwrite each other's sessions - fix: call session.dispose() in exit event handler (was missing, leaking event listeners and title polling timer for server-initiated closes) - test: update protocol.test.ts for new sentinel and null-return behavior
3ce56b7 to
857de4e
Compare
|
Closing — PR submitted against wrong repo. Will re-open against visionik/localterm fork. |


Summary
Implements the server-side xumux v0.2 binary transport for localterm, replacing JSON framing with binary multiplexed channels.
Changes
packages/server/src/xumux/): XumuxServer, WebSocketAdapter, frame codec, protocol constantsprotocol.ts): TERMINAL_MSG_TYPE constants and encode/decode for all 6 message types (input, output, resize, exit, title, session-info)Set<Session>toMap<number, Session>withregister(channelId, session),unregister(channelId),getByChannelId(channelId)/xumuxendpoint (index.ts): WebSocket upgrade route with HELLO/WELCOME handshake, OPEN_CHANNEL/CHANNEL_ACK, session lifecycle wiring, backpressure enforcement, loopback securityReferences
vbrief/active/2026-05-06-xumux-server-transport.vbrief.jsonvbrief/specification.vbrief.jsonNotes
libxumuxnpm package does not exist — xumux v0.2 protocol implemented inline inpackages/server/src/xumux/apps/terminal/*(Agent 2 scope)/wsJSON endpoint preserved (will be removed in Phase 4 cutover)Note
Medium Risk
Adds a new binary, multiplexed WebSocket endpoint with custom framing/handshake and new session lifecycle paths, which could impact session cleanup and capacity/backpressure behavior. Existing
/wsremains but its registry tracking changes (auto IDs) could affect session counting if misused.Overview
Introduces a new
/xumuxWebSocket endpoint that speaks xumux v0.2: binary frame parsing, HELLO/WELCOME handshake, per-channel OPEN/CLOSE management, and multiplexed PTY sessions over a single connection (with loopback enforcement and per-channel backpressure closes).Adds a binary terminal message codec in
protocol.tsfor input/output/resize/exit/title/session-info, including a safer exit-code null sentinel (INT32_MIN) and a non-throwingdecodeResizeon short payloads.Refactors
SessionRegistryfrom aSetto an ID-keyedMapwithregisterAuto()(used by both/wsand/xumux) and updates cleanup paths to unregister by ID; adds comprehensive unit/integration tests for the codecs, xumux framing/server, adapter behavior, and registry semantics.Reviewed by Cursor Bugbot for commit 857de4e. Bugbot is set up for automated code reviews on this repo. Configure here.