Skip to content

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

Open
ckeshava wants to merge 6 commits into
XRPLF:mainfrom
ckeshava:fix/validate-event-type
Open

fix(connection): block reserved internal event types from server messages#3378
ckeshava wants to merge 6 commits into
XRPLF:mainfrom
ckeshava:fix/validate-event-type

Conversation

@ckeshava

Copy link
Copy Markdown
Collaborator

Blocks server-supplied data.type values that collide with Connection's reserved internal events (connected, disconnected, error, reconnect, reconnecting). Such messages are dropped and surfaced as badMessage; unknown types are still forwarded so forward-compatibility is preserved.

Continues the work in #3340.

Fixes #3318

…ages

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
- Include 'reconnecting' in the reserved internal-event denylist, since
  Connection also emits it during automatic reconnect attempts.
- Drop internal ticket reference from inline comments.
- Collapse the separate type:"error" test into the parameterized
  it.each so all reserved types (including 'error' and 'reconnecting')
  share one test body. The 'error' iteration retains the extra check
  that the raw spoofed payload never reaches 'error' listeners.
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b62501aa-be98-41e2-ab47-b26109c3ced7

📥 Commits

Reviewing files that changed from the base of the PR and between 5dfb28d and bb70c6d.

📒 Files selected for processing (2)
  • packages/xrpl/src/client/connection.ts
  • packages/xrpl/test/connection.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/xrpl/src/client/connection.ts
  • packages/xrpl/test/connection.test.ts

Walkthrough

connection.ts now blocks server-controlled data.type values that match internal connection events or are not strings, emitting badMessage errors instead of forwarding them. Tests cover reserved string types and malformed values, and the changelog notes the behavior change.

Changes

Reserved internal event injection guard

Layer / File(s) Summary
Reserved event guard: constant, entry point, and validation helper
packages/xrpl/src/client/connection.ts
Defines RESERVED_INTERNAL_EVENTS with connection lifecycle event names. Updates onMessage to delegate message type forwarding to forwardServerMessage. forwardServerMessage rejects non-string types and reserved names, emits badMessage errors on rejection, and forwards allowed messages.
Regression tests and changelog entry
packages/xrpl/test/connection.test.ts, packages/xrpl/HISTORY.md
Adds parametrized tests for reserved string types and non-string values that coerce to reserved names, asserting reserved listeners do not fire and badMessage errors are emitted. Adds a HISTORY.md note documenting the fix.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through packets, swift and bright,
But guarded types put tricks to flight.
Reserved names fizzled, false alerts blew past,
badMessage rang out safe and fast.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix: blocking reserved internal event types from server messages.
Description check ✅ Passed The description covers the core change and issue linkage, with only template fields like test plan and type checklist left out.
Linked Issues check ✅ Passed The code validates server-supplied types, drops reserved internal events, preserves unknown types, and adds regression tests for #3318.
Out of Scope Changes check ✅ Passed The changes stay focused on connection message validation, tests, and changelog updates, with no unrelated scope evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/xrpl/src/client/connection.ts

Parsing error: The keyword 'import' is reserved

packages/xrpl/test/connection.test.ts

Parsing error: The keyword 'import' is reserved


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/xrpl/test/connection.test.ts (1)

912-915: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add non-string reserved-name bypass cases.

The test only covers exact string type values. Add cases like type: ['error'] and type: ['connected'] so the regression suite catches payloads that bypass a string-only denylist check but still resolve to reserved listener names at emission time.

