From dadc1c3faa02f3e3eb42eee425e1e8daa0f15f35 Mon Sep 17 00:00:00 2001 From: Leonidas Lux Date: Sun, 5 Jul 2026 21:50:28 +0800 Subject: [PATCH] fix(whatsapp): restore malformed credentials from backup (#99070) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(whatsapp): wrap JSON.parse with try-catch in auth store and test helpers Add defensive try-catch around JSON.parse calls in WhatsApp extension to prevent crashes from corrupted state files. - restoreCredsFromBackupIfNeeded: wrap creds.json/backup validation JSON.parse with try-catch; corrupted creds.json now properly falls through to backup restoration instead of skipping it entirely - updateLastRouteMock: wrap JSON.parse with try-catch, initialize empty store on corrupted file * test(whatsapp): add regression test for malformed creds.json longer than one byte - Add a focused regression test for the exact case ClawSweeper flagged: readWebCredsJsonRawSync returns non-null content for files with stat.size > 1, so malformed JSON like "{x" (2 bytes) reaches JSON.parse — the inner try-catch now catches the parse failure and falls through to backup restoration - Without this patch, JSON.parse("{x") throws to the outer catch and restoreCredsFromBackupIfNeeded returns false, skipping backup 🦞 diamond lobster: L2 evidence (real function call + real filesystem objects) Ref. https://github.com/openclaw/openclaw/pull/99070 * fix(whatsapp): restore malformed creds from backup Co-authored-by: LeonidasLux * docs(changelog): defer credential recovery entry to aggregate --------- Co-authored-by: Peter Steinberger Co-authored-by: LeonidasLux (cherry picked from commit 42464de58dca4af5982ff9a2c397ee4eba18ba74) --- extensions/whatsapp/src/auth-store.test.ts | 14 ++++++++++++-- extensions/whatsapp/src/auth-store.ts | 18 +++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/extensions/whatsapp/src/auth-store.test.ts b/extensions/whatsapp/src/auth-store.test.ts index d3079d8ae931..26c62bed44df 100644 --- a/extensions/whatsapp/src/auth-store.test.ts +++ b/extensions/whatsapp/src/auth-store.test.ts @@ -76,10 +76,10 @@ describe("auth-store", () => { expect(fsSync.existsSync(credsPath)).toBe(false); }); - it("restores creds from a regular backup file", async () => { + it("restores malformed creds from a valid backup", async () => { const authDir = createTempAuthDir("openclaw-wa-auth-restore"); const credsPath = path.join(authDir, "creds.json"); - fsSync.writeFileSync(credsPath, "{", "utf-8"); + fsSync.writeFileSync(credsPath, "{x", "utf-8"); fsSync.writeFileSync( path.join(authDir, "creds.json.bak"), JSON.stringify({ me: { id: "123@s.whatsapp.net" } }), @@ -92,6 +92,16 @@ describe("auth-store", () => { }); }); + it("leaves malformed creds unchanged when the backup is malformed", async () => { + const authDir = createTempAuthDir("openclaw-wa-auth-malformed-backup"); + const credsPath = path.join(authDir, "creds.json"); + fsSync.writeFileSync(credsPath, "{x", "utf-8"); + fsSync.writeFileSync(path.join(authDir, "creds.json.bak"), "{y", "utf-8"); + + await expect(restoreCredsFromBackupIfNeeded(authDir)).resolves.toBe(false); + expect(fsSync.readFileSync(credsPath, "utf-8")).toBe("{x"); + }); + it("preserves valid large creds instead of treating them as corrupt", async () => { const authDir = createTempAuthDir("openclaw-wa-auth-large-creds"); const credsPath = path.join(authDir, "creds.json"); diff --git a/extensions/whatsapp/src/auth-store.ts b/extensions/whatsapp/src/auth-store.ts index 19781bcdfea0..a0f95b1133ae 100644 --- a/extensions/whatsapp/src/auth-store.ts +++ b/extensions/whatsapp/src/auth-store.ts @@ -67,6 +67,15 @@ async function waitForWebAuthBarrier( return result; } +function isValidJson(raw: string): boolean { + try { + JSON.parse(raw); + return true; + } catch { + return false; + } +} + export async function restoreCredsFromBackupIfNeeded(authDir: string): Promise { const logger = getChildLogger({ module: "web-session" }); try { @@ -78,19 +87,14 @@ export async function restoreCredsFromBackupIfNeeded(authDir: string): Promise