Theseus: page scans run in an isolated world; install sheet names the risk

The wiz:// page scan ran in the page's own world, where the page can
replace RegExp, querySelectorAll or Set and shape what the wallet is
handed; it now runs in an isolated world of its own and the results
are checked again in main. The extension install sheet now says what a
community extension can reach while the vault is unlocked (passwords
and the wallet), since extensions still run in the main process.
This commit is contained in:
Local Dev 2026-10-04 04:29:02 +02:00
parent f834fbf80a
commit 76d342797b

15
main.js
View file

@ -2804,7 +2804,10 @@ function initAddons() {
// Percent-encoded QR spelling: WIZ://%3FP%3D… // Percent-encoded QR spelling: WIZ://%3FP%3D…
const qrSrc = `\\b${scheme}://(?:%[0-9A-Fa-f]{2}|[A-Za-z0-9._~$+-])+`; const qrSrc = `\\b${scheme}://(?:%[0-9A-Fa-f]{2}|[A-Za-z0-9._~$+-])+`;
const arg = JSON.stringify({ plainSrc, qrSrc, limit }); const arg = JSON.stringify({ plainSrc, qrSrc, limit });
const found = await wc.executeJavaScript(`(() => { // Run in an isolated world of our own (the inject preload uses 999):
// the page shares the DOM but not its globals, so it cannot replace
// RegExp, querySelectorAll or Set to shape what the scan returns.
const found = await wc.executeJavaScriptInIsolatedWorld(1001, [{ code: `(() => {
const { plainSrc, qrSrc, limit } = ${arg}; const { plainSrc, qrSrc, limit } = ${arg};
const out = new Set(); const out = new Set();
const plain = new RegExp(plainSrc, "gi"); const plain = new RegExp(plainSrc, "gi");
@ -2828,8 +2831,12 @@ function initAddons() {
for (const el of document.querySelectorAll("input,textarea")) scan(el.value); for (const el of document.querySelectorAll("input,textarea")) scan(el.value);
scan(document.body ? document.body.innerText : ""); scan(document.body ? document.body.innerText : "");
return [...out].slice(0, limit); return [...out].slice(0, limit);
})()`, true); })()` }], true);
const uris = Array.isArray(found) ? found.filter((s) => typeof s === "string") : []; // And checked again here, whatever came back.
const want = scheme.toLowerCase() + "://";
const uris = (Array.isArray(found) ? found : [])
.filter((s) => typeof s === "string" && s.length <= 2048 && s.toLowerCase().startsWith(want))
.slice(0, Math.max(0, Number(limit) || 0));
console.log(`[addons] ${addonId} scanned ${origin || "tab"} for ${scheme}:// — ${uris.length} match(es)`); console.log(`[addons] ${addonId} scanned ${origin || "tab"} for ${scheme}:// — ${uris.length} match(es)`);
return { origin, uris }; return { origin, uris };
}, },
@ -5692,7 +5699,7 @@ async function installExtensionWithConsent(id, requester) {
? `Update ${card.name || id} ${installed.version} → ${card.latest}?` ? `Update ${card.name || id} ${installed.version} → ${card.latest}?`
: `Install ${card.name || id} ${card.latest}?`, : `Install ${card.name || id} ${card.latest}?`,
detail: `${from}Published by ${card.publisher}. Theseus verifies the package signature against ${card.publisher}'s current owner in its own chain index before anything is written.\n\n` detail: `${from}Published by ${card.publisher}. Theseus verifies the package signature against ${card.publisher}'s current owner in its own chain index before anything is written.\n\n`
+ `A community extension runs with the same access as any add-on — treat it like a program from that publisher.`, + `A community extension runs inside Theseus with the same access as Theseus itself: while your vault is unlocked it can reach your saved passwords and your Aegis wallet. Install it only if you would install a program from this publisher.`,
buttons: ["Install", "Cancel"], defaultId: 1, cancelId: 1, noLink: true, buttons: ["Install", "Cancel"], defaultId: 1, cancelId: 1, noLink: true,
}); });
if (response !== 0) return { ok: false, error: "cancelled" }; if (response !== 0) return { ok: false, error: "cancelled" };