Skip to content

fix(ws): correct inverted response schema validation in createHandleWSResponse#1934

Open
tsushanth wants to merge 1 commit into
elysiajs:mainfrom
tsushanth:fix/1933-ws-response-validation-inverted
Open

fix(ws): correct inverted response schema validation in createHandleWSResponse#1934
tsushanth wants to merge 1 commit into
elysiajs:mainfrom
tsushanth:fix/1933-ws-response-validation-inverted

Conversation

@tsushanth

@tsushanth tsushanth commented Jul 5, 2026

Copy link
Copy Markdown

Fixes #1933.

Root cause

createHandleWSResponse builds a validateResponse predicate that returns truthy on failure:

  • TypeBox path: (data) => responseValidator.Check(data) === false — returns true when invalid
  • Standard-schema path: (data) => responseValidator.schema['~standard'].validate(data).issues — returns the issues array (truthy) when invalid

The send() guard checked validateResponse(datum) === false, which is truthy only when validation passes — exactly backwards. Valid messages were replaced by ValidationError frames; invalid messages were sent through unvalidated.

Regression from 23f30f78a ("fix: #1563 standard schema on websocket"), which extracted the old inline Check(datum) === false check into a closure but left === false at the call site, double-negating it.

Second bug in the same block: the async-generator early-return path validated first (the {value, done} iterator result object) instead of first.value (the handler's actual yielded datum), embedding the wrapper in the ValidationError message.

Fix

  1. Remove === false from the send() guard — validateResponse(datum) alone is truthy on failure.
  2. Validate first.value instead of first in the async-generator branch.

Summary by CodeRabbit

  • Bug Fixes
    • Improved response validation for websocket interactions so invalid payloads are rejected more reliably.
    • Fixed handling of generator-based responses to validate the returned value correctly and show clearer validation errors when the first result is invalid.

…SResponse

validateResponse returns truthy on failure (TypeBox: Check(data) === false
cast to boolean; standard-schema: returns .issues). The send() guard used
'=== false', which double-negated: valid messages triggered a ValidationError
frame and invalid messages were sent through unvalidated.

Also fix the async-generator branch which validated 'first' (the iterator
result object) instead of 'first.value' (the actual yielded datum), so the
ValidationError message embedded the {value, done} wrapper rather than the
handler's output.

Both regressions trace to 23f30f7 which wrapped the old inline Check
predicate into a helper but left the inversion at the call site.
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Fixes inverted validation logic in createHandleWSResponse within src/ws/index.ts. The send(datum) guard now treats a truthy validateResponse(datum) result as invalid instead of a strict === false check. The async generator path now validates first.value and includes it in the ValidationError payload.

Changes

WebSocket Response Validation Fix

Layer / File(s) Summary
Fix send() validation guard
src/ws/index.ts
Corrects the invalid-response condition from validateResponse(datum) === false to a truthiness check, so validation failures actually trigger the error path.
Fix async generator validation target
src/ws/index.ts
Validates first.value instead of the raw next() result and passes first.value into the constructed ValidationError.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

Ara ara~ found your bug hiding in plain sight (๑>ᴗ<๑)
Sending bad data through like it's totally alright?
Baka baka, inverted logic, how embarrassing for you~ ♡
Now validateResponse actually does what it's s'posed to do,
You're welcome, I fixed it while you weren't even lookin'~ ♡(◕‿◕)

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main fix: correcting inverted WebSocket response validation in createHandleWSResponse.
Linked Issues check ✅ Passed The changes satisfy #1933 and #1563 by treating truthy validation failures correctly and validating first.value in the async-generator path.
Out of Scope Changes check ✅ Passed The patch stays scoped to the WebSocket validation bug fix and does not introduce unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

src/ws/index.ts

Oops! Something went wrong! :(

ESLint: 9.39.4

Error: ESLint configuration in --config » plugin:sonarjs/recommended is invalid:

  • Unexpected top-level property "name".

Referenced from: /.eslintrc.json
at ConfigValidator.validateConfigSchema (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2277:19)
at ConfigArrayFactory._normalizeConfigData (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3123:19)
at ConfigArrayFactory._loadExtendedPluginConfig (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3346:25)
at ConfigArrayFactory._loadExtends (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3259:29)
at ConfigArrayFactory._normalizeObjectConfigDataBody (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3199:25)
at _normalizeObjectConfigDataBody.next (<anonym

... [truncated 291 characters] ...

/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2952:16)
at createCLIConfigArray (/node_modules/.pnpm/@eslint+eslintrc@3.3.5/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3768:35)
(node:2) ESLintRCWarning: You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.
(Use node --trace-warnings ... to show where the warning was created)


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.

🧹 Nitpick comments (1)
src/ws/index.ts (1)

289-298: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Nyaa~ you're validating first.value twice, silly♡

The early check on Line 289 runs validateResponse(first.value), and then send(first.value) on Line 298 runs the exact same validation all over again (◞‸◟). The correctness is fine — the early return is genuinely needed so an invalid first frame short-circuits the rest of the generator — but you're paying for the validation twice on every WS generator response's first yield~.

If you feel like being a good kid, have send report whether it emitted a ValidationError and reuse that to early-return, so validateResponse only runs once. Not a blocker though, so I'll let it slide this time ♡~ (^▽^)

♻️ One sketch (preserving early-return)
-		const send = (datum: unknown) => {
-			if (validateResponse && validateResponse(datum))
-				return ws.send(
-					new ValidationError('message', responseValidator!, datum)
-						.message
-				)
-
-			if (typeof datum === 'object') return ws.send(JSON.stringify(datum))
-
-			ws.send(datum as any)
-		}
+		const send = (datum: unknown): boolean => {
+			if (validateResponse && validateResponse(datum)) {
+				ws.send(
+					new ValidationError('message', responseValidator!, datum)
+						.message
+				)
+				return false
+			}
+
+			if (typeof datum === 'object') ws.send(JSON.stringify(datum))
+			else ws.send(datum as any)
+			return true
+		}
-				if (validateResponse && validateResponse(first.value))
-					return ws.send(
-						new ValidationError(
-							'message',
-							responseValidator!,
-							first.value
-						).message
-					)
-
-				send(first.value as any)
-
-				if (!first.done)
+				if (!send(first.value as any)) return
+
+				if (!first.done)
🤖 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 `@src/ws/index.ts` around lines 289 - 298, The first WebSocket generator yield
in src/ws/index.ts is being validated twice: once before the early return and
again inside send, so update the send path to report whether it emitted a
ValidationError and use that result to short-circuit the generator flow. Keep
the existing early-return behavior for invalid first frames, but reuse the
validation outcome from validateResponse / responseValidator in the send logic
so first.value is only validated once.
🤖 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 `@src/ws/index.ts`:
- Around line 289-298: The first WebSocket generator yield in src/ws/index.ts is
being validated twice: once before the early return and again inside send, so
update the send path to report whether it emitted a ValidationError and use that
result to short-circuit the generator flow. Keep the existing early-return
behavior for invalid first frames, but reuse the validation outcome from
validateResponse / responseValidator in the send logic so first.value is only
validated once.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 04d4c1c8-cf3d-430e-8377-15b1a4f5fa1f

📥 Commits

Reviewing files that changed from the base of the PR and between 8358ff9 and 845ab7e.

📒 Files selected for processing (1)
  • src/ws/index.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

2 participants