From 145e97d987181007cea44aafd1f31823bb496742 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:56:43 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[MEDIUM]=20?= =?UTF-8?q?Add=20structural=20validation=20for=20deserialized=20save=20dat?= =?UTF-8?q?a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented structural validation in `SaveSystem.deserialize` to ensure that game state loaded from `localStorage` contains all required fields with correct types. This prevents potential runtime crashes or logic errors caused by malformed or maliciously modified save data. - Added `isValidSaveData` private method to `SaveSystem`. - Updated `deserialize` to use `isValidSaveData` before returning. - Updated security journal in `.jules/sentinel.md`. Co-authored-by: b0x1 <21123655+b0x1@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ src/game/systems/SaveSystem.ts | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 57c2298..ea4e8c3 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -2,3 +2,8 @@ **Vulnerability:** Missing input length limits on user-provided strings (Player Name) could lead to UI breakage or minor DoS. **Learning:** Enforcing limits at both the UI layer (maxLength) and the System layer (trim/slice) provides defense-in-depth and ensures data integrity regardless of the entry point. **Prevention:** Always apply length constraints and sanitization to user-controlled inputs that are persisted or rendered globally. + +## 2025-05-16 - Structural Validation for Deserialized Data +**Vulnerability:** Insecure deserialization of save game data from `localStorage` using blind type casting (`as SaveData`). +**Learning:** Blindly trusting data from `localStorage` or other external sources can lead to runtime crashes or logic errors if the data is malformed or maliciously modified. +**Prevention:** Always perform structural validation (checking presence and types of required fields) on deserialized JSON data before using it in application logic. diff --git a/src/game/systems/SaveSystem.ts b/src/game/systems/SaveSystem.ts index e3044ac..c7d7c11 100644 --- a/src/game/systems/SaveSystem.ts +++ b/src/game/systems/SaveSystem.ts @@ -34,12 +34,33 @@ export class SaveSystem { static deserialize(serialized: string): SaveData | null { try { - return JSON.parse(serialized, (key, value) => this.reviver(key, value)) as SaveData; + const parsed = JSON.parse(serialized, (key, value) => this.reviver(key, value)) as unknown; + if (this.isValidSaveData(parsed)) { + return parsed; + } + return null; } catch { return null; } } + private static isValidSaveData(data: unknown): data is SaveData { + if (typeof data !== 'object' || data === null) { + return false; + } + + const candidate = data as Record; + return ( + Array.isArray(candidate.players) && + typeof candidate.currentPlayerId === 'string' && + typeof candidate.turn === 'number' && + typeof candidate.phase === 'string' && + candidate.europePrices !== null && + typeof candidate.europePrices === 'object' && + Array.isArray(candidate.map) + ); + } + private static replacer(_key: string, value: unknown): unknown { if (value instanceof Map) { return {