Harden InputHandler Against Event Flooding and Oversized Payload Attacks (Add Rate Limiting & Input Safeguards)#87
Conversation
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughAdded input sanitization and per-event throttling to InputHandler: text capped at 500 chars, dx/dy clamped to [-2000,2000], move/scroll events debounced/batched with ~8ms timers and pending-event state, plus normalized combo/key handling and extra logging. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/server/InputHandler.ts`:
- Around line 30-32: The current clamping using MAX_COORD on msg.dx/msg.dy
allows NaN to pass through because Math.max/Math.min return NaN for non-numeric
inputs; update the InputHandler logic that mutates msg.dx and msg.dy to first
validate values with Number.isFinite or typeof === 'number' before clamping
(MAX_COORD), and if invalid either discard/normalize to 0 or skip assigning so
downstream calls like mouse.setPosition don't receive NaN; ensure you update the
branches that handle msg.dx and msg.dy accordingly.
- Around line 34-41: The current leading-edge throttle in InputHandler (using
lastEventTime and checking msg.type === 'move' || msg.type === 'scroll') drops
the trailing event and shares one timestamp across move and scroll; change it to
a per-event-type trailing-edge throttle: replace the single lastEventTime with
separate state (e.g., lastMoveEventTime, lastScrollEventTime and
pendingMoveEvent, pendingScrollEvent) and on a rapid burst, let the first event
go through immediately but if subsequent events are skipped schedule the last
skipped event via setTimeout (~16ms) to fire after the window expires; ensure
the dispatch logic references msg.type to select the correct timestamp/pending
slot so move and scroll throttling are independent.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/server/InputHandler.ts (1)
143-144:⚠️ Potential issue | 🟠 Major
msg.deltais missing the sameNumber.isFiniteguard applied todx/dy—NaNslips through.
NaN !== undefinedandNaN !== 0are bothtrue, so aNaNdelta enters the zoom block.Math.sign(NaN)→NaN, makingscaledDelta = NaNandamount = NaN. SinceNaN !== 0is true,keyboard.pressKey(Key.LeftControl)fires followed bymouse.scrollUp(NaN), leaving the system in an inconsistent state.🐛 Proposed fix
- if (msg.delta !== undefined && msg.delta !== 0) { + if (typeof msg.delta === 'number' && Number.isFinite(msg.delta) && msg.delta !== 0) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/InputHandler.ts` around lines 143 - 144, The zoom branch allows NaN because it only checks msg.delta !== undefined and !== 0; update the condition to mirror the dx/dy guards (use Number.isFinite) — i.e. change the case 'zoom' guard to ensure Number.isFinite(msg.delta) && msg.delta !== 0 before computing scaledDelta and amount (these variables inside the zoom handling use Math.sign and will become NaN otherwise), and ensure you skip calling keyboard.pressKey(Key.LeftControl) and mouse.scrollUp/scrollDown when the delta is not a finite non-zero number. This keeps the existing variable names (msg.delta, scaledDelta, amount) and the side-effect calls (keyboard.pressKey, mouse.scrollUp) intact but prevents NaN from propagating.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/server/InputHandler.ts`:
- Line 174: The console.log that prints raw keystrokes (console.log(`Processing
key: ${msg.key}`)) in InputHandler should be removed or gated to avoid leaking
sensitive input; update the InputHandler message-processing code to either (a)
use a proper logger with debug-level checks (e.g., logger.debug(...) or if
(DEBUG) {...}) before emitting any key-related logs, or (b) redact the sensitive
field (log only masked info such as length or "<redacted>") instead of msg.key;
apply the same change to the similar logs in the combo handling branches
referencing msg.key/combo so production logs never contain plaintext keystrokes.
- Around line 17-22: Add a destroy() method to the InputHandler class that
cancels any pending throttle timers and clears pending messages and state: clear
and nullify moveTimer and scrollTimer (clearTimeout), set pendingMove and
pendingScroll to null, and reset any per-connection references/flags used by
handleMessage; then call inputHandler.destroy() from the websocket client close
handler (the ws.on('close') block) so pending timers won't fire after a client
disconnects. Ensure the destroy() method references the same fields shown
(moveTimer, scrollTimer, pendingMove, pendingScroll, lastMoveTime/lastScrollTime
if needed) and that no lingering closures can invoke handleMessage for a closed
connection.
---
Outside diff comments:
In `@src/server/InputHandler.ts`:
- Around line 143-144: The zoom branch allows NaN because it only checks
msg.delta !== undefined and !== 0; update the condition to mirror the dx/dy
guards (use Number.isFinite) — i.e. change the case 'zoom' guard to ensure
Number.isFinite(msg.delta) && msg.delta !== 0 before computing scaledDelta and
amount (these variables inside the zoom handling use Math.sign and will become
NaN otherwise), and ensure you skip calling keyboard.pressKey(Key.LeftControl)
and mouse.scrollUp/scrollDown when the delta is not a finite non-zero number.
This keeps the existing variable names (msg.delta, scaledDelta, amount) and the
side-effect calls (keyboard.pressKey, mouse.scrollUp) intact but prevents NaN
from propagating.
|
hi @imxade |
|
Looks good to me! |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/server/InputHandler.ts (1)
143-157:⚠️ Potential issue | 🟡 Minor
msg.deltais not guarded againstNaN/ non-numeric values, unlikedx/dy.If
msg.deltaisNaNor a non-numeric type, it passes the!== undefined && !== 0checks (sinceNaN !== 0istrue), andNaNpropagates throughMath.sign/Math.abs/Math.roundintomouse.scrollUp(NaN)whileLeftControlis held. Apply the sametypeof+Number.isFiniteguard used fordx/dy.🐛 Proposed fix
- case 'zoom': - if (msg.delta !== undefined && msg.delta !== 0) { + case 'zoom': + if (typeof msg.delta === 'number' && Number.isFinite(msg.delta) && msg.delta !== 0) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/InputHandler.ts` around lines 143 - 157, The zoom branch currently checks only msg.delta !== undefined && msg.delta !== 0 which allows NaN or non-numeric values through; change the guard to the same pattern used for dx/dy (verify typeof msg.delta === 'number' && Number.isFinite(msg.delta') in the case 'zoom' block before computing scaledDelta/amount) so Math.sign/Math.abs/Math.round never receive NaN; update the conditional around the scaledDelta/amount computation (the code surrounding msg.delta, sensitivityFactor, MAX_ZOOM_STEP, scaledDelta, amount) to bail out if the numeric/finite check fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/server/InputHandler.ts`:
- Around line 43-78: The timer callbacks for moveTimer and scrollTimer call the
async method handleMessage(pending) without handling its returned Promise,
causing unhandled rejection risk; update both setTimeout callbacks (the ones
that reference this.moveTimer/this.scrollTimer and
this.pendingMove/this.pendingScroll) to call handleMessage(pending).catch(err =>
/* handle/log error */) (or await inside an async IIFE) so any rejection is
caught and logged/handled instead of crashing the process.
---
Outside diff comments:
In `@src/server/InputHandler.ts`:
- Around line 143-157: The zoom branch currently checks only msg.delta !==
undefined && msg.delta !== 0 which allows NaN or non-numeric values through;
change the guard to the same pattern used for dx/dy (verify typeof msg.delta ===
'number' && Number.isFinite(msg.delta') in the case 'zoom' block before
computing scaledDelta/amount) so Math.sign/Math.abs/Math.round never receive
NaN; update the conditional around the scaledDelta/amount computation (the code
surrounding msg.delta, sensitivityFactor, MAX_ZOOM_STEP, scaledDelta, amount) to
bail out if the numeric/finite check fails.
---
Duplicate comments:
In `@src/server/InputHandler.ts`:
- Around line 172-185: The console.log calls in the InputHandler handling
(specifically the 'key' case logging msg.key and similar logs in the 'combo'
handling) expose raw keystrokes; remove these plaintext logs or gate them behind
a controlled debug flag/log level. Update the code in the 'key' case (and
mirrored lines in the combo handling) to either delete the console.log calls or
replace them with a logger.debug(...) that only runs when a DEBUG/LOG_LEVEL flag
is enabled (e.g., check process.env.DEBUG or a configured
logger.isDebugEnabled()) and ensure no raw msg.key is emitted in production.
Ensure the change consistently touches the console.log usages where msg.key or
combo keystrokes are logged (e.g., the Processing key / Unmapped key messages
and the combo-case logs).
- Around line 17-22: Add a new destroy() method on InputHandler that clears
moveTimer and scrollTimer via clearTimeout if present, sets moveTimer and
scrollTimer to null, and also nulls pendingMove and pendingScroll to avoid
callbacks after disconnect; then ensure websocket.ts calls
inputHandler.destroy() from the ws.on('close') handler so no timers or pending
messages remain after client disconnect. Reference the existing fields
moveTimer, scrollTimer, pendingMove, pendingScroll and the new destroy() method
when implementing and invoking it.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/server/InputHandler.ts`:
- Around line 71-73: The error log in the scroll-timer catch currently prints
"Error processing pending move event" which is a copy-paste mistake; update the
console.error inside the this.handleMessage(pending).catch(...) block in
InputHandler.ts to reference "scroll event" (or include the actual event type
from pending, e.g., pending.type) so the message correctly identifies a
scroll-related failure when calling handleMessage with pending.
- Around line 49-57: The moveTimer callback in InputHandler currently calls
this.handleMessage(pending) without handling promise rejections, causing
unhandled rejections; update the callback so the call mirrors the scrollTimer
pattern by chaining .catch(...) on this.handleMessage(pending) (and keep
this.moveTimer/nulling logic intact), i.e., locate the moveTimer closure and
replace the direct this.handleMessage(pending) call with a promise chain that
catches errors and logs or handles them the same way scrollTimer does.
---
Duplicate comments:
In `@src/server/InputHandler.ts`:
- Around line 17-22: Add a destroy() method to InputHandler that clears and
nulls moveTimer and scrollTimer and cancels any pendingMove/pendingScroll; call
this.destroy() from the ws.on('close') handler so timers cannot fire after
disconnect. Specifically, implement InputHandler.destroy() to call
clearTimeout(this.moveTimer) and clearTimeout(this.scrollTimer) if not null, set
moveTimer/scrollTimer/pendingMove/pendingScroll to null, and ensure any other
cleanup needed (e.g., remove event listeners) is performed; update the
ws.on('close') callback to invoke inputHandler.destroy().
- Line 176: Remove the plaintext keystroke logging or guard it with a runtime
debug flag: in InputHandler (the method that logs `console.log(\`Processing key:
${msg.key}\`)` and the similar logs at the other spots), either delete those
console.log calls entirely or wrap them in a check like `if
(DEBUG_INPUT_LOGGING) { ... }` so they are only emitted when an explicit,
non-default debug flag is enabled; ensure the debug flag is configurable off by
default and never logs raw msg.key values in production.
|
hi @imxade I have fixed the frame size from 16ms to 8ms |
Addressed Issues:
Closes # 28
Why these changes are important
If a client keeps sending mouse move or scroll events very fast in a loop, the server handles all of them without limits. This can overload the system and make the Mac freeze or become very slow. Also, if someone sends a very large text message, it can use too much memory and crash the server.
fixed
InputHandler.tswith changes to implement input validation (text length, coordinate bounds) and rate limiting (throttling) for high-frequency events.Changes Applied:
Rate Limiting: Added a lastEventTime timestamp and a check at the beginning of handleMessage to throttle move and scroll events to approximately 60 FPS (~16ms).
Input Sanitation: Added a check to truncate msg.text to 500 characters.
Coordinate Validation: Added clamping for msg.dx and msg.dy to ensure they stay within a sane range (+/- 2000 pixels) before processing.
Checklist
We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact.
@imxade Please review this , can be merged ,let me know if any changes required
Thankyou
Summary by CodeRabbit
Bug Fixes
Performance
Behavior