From 7bf360b2eefd258354f3a45fd832ddfafa6c4998 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 05:16:14 +0000 Subject: [PATCH] Secure migration of legacy R2 settings from LocalStorage - Modified `loadLegacyR2Settings` to check `localStorage` if IndexedDB is empty. - Implemented immediate migration of settings from `localStorage` to IndexedDB upon access. - Added logic to clear `localStorage` after successful migration to prevent secret exposure. - Verified fix with reproduction script and regression tests. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- js/r2.js | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/js/r2.js b/js/r2.js index fcaa8b08..7e9a4ee5 100644 --- a/js/r2.js +++ b/js/r2.js @@ -101,7 +101,7 @@ export async function loadLegacyR2Settings() { try { const db = await openSettingsDb(); if (db) { - return await new Promise((resolve, reject) => { + const fromDb = await new Promise((resolve, reject) => { const tx = db.transaction(STORE_NAME, "readonly"); const store = tx.objectStore(STORE_NAME); const req = store.get(SETTINGS_KEY); @@ -109,9 +109,16 @@ export async function loadLegacyR2Settings() { resolve(normalizeSettings(req.result)); }; req.onerror = () => { - reject(req.error || new Error("Failed to load settings")); + // If read fails, resolve null to try fallback + resolve(null); }; }); + + // If we have valid settings from DB, return them. + // We check for accountId as a signal that settings are present. + if (fromDb && fromDb.accountId) { + return fromDb; + } } } catch (err) { userLogger.warn("Failed to open IndexedDB for settings, falling back:", err); @@ -121,7 +128,37 @@ export async function loadLegacyR2Settings() { if (typeof localStorage !== "undefined") { const raw = localStorage.getItem(LOCALSTORAGE_FALLBACK_KEY); if (raw) { - return normalizeSettings(JSON.parse(raw)); + const parsed = JSON.parse(raw); + const settings = normalizeSettings(parsed); + + // Security Fix: Move secrets from insecure LocalStorage to IndexedDB immediately + try { + const db = await openSettingsDb(); + if (db) { + await new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite"); + const store = tx.objectStore(STORE_NAME); + // Store the raw parsed object, not normalized, to preserve original structure + // though normalizeSettings handles it fine either way. + const req = store.put(parsed, SETTINGS_KEY); + req.onsuccess = () => resolve(); + req.onerror = () => reject(req.error); + }); + localStorage.removeItem(LOCALSTORAGE_FALLBACK_KEY); + userLogger.info( + "Migrated legacy R2 settings from LocalStorage to IndexedDB" + ); + } + } catch (migrationErr) { + userLogger.warn( + "Failed to migrate legacy settings to IndexedDB:", + migrationErr + ); + // If migration fails, we still return settings to avoid data loss, + // but the vulnerability remains until next successful load. + } + + return settings; } } } catch (err) {