From 8f6d3a236af03e9ad2f97b3e6efd50920142ea13 Mon Sep 17 00:00:00 2001 From: Wraith <54374743+wraith4081@users.noreply.github.com> Date: Tue, 30 Dec 2025 23:11:39 +0300 Subject: [PATCH] fix(client): validate lobby IDs and sanitize logs (#2741) ## Description: validate lobby IDs and sanitize logs ## 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 ## Please put your Discord username so you can be contacted if a bug or regression is found: wraith4081 Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com> --- src/client/JoinPrivateLobbyModal.ts | 45 +++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/client/JoinPrivateLobbyModal.ts b/src/client/JoinPrivateLobbyModal.ts index 300ea08a9..7a7ece0d8 100644 --- a/src/client/JoinPrivateLobbyModal.ts +++ b/src/client/JoinPrivateLobbyModal.ts @@ -136,6 +136,22 @@ export class JoinPrivateLobbyModal extends LitElement { ); } + private isValidLobbyId(value: string): boolean { + return /^[a-zA-Z0-9]{8}$/.test(value); + } + + private normalizeLobbyId(input: string): string | null { + const trimmed = input.trim(); + if (!trimmed) return null; + const extracted = this.extractLobbyIdFromUrl(trimmed).trim(); + if (!this.isValidLobbyId(extracted)) return null; + return extracted; + } + + private sanitizeForLog(value: string): string { + return value.replace(/[\r\n]/g, ""); + } + private extractLobbyIdFromUrl(input: string): string { if (input.startsWith("http")) { if (input.includes("#join=")) { @@ -170,8 +186,14 @@ export class JoinPrivateLobbyModal extends LitElement { } private async joinLobby(): Promise { - const lobbyId = this.lobbyIdInput.value; - console.log(`Joining lobby with ID: ${lobbyId}`); + const lobbyId = this.normalizeLobbyId(this.lobbyIdInput.value); + if (!lobbyId) { + this.message = translateText("private_lobby.not_found"); + return; + } + + this.lobbyIdInput.value = lobbyId; + console.log(`Joining lobby with ID: ${this.sanitizeForLog(lobbyId)}`); this.message = `${translateText("private_lobby.checking")}`; try { @@ -278,8 +300,9 @@ export class JoinPrivateLobbyModal extends LitElement { // Allow DEV to join games created with a different version for debugging. if (myGitCommit !== "DEV" && parsed.data.gitCommit !== myGitCommit) { + const safeLobbyId = this.sanitizeForLog(lobbyId); console.warn( - `Git commit hash mismatch for game ${lobbyId}`, + `Git commit hash mismatch for game ${safeLobbyId}`, archiveData.details, ); return "version_mismatch"; @@ -300,18 +323,16 @@ export class JoinPrivateLobbyModal extends LitElement { } private async pollPlayers() { - if (!this.lobbyIdInput?.value) return; + const lobbyId = this.normalizeLobbyId(this.lobbyIdInput.value); + if (!lobbyId) return; const config = await getServerConfigFromClient(); - fetch( - `/${config.workerPath(this.lobbyIdInput.value)}/api/game/${this.lobbyIdInput.value}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - }, + fetch(`/${config.workerPath(lobbyId)}/api/game/${lobbyId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", }, - ) + }) .then((response) => response.json()) .then((data: GameInfo) => { this.players = data.clients?.map((p) => p.username) ?? [];