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 <[email protected]>
This commit is contained in:
google-labs-jules[bot]andPR0M3TH3AN committed 2026-02-17 05:16:14 +00:00
1 parent 84e7f50f96
commit 7bf360b2ee
1 file changed
+40 -3
+40 -3
View File
@@ -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) {