Theseus: scrub stale add-on store copies of the PIN and session secrets
Stores nothing reads any more kept whatever they held when they were copied: the pre-rename addons-data/ folder, the bchwallet.json left by the Aegis absorb, and parse-failure copies. For Aegis before 0.31 that could include the master password behind only an unsealed 6-digit PIN and the stay-unlocked blob. Those two keys are removed from such copies at startup; nothing else in them is touched, and the live store is not among them. extensions-backups/, which kept a copy of an add-on on every reseed and update forever, is pruned to the newest three per add-on.
This commit is contained in:
parent
2f7d42d077
commit
455e468186
1 changed files with 53 additions and 0 deletions
53
main.js
53
main.js
|
|
@ -2224,6 +2224,57 @@ function migrateExtensionDirs() {
|
||||||
catch (e) { console.warn(`[addons] could not move ${from} -> ${to}:`, e?.message); }
|
catch (e) { console.warn(`[addons] could not move ${from} -> ${to}:`, e?.message); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Copies of add-on stores that nothing reads any more still hold whatever
|
||||||
|
// was in them when they were copied — for Aegis builds before 0.31 that
|
||||||
|
// included the master password behind only a 6-digit PIN (aegis/pin/v1,
|
||||||
|
// unsealed) and the stay-unlocked blob. The stale pre-rename addons-data/
|
||||||
|
// folder (kept when extensions-data/ already existed), the bchwallet.json
|
||||||
|
// left behind by the Aegis absorb, and parse-failure copies are scrubbed of
|
||||||
|
// those keys; everything else in them is left as it was.
|
||||||
|
const SECRET_STORE_KEYS = ["aegis/pin/v1", "aegis/session/enc"];
|
||||||
|
function scrubStaleStoreCopies() {
|
||||||
|
const ud = app.getPath("userData");
|
||||||
|
const files = [];
|
||||||
|
const listJson = (d, test) => { try { for (const n of fs.readdirSync(d)) if (test(n)) files.push(path.join(d, n)); } catch {} };
|
||||||
|
listJson(path.join(ud, "addons-data"), (n) => /\.json(\.tmp)?$/i.test(n));
|
||||||
|
listJson(path.join(ud, "extensions-data"), (n) => /^bchwallet\.json$/i.test(n) || /\.json\.(corrupt-\d+|tmp)$/i.test(n));
|
||||||
|
for (const f of files) {
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(f, "utf8");
|
||||||
|
if (!SECRET_STORE_KEYS.some((k) => raw.includes(JSON.stringify(k)))) continue;
|
||||||
|
let data;
|
||||||
|
try { data = JSON.parse(raw); } catch { continue; }
|
||||||
|
if (!data || typeof data !== "object") continue;
|
||||||
|
let n = 0;
|
||||||
|
for (const k of SECRET_STORE_KEYS) if (k in data) { delete data[k]; n++; }
|
||||||
|
if (!n) continue;
|
||||||
|
fs.writeFileSync(f, JSON.stringify(data));
|
||||||
|
console.log(`[addons] removed ${n} stale secret key(s) from ${path.relative(ud, f)}`);
|
||||||
|
} catch (e) { console.warn("[addons] scrub failed for", f, e?.message); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// extensions-backups/ gets a copy of an add-on's folder on every reseed,
|
||||||
|
// promote and community update and was never pruned (118 copies, 93 MB on
|
||||||
|
// one profile). Keep the newest three per add-on.
|
||||||
|
function pruneAddonBackups(keep = 3) {
|
||||||
|
const d = addonsBackupDir();
|
||||||
|
let names = [];
|
||||||
|
try { names = fs.readdirSync(d, { withFileTypes: true }).filter((x) => x.isDirectory()).map((x) => x.name); } catch { return; }
|
||||||
|
const byId = new Map();
|
||||||
|
for (const name of names) {
|
||||||
|
const m = /^(.+?)-(?:\d[\w.]*|unknown|migrated)-/.exec(name);
|
||||||
|
const id = m ? m[1] : name;
|
||||||
|
let mtime = 0; try { mtime = fs.statSync(path.join(d, name)).mtimeMs; } catch {}
|
||||||
|
if (!byId.has(id)) byId.set(id, []);
|
||||||
|
byId.get(id).push({ name, mtime });
|
||||||
|
}
|
||||||
|
for (const [, list] of byId) {
|
||||||
|
list.sort((a, b) => b.mtime - a.mtime);
|
||||||
|
for (const { name } of list.slice(keep)) {
|
||||||
|
try { fs.rmSync(path.join(d, name), { recursive: true, force: true }); } catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
function bundledAddonsDir() { return path.join(RES_DIR, "bundled-addons"); }
|
function bundledAddonsDir() { return path.join(RES_DIR, "bundled-addons"); }
|
||||||
// Ids that ship inside Theseus, and the legacy ids those add-ons absorb
|
// Ids that ship inside Theseus, and the legacy ids those add-ons absorb
|
||||||
// (Aegis absorbs "bchwallet" and "siawallet": the wallet's key namespace).
|
// (Aegis absorbs "bchwallet" and "siawallet": the wallet's key namespace).
|
||||||
|
|
@ -8285,6 +8336,8 @@ if (!process.env.THESEUS_NO_AUTOSTART && !app.requestSingleInstanceLock()) {
|
||||||
installDownloadTracker();
|
installDownloadTracker();
|
||||||
installRequestFilter();
|
installRequestFilter();
|
||||||
migrateExtensionDirs();
|
migrateExtensionDirs();
|
||||||
|
scrubStaleStoreCopies();
|
||||||
|
setTimeout(() => { try { pruneAddonBackups(); } catch {} }, 20000);
|
||||||
initAddons();
|
initAddons();
|
||||||
// Custom engines saved before icons were cached (or whose fetch never
|
// Custom engines saved before icons were cached (or whose fetch never
|
||||||
// landed) get theirs once the startup burst is over.
|
// landed) get theirs once the startup burst is over.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue