Skip to content

fix(connection): block reserved internal event types from server messages#3340

Open
achowdhry-ripple wants to merge 1 commit into
XRPLF:mainfrom
achowdhry-ripple:fix/dge-6740-validate-event-type
Open

fix(connection): block reserved internal event types from server messages#3340
achowdhry-ripple wants to merge 1 commit into
XRPLF:mainfrom
achowdhry-ripple:fix/dge-6740-validate-event-type

Conversation

@achowdhry-ripple
Copy link
Copy Markdown
Collaborator

@achowdhry-ripple achowdhry-ripple commented May 18, 2026

Summary

Closes #3318

Connection.onMessage previously called this.emit(data.type as string, data) with zero validation of data.type. A malicious or compromised rippled server could therefore send {"type":"connected"}, {"type":"disconnected"}, {"type":"error"}, or {"type":"reconnect"} and trigger Connection's internal state-event listeners (registered in Client), spoofing the local connection state, faking errors, or driving reconnect logic.

This PR introduces a denylist of reserved internal event names and refuses to forward server messages whose type collides with any of them. Such messages are dropped and surfaced via the existing ('error', 'badMessage', ...) channel so they remain observable.

A denylist (rather than the whitelist suggested in the issue) was chosen deliberately to preserve the existing "unrecognized message type" forward-compatibility contract in packages/xrpl/test/connection.test.ts. Unknown future rippled stream types continue to be forwarded as before; only reserved local event names are blocked.

Changes

  • packages/xrpl/src/client/connection.ts: add RESERVED_INTERNAL_EVENTS set and guard the emit in onMessage against it.
  • packages/xrpl/test/connection.test.ts: add unit tests covering all four reserved event names, including the subtle type: "error" case (verifies the raw payload object never reaches error listeners).
  • packages/xrpl/HISTORY.md: changelog entry under Unreleased -> Fixed.

Test plan

  • cd packages/xrpl && npm test -- connection.test.ts - new "drops server message with reserved internal event type" cases pass.
  • Existing "unrecognized message type" test still passes (forward-compat preserved).
  • Existing "emit stream messages" and "propagates error message" tests still pass (legitimate stream/error paths unaffected).
  • npm run lint in packages/xrpl clean.

…ages (DGE-6740)

A malicious or compromised rippled server could previously send
`{"type":"connected"}`, `{"type":"disconnected"}`, `{"type":"error"}`,
or `{"type":"reconnect"}` and have `Connection.onMessage` forward those
events to internal Connection/Client listeners — spoofing connection
state, faking errors, or triggering reconnect logic.

`onMessage` now refuses to forward server-supplied `data.type` values
that collide with these reserved internal event names. Such messages
are dropped and surfaced as a `badMessage` error. Unknown server event
types (e.g. future rippled stream types) continue to be forwarded so
forward-compatibility is preserved.

Fixes XRPLF#3318
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Review Change Stack

Walkthrough

This PR fixes a critical server-supplied event injection vulnerability by preventing a rogue or compromised server from emitting spoofed internal connection state events. The client now maintains a reserved events list and filters all server-supplied message types against it, rejecting collisions and emitting a badMessage error instead.

Changes

Server-Supplied Event Injection Prevention

Layer / File(s) Summary
Reserved events constant and message filtering
packages/xrpl/src/client/connection.ts
A RESERVED_INTERNAL_EVENTS set enumerates connection internal event names (connected, disconnected, error, reconnect). The onMessage handler now checks incoming data.type against this set before emission; collisions trigger a badMessage error and drop the server event.
Test cases verifying event filtering
packages/xrpl/test/connection.test.ts
Parameterized test (lines 910–947) confirms reserved type values do not trigger internal listeners and emit badMessage errors instead; dedicated test (lines 953–995) validates spoofed {"type":"error"} payloads are rejected and do not reach error listeners.
Changelog documentation
packages/xrpl/HISTORY.md
Release notes entry documents the refusal to forward server-supplied message types that collide with internal connection event names.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • pdp2121
  • justinr1234

Poem

A rabbit guards the garden gate,
With lists of names reserved, first-rate.
When servers send their spoofed disguise,
The filter stops them—no surprise! 🐰
Safe events flow, and fakes must cease,
The client's trust returns to peace.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main security fix: blocking reserved internal event types from being triggered by server messages.
Linked Issues check ✅ Passed The PR fully addresses issue #3318 by preventing server-controlled event injection through a denylist of reserved internal event names (connected, disconnected, error, reconnect).
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the security vulnerability: denylist implementation, targeted unit tests, and changelog entry.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description comprehensively covers all required template sections with clear context, change rationale, and detailed test plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/xrpl/src/client/connection.ts (2)

370-386: ⚡ Quick win

Add type validation for data.type before checking reserved events.

The current code uses a type assertion (data.type as string) without runtime validation. While JSON-parsed messages will typically have string types, a malicious or malformed message could have data.type as a number, boolean, array, or object. For example, {"type": 123} would bypass the reserved-event check and emit event "123". Although non-string types won't collide with reserved event names (so this isn't a security bypass), adding explicit string validation improves robustness and makes the code's assumptions explicit.

Suggested validation
   if (data.type) {
-    const type = data.type as string
+    if (typeof data.type !== 'string') {
+      this.emit('error', 'badMessage', 'data.type must be a string', message)
+      return
+    }
+    const type = data.type
     // Refuse to forward server-supplied event names that collide with
     // Connection's internal state events. Otherwise a rogue server could
     // spoof local connection state by sending e.g. `{"type":"connected"}`
     // or `{"type":"error"}`. See DGE-6740.
     if (RESERVED_INTERNAL_EVENTS.has(type)) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/xrpl/src/client/connection.ts` around lines 370 - 386, The code
assumes data.type is a string by asserting `data.type as string`; instead
perform a runtime check on `data.type` before using it: verify typeof data.type
=== 'string' and only then assign to a local `type` variable and run the
`RESERVED_INTERNAL_EVENTS.has(type)` check and `this.emit(type, data)`; if
`data.type` is missing or not a string, call `this.emit('error', 'badMessage',
'Invalid or non-string message type; dropping.', message)` (or similar error
handling used elsewhere) and return to avoid emitting non-string event names.

33-37: ⚡ Quick win

Consider adding 'reconnecting' to the reserved events list.

Line 488 shows that 'reconnecting' is also emitted internally during automatic reconnection attempts. While less critical than the four reserved events (which directly affect connection state), a malicious server could send {"type":"reconnecting"} to spoof automatic reconnection behavior and confuse monitoring code. For consistency and defense in depth, consider adding it to the reserved set.

Suggested addition
 const RESERVED_INTERNAL_EVENTS: ReadonlySet<string> = new Set([
   'connected',
   'disconnected',
   'error',
   'reconnect',
+  'reconnecting',
 ])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/xrpl/src/client/connection.ts` around lines 33 - 37, The
RESERVED_INTERNAL_EVENTS set (RESERVED_INTERNAL_EVENTS) currently protects
internal event names but omits the internally-emitted 'reconnecting' event; add
the string 'reconnecting' to that ReadonlySet so external messages cannot spoof
the client's internal reconnection signal (ensure the literal 'reconnecting' is
added alongside 'connected','disconnected','error','reconnect' in the
RESERVED_INTERNAL_EVENTS declaration).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/xrpl/src/client/connection.ts`:
- Around line 370-386: The code assumes data.type is a string by asserting
`data.type as string`; instead perform a runtime check on `data.type` before
using it: verify typeof data.type === 'string' and only then assign to a local
`type` variable and run the `RESERVED_INTERNAL_EVENTS.has(type)` check and
`this.emit(type, data)`; if `data.type` is missing or not a string, call
`this.emit('error', 'badMessage', 'Invalid or non-string message type;
dropping.', message)` (or similar error handling used elsewhere) and return to
avoid emitting non-string event names.
- Around line 33-37: The RESERVED_INTERNAL_EVENTS set (RESERVED_INTERNAL_EVENTS)
currently protects internal event names but omits the internally-emitted
'reconnecting' event; add the string 'reconnecting' to that ReadonlySet so
external messages cannot spoof the client's internal reconnection signal (ensure
the literal 'reconnecting' is added alongside
'connected','disconnected','error','reconnect' in the RESERVED_INTERNAL_EVENTS
declaration).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 98c777fd-3f48-4e0c-a3c6-2d1ecb312d4e

📥 Commits

Reviewing files that changed from the base of the PR and between d739d47 and 383a45a.

📒 Files selected for processing (3)
  • packages/xrpl/HISTORY.md
  • packages/xrpl/src/client/connection.ts
  • packages/xrpl/test/connection.test.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Server-controlled event injection via unvalidated data.type

1 participant