From 14ab1bcbbafa8b99af305a1ef41b0619a0aa5fd2 Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Wed, 4 Jun 2025 12:48:48 -0400 Subject: [PATCH] Close socket on ClientMessageSchema, improve zod error (#1003) ## Description: - Close the socket on parse failure. - Use `safeParse` and `prettifyError` to improve logging output on zod validation failures. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced - [x] I understand that submitting code with bugs that could have been caught through manual testing blocks releases and new features for all contributors Co-authored-by: Scott Anderson <662325+scottanderson@users.noreply.github.com> Co-authored-by: evanpelle --- src/server/GameServer.ts | 16 ++++++++++------ src/server/Worker.ts | 13 ++++++++++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index 003542bd8..5754ad218 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -1,9 +1,9 @@ import ipAnonymize from "ip-anonymize"; import { Logger } from "winston"; import WebSocket from "ws"; +import { z } from "zod/v4"; import { ClientID, - ClientMessage, ClientMessageSchema, ClientSendWinnerMessage, GameConfig, @@ -178,12 +178,16 @@ export class GameServer { "message", gatekeeper.wsHandler(client.ip, async (message: string) => { try { - let clientMsg: ClientMessage | null = null; - try { - clientMsg = ClientMessageSchema.parse(JSON.parse(message)); - } catch (error) { - throw Error(`error parsing schema for ${ipAnonymize(client.ip)}`); + const parsed = ClientMessageSchema.safeParse(JSON.parse(message)); + if (!parsed.success) { + const error = z.prettifyError(parsed.error); + this.log.error("Failed to parse client message", error, { + clientID: client.clientID, + }); + client.ws.close(); + return; } + const clientMsg = parsed.data; if (clientMsg.type === "intent") { if (clientMsg.intent.clientID !== client.clientID) { this.log.warn( diff --git a/src/server/Worker.ts b/src/server/Worker.ts index 068799fed..3f072ebdf 100644 --- a/src/server/Worker.ts +++ b/src/server/Worker.ts @@ -10,7 +10,8 @@ import { GameEnv } from "../core/configuration/Config"; import { getServerConfigFromServer } from "../core/configuration/ConfigLoader"; import { GameType } from "../core/game/Game"; import { - ClientMessageSchema, + ClientJoinMessageSchema, + GameConfig, GameRecord, GameRecordSchema, } from "../core/Schemas"; @@ -293,11 +294,17 @@ export function startWorker() { : forwarded || req.socket.remoteAddress || "unknown"; try { - // Process WebSocket messages as in your original code // Parse and handle client messages - const clientMsg = ClientMessageSchema.parse( + const parsed = ClientJoinMessageSchema.safeParse( JSON.parse(message.toString()), ); + if (!parsed.success) { + const error = z.prettifyError(parsed.error); + log.warn("Error parsing join message client", error); + ws.close(); + return; + } + const clientMsg = parsed.data; if (clientMsg.type === "join") { // Verify this worker should handle this game