🧪 Suggested test shape
-  it.each(['connected', 'disconnected', 'reconnect', 'reconnecting', 'error'])(
+  it.each([
+    ['connected', { type: 'connected', error: 'spoof' }],
+    ['disconnected', { type: 'disconnected', error: 'spoof' }],
+    ['reconnect', { type: 'reconnect', error: 'spoof' }],
+    ['reconnecting', { type: 'reconnecting', error: 'spoof' }],
+    ['error', { type: 'error', error: 'spoof' }],
+    ['connected', { type: ['connected'], error: 'spoof' }],
+    ['error', { type: ['error'], error: 'spoof' }],
+  ])(
     'drops server message with reserved internal event type "%s"',
-    async (reservedType) => {
-      const spoofedPayload = { type: reservedType, error: 'spoof' }
+    async (reservedType, spoofedPayload) => {
🤖 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/test/connection.test.ts` around lines 912 - 915, The test for
reserved internal event types currently only covers exact string values in the
it.each array. Add additional test cases with non-string types that could bypass
a string-only denylist check but still resolve to reserved listener names, such
as type values that are arrays containing reserved names like ['error'] and
['connected']. Extend the it.each array to include these non-string variants
alongside the existing string cases, and ensure the spoofedPayload is
constructed appropriately for each test variant to verify these bypass scenarios
are properly prevented.
🤖 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.

Inline comments:
In `@packages/xrpl/HISTORY.md`:
- Line 36: The release note in HISTORY.md lists only four blocked event names
(connected, disconnected, error, reconnect) but the implementation blocks five
names. Add the missing `reconnecting` event name to the list in the changelog
entry to accurately reflect all the internal state events that are now blocked
from being forwarded from the server, ensuring users see the complete behavior
change.

In `@packages/xrpl/src/client/connection.ts`:
- Around line 373-387: Add a runtime type validation check for data.type before
checking against RESERVED_INTERNAL_EVENTS, since the TypeScript type assertion
does not validate runtime type. Insert a guard that checks if typeof data.type
!== 'string' before the RESERVED_INTERNAL_EVENTS.has(type) check, and if the
type is not a string, emit an 'error' event with 'badMessage' to reject
non-string event types and prevent type coercion attacks that could bypass the
reserved-event security check.

---

Nitpick comments:
In `@packages/xrpl/test/connection.test.ts`:
- Around line 912-915: The test for reserved internal event types currently only
covers exact string values in the it.each array. Add additional test cases with
non-string types that could bypass a string-only denylist check but still
resolve to reserved listener names, such as type values that are arrays
containing reserved names like ['error'] and ['connected']. Extend the it.each
array to include these non-string variants alongside the existing string cases,
and ensure the spoofedPayload is constructed appropriately for each test variant
to verify these bypass scenarios are properly prevented.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: db3e9818-ab62-4444-b5d8-b137bf47f34a

📥 Commits

Reviewing files that changed from the base of the PR and between 7791ce2 and 4671cb0.

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

Comment thread packages/xrpl/HISTORY.md Outdated
Comment thread packages/xrpl/src/client/connection.ts Outdated
ckeshava and others added 2 commits June 22, 2026 16:44
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
A non-string `type` (e.g. `["error"]`) bypassed the reserved-event Set
check but still coerced to a reserved name when passed to `emit`.
Validate `type` is a string before forwarding and drop non-strings as
`badMessage`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ckeshava

Copy link
Copy Markdown
Collaborator Author

/ai-reviewer

@xrplf-ai-reviewer xrplf-ai-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues.

Review by Claude Sonnet 4.6 · Prompt: V15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/xrpl/test/connection.test.ts (1)

990-998: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Reject any non-error internal event emission.

For connected/disconnected/reconnect/reconnecting, the listener should never fire at all; currently the test only fails when the forwarded argument is an object. Tighten this so a regression emitting the reserved event with no args or a primitive still fails, while preserving the error/badMessage path.

Proposed assertion tightening
         clientContext.client.connection.on(coercesTo, (forwarded) => {
-          if (forwarded != null && typeof forwarded === 'object') {
+          if (
+            coercesTo !== 'error' ||
+            (forwarded != null && typeof forwarded === 'object')
+          ) {
             reject(
               new XrplError(
                 `Non-string server type was forwarded as internal event "${coercesTo}"`,
               ),
             )
           }
🤖 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/test/connection.test.ts` around lines 990 - 998, The listener
attached to the internal event in the connection test currently only rejects
when the forwarded argument is a non-null object, but it should reject whenever
the listener fires for the internal events
connected/disconnected/reconnect/reconnecting since these should never be
forwarded at all. Replace the conditional check with an unconditional reject
that fires whenever this listener is invoked for these specific events, while
ensuring the error and badMessage event paths are excluded from this rejection
logic so they can continue to be forwarded without triggering the test failure.
🤖 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.

Inline comments:
In `@packages/xrpl/test/connection.test.ts`:
- Around line 974-979: Add the missing `reconnecting` event case to the it.each
test matrix in the connection.test.ts file. The upstream reserved-event set
includes `reconnecting`, but the current non-string coercion test matrix does
not cover it. Add a new row to the test case array following the same pattern as
the existing entries (like `['connected', ['connected']]`), ensuring that
`reconnecting` is tested alongside the other reserved internal events
(`connected`, `disconnected`, `reconnect`, and `error`) to provide complete
regression test coverage.

---

Nitpick comments:
In `@packages/xrpl/test/connection.test.ts`:
- Around line 990-998: The listener attached to the internal event in the
connection test currently only rejects when the forwarded argument is a non-null
object, but it should reject whenever the listener fires for the internal events
connected/disconnected/reconnect/reconnecting since these should never be
forwarded at all. Replace the conditional check with an unconditional reject
that fires whenever this listener is invoked for these specific events, while
ensuring the error and badMessage event paths are excluded from this rejection
logic so they can continue to be forwarded without triggering the test failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8d9ba3a6-ea2e-4447-956a-f438ae064c89

📥 Commits

Reviewing files that changed from the base of the PR and between b0d3231 and 5dfb28d.

📒 Files selected for processing (2)
  • packages/xrpl/src/client/connection.ts
  • packages/xrpl/test/connection.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/xrpl/src/client/connection.ts

Comment thread packages/xrpl/test/connection.test.ts
Comment thread packages/xrpl/test/connection.test.ts
Comment thread packages/xrpl/src/client/connection.ts Outdated
ckeshava and others added 2 commits June 24, 2026 15:59
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
- Include the offending `typeof data.type` in the non-string `badMessage`
  error to aid debugging.
- Fail fast on unexpected `error` emissions in the reserved-event test
  instead of waiting out the 20s timeout.

Addresses review feedback on XRPLF#3378.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ckeshava ckeshava requested a review from cybele-ripple June 24, 2026 23:06
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

3 participants