From ecfb0754b16c2b5324d42ad818289a7dec7e793e Mon Sep 17 00:00:00 2001 From: aniket866 Date: Thu, 12 Feb 2026 14:07:04 +0530 Subject: [PATCH 1/3] prevent-client-side-crash --- src/server/InputHandler.ts | 103 ++++++++++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 18 deletions(-) diff --git a/src/server/InputHandler.ts b/src/server/InputHandler.ts index 77ffe46..de55424 100644 --- a/src/server/InputHandler.ts +++ b/src/server/InputHandler.ts @@ -14,19 +14,37 @@ export interface InputMessage { } export class InputHandler { + // Security Constants (ADDED) + private static readonly MAX_TEXT_LENGTH = 500; + private static readonly MAX_DELTA = 2000; + private static readonly MAX_SCROLL = 500; + private static readonly MAX_ZOOM_INPUT = 1000; + private static readonly MAX_COMBO_KEYS = 5; + constructor() { mouse.config.mouseSpeed = 1000; } + // Utility clamp (ADDED) + private clamp(value: number, min: number, max: number): number { + return Math.max(min, Math.min(max, value)); + } + async handleMessage(msg: InputMessage) { switch (msg.type) { + case 'move': if (msg.dx !== undefined && msg.dy !== undefined) { + + // Clamp dx/dy + const safeDx = this.clamp(msg.dx, -InputHandler.MAX_DELTA, InputHandler.MAX_DELTA); + const safeDy = this.clamp(msg.dy, -InputHandler.MAX_DELTA, InputHandler.MAX_DELTA); + const currentPos = await mouse.getPosition(); await mouse.setPosition(new Point( - currentPos.x + msg.dx, - currentPos.y + msg.dy + currentPos.x + safeDx, + currentPos.y + safeDy )); } break; @@ -45,21 +63,30 @@ export class InputHandler { case 'scroll': const promises: Promise[] = []; + //Clamp scroll values + const safeScrollY = typeof msg.dy === 'number' + ? this.clamp(msg.dy, -InputHandler.MAX_SCROLL, InputHandler.MAX_SCROLL) + : 0; + + const safeScrollX = typeof msg.dx === 'number' + ? this.clamp(msg.dx, -InputHandler.MAX_SCROLL, InputHandler.MAX_SCROLL) + : 0; + // Vertical scroll - if (typeof msg.dy === 'number' && msg.dy !== 0) { - if (msg.dy > 0) { - promises.push(mouse.scrollDown(msg.dy)); + if (safeScrollY !== 0) { + if (safeScrollY > 0) { + promises.push(mouse.scrollDown(safeScrollY)); } else { - promises.push(mouse.scrollUp(-msg.dy)); + promises.push(mouse.scrollUp(-safeScrollY)); } } // Horizontal scroll - if (typeof msg.dx === 'number' && msg.dx !== 0) { - if (msg.dx > 0) { - promises.push(mouse.scrollRight(msg.dx)); + if (safeScrollX !== 0) { + if (safeScrollX > 0) { + promises.push(mouse.scrollRight(safeScrollX)); } else { - promises.push(mouse.scrollLeft(-msg.dx)); + promises.push(mouse.scrollLeft(-safeScrollX)); } } @@ -70,12 +97,20 @@ export class InputHandler { case 'zoom': if (msg.delta !== undefined && msg.delta !== 0) { + + // Clamp incoming zoom delta + const safeDelta = this.clamp( + msg.delta, + -InputHandler.MAX_ZOOM_INPUT, + InputHandler.MAX_ZOOM_INPUT + ); + const sensitivityFactor = 0.5; const MAX_ZOOM_STEP = 5; const scaledDelta = - Math.sign(msg.delta) * - Math.min(Math.abs(msg.delta) * sensitivityFactor, MAX_ZOOM_STEP); + Math.sign(safeDelta) * + Math.min(Math.abs(safeDelta) * sensitivityFactor, MAX_ZOOM_STEP); const amount = -scaledDelta; @@ -91,23 +126,48 @@ export class InputHandler { case 'key': if (msg.key) { console.log(`Processing key: ${msg.key}`); - const nutKey = KEY_MAP[msg.key.toLowerCase()]; + const lowerKey = msg.key.toLowerCase(); + + // Allowlist validation + if (!(lowerKey in KEY_MAP) && lowerKey.length !== 1) { + console.warn(`Blocked unknown key: ${msg.key}`); + return; + } + + const nutKey = KEY_MAP[lowerKey]; + if (nutKey !== undefined) { await keyboard.type(nutKey); - } else if (msg.key.length === 1) { - await keyboard.type(msg.key); - } else { - console.log(`Unmapped key: ${msg.key}`); + } else if (lowerKey.length === 1) { + await keyboard.type(lowerKey); } } break; case 'combo': if (msg.keys && msg.keys.length > 0) { + + // Limit combo length + if (msg.keys.length > InputHandler.MAX_COMBO_KEYS) { + console.warn('Combo too long — blocked'); + return; + } + + // Remove duplicate keys + const uniqueKeys = [...new Set(msg.keys)]; + const nutKeys: (Key | string)[] = []; - for (const k of msg.keys) { + + for (const k of uniqueKeys) { const lowerKey = k.toLowerCase(); + + if (!(lowerKey in KEY_MAP) && lowerKey.length !== 1) { + console.warn(`Blocked unknown key in combo: ${k}`); + continue; + } + const nutKey = KEY_MAP[lowerKey]; + if (nutKey !== undefined) { nutKeys.push(nutKey); } else if (lowerKey.length === 1) { @@ -148,6 +208,13 @@ export class InputHandler { case 'text': if (msg.text) { + + // Limit text length + if (msg.text.length > InputHandler.MAX_TEXT_LENGTH) { + console.warn('Text input too long — blocked'); + return; + } + await keyboard.type(msg.text); } break; From 34e26371f8a33fae00532f8dd6c206253ad1aa7e Mon Sep 17 00:00:00 2001 From: Aniket Date: Thu, 12 Feb 2026 15:40:18 +0530 Subject: [PATCH 2/3] Code rabbit follow up Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/server/InputHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/InputHandler.ts b/src/server/InputHandler.ts index de55424..6e374d8 100644 --- a/src/server/InputHandler.ts +++ b/src/server/InputHandler.ts @@ -27,6 +27,7 @@ export class InputHandler { // Utility clamp (ADDED) private clamp(value: number, min: number, max: number): number { + if (!Number.isFinite(value)) return 0; return Math.max(min, Math.min(max, value)); } From bf52df1f9912d0341bea94748b4e1cb9795ba50a Mon Sep 17 00:00:00 2001 From: Aniket Date: Thu, 12 Feb 2026 15:44:15 +0530 Subject: [PATCH 3/3] Code rabbit follow up Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/server/InputHandler.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/server/InputHandler.ts b/src/server/InputHandler.ts index 6e374d8..3d79611 100644 --- a/src/server/InputHandler.ts +++ b/src/server/InputHandler.ts @@ -155,7 +155,15 @@ export class InputHandler { } // Remove duplicate keys - const uniqueKeys = [...new Set(msg.keys)]; + const seen = new Set(); + const uniqueKeys: string[] = []; + for (const k of msg.keys) { + const lower = k.toLowerCase(); + if (!seen.has(lower)) { + seen.add(lower); + uniqueKeys.push(k); // preserve original for single-char typing + } + } const nutKeys: (Key | string)[] = [];