Skip to content

Commit 2436391

Browse files
authored
Merge pull request #87 from aniket866/fixing-input-validation
Harden InputHandler Against Event Flooding and Oversized Payload Attacks (Add Rate Limiting & Input Safeguards)
2 parents b299e45 + f747e55 commit 2436391

File tree

1 file changed

+80
-4
lines changed

1 file changed

+80
-4
lines changed

src/server/InputHandler.ts

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,73 @@ export interface InputMessage {
1414
}
1515

1616
export class InputHandler {
17+
private lastMoveTime = 0;
18+
private lastScrollTime = 0;
19+
private pendingMove: InputMessage | null = null;
20+
private pendingScroll: InputMessage | null = null;
21+
private moveTimer: ReturnType<typeof setTimeout> | null = null;
22+
private scrollTimer: ReturnType<typeof setTimeout> | null = null;
23+
1724
constructor() {
1825
mouse.config.mouseSpeed = 1000;
1926
}
2027

2128
async handleMessage(msg: InputMessage) {
29+
// Validation: Text length sanitation
30+
if (msg.text && msg.text.length > 500) {
31+
msg.text = msg.text.substring(0, 500);
32+
}
33+
34+
// Validation: Sane bounds for coordinates
35+
const MAX_COORD = 2000;
36+
if (typeof msg.dx === 'number' && Number.isFinite(msg.dx)) {
37+
msg.dx = Math.max(-MAX_COORD, Math.min(MAX_COORD, msg.dx));
38+
}
39+
if (typeof msg.dy === 'number' && Number.isFinite(msg.dy)) {
40+
msg.dy = Math.max(-MAX_COORD, Math.min(MAX_COORD, msg.dy));
41+
}
42+
43+
// Throttling: Limit high-frequency events to ~125fps (8ms)
44+
if (msg.type === 'move') {
45+
const now = Date.now();
46+
if (now - this.lastMoveTime < 8) {
47+
this.pendingMove = msg;
48+
if (!this.moveTimer) {
49+
this.moveTimer = setTimeout(() => {
50+
this.moveTimer = null;
51+
if (this.pendingMove) {
52+
const pending = this.pendingMove;
53+
this.pendingMove = null;
54+
this.handleMessage(pending).catch((err) => {
55+
console.error('Error processing pending move event:', err);
56+
});
57+
}
58+
}, 8);
59+
}
60+
return;
61+
}
62+
this.lastMoveTime = now;
63+
} else if (msg.type === 'scroll') {
64+
const now = Date.now();
65+
if (now - this.lastScrollTime < 8) {
66+
this.pendingScroll = msg;
67+
if (!this.scrollTimer) {
68+
this.scrollTimer = setTimeout(() => {
69+
this.scrollTimer = null;
70+
if (this.pendingScroll) {
71+
const pending = this.pendingScroll;
72+
this.pendingScroll = null;
73+
this.handleMessage(pending).catch((err) => {
74+
console.error('Error processing pending move event:', err);
75+
});
76+
}
77+
}, 8);
78+
}
79+
return;
80+
}
81+
this.lastScrollTime = now;
82+
}
83+
2284
switch (msg.type) {
2385
case 'move':
2486
if (
@@ -38,7 +100,13 @@ export class InputHandler {
38100

39101
case 'click':
40102
if (msg.button) {
41-
const btn = msg.button === 'left' ? Button.LEFT : msg.button === 'right' ? Button.RIGHT : Button.MIDDLE;
103+
const btn =
104+
msg.button === 'left'
105+
? Button.LEFT
106+
: msg.button === 'right'
107+
? Button.RIGHT
108+
: Button.MIDDLE;
109+
42110
if (msg.press) {
43111
await mouse.pressButton(btn);
44112
} else {
@@ -83,7 +151,10 @@ export class InputHandler {
83151

84152
const scaledDelta =
85153
Math.sign(msg.delta) *
86-
Math.min(Math.abs(msg.delta) * sensitivityFactor, MAX_ZOOM_STEP);
154+
Math.min(
155+
Math.abs(msg.delta) * sensitivityFactor,
156+
MAX_ZOOM_STEP
157+
);
87158

88159
const amount = Math.round(-scaledDelta);
89160

@@ -106,6 +177,7 @@ export class InputHandler {
106177
if (msg.key) {
107178
console.log(`Processing key: ${msg.key}`);
108179
const nutKey = KEY_MAP[msg.key.toLowerCase()];
180+
109181
if (nutKey !== undefined) {
110182
await keyboard.type(nutKey);
111183
} else if (msg.key.length === 1) {
@@ -119,9 +191,11 @@ export class InputHandler {
119191
case 'combo':
120192
if (msg.keys && msg.keys.length > 0) {
121193
const nutKeys: (Key | string)[] = [];
194+
122195
for (const k of msg.keys) {
123196
const lowerKey = k.toLowerCase();
124197
const nutKey = KEY_MAP[lowerKey];
198+
125199
if (nutKey !== undefined) {
126200
nutKeys.push(nutKey);
127201
} else if (lowerKey.length === 1) {
@@ -141,15 +215,17 @@ export class InputHandler {
141215

142216
try {
143217
for (const k of nutKeys) {
144-
if (typeof k === "string") {
218+
if (typeof k === 'string') {
145219
await keyboard.type(k);
146220
} else {
147221
await keyboard.pressKey(k);
148222
pressedKeys.push(k);
149223
}
150224
}
151225

152-
await new Promise(resolve => setTimeout(resolve, 10));
226+
await new Promise(resolve =>
227+
setTimeout(resolve, 10)
228+
);
153229
} finally {
154230
for (const k of pressedKeys.reverse()) {
155231
await keyboard.releaseKey(k);

0 commit comments

Comments
 (0)