From 1e45428dec0ec30609344296677a1eb877775975 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Mon, 5 Oct 2026 23:40:16 +0200 Subject: [PATCH] Aegis: a BNS site and a web site of the same name are different sites Wallet permissions were filed by the origin Theseus reports, which is https://name for both registries. Whoever registered a colliding name in BNS inherited the real site's connection and its silent-payment allowance, and the other way round. A page served from BNS is now filed, and named on every approval, as bns://name. Grants saved before this stay under https://name: a saved connection only discloses an address and everything else still prompts, so the BNS site of that name simply asks again. Payment allowances are reset once, because nobody can say which registry's site an old one was granted to. Needs a host that reports the registry; older ones behave as before. --- bundled-addons/aegis/index.js | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/bundled-addons/aegis/index.js b/bundled-addons/aegis/index.js index 83d4a337..e71c40f7 100644 --- a/bundled-addons/aegis/index.js +++ b/bundled-addons/aegis/index.js @@ -1495,8 +1495,17 @@ function plainLabel(v, max = 80) { return String(v ?? "").replace(INVISIBLE_RE, "").replace(/\s+/g, " ").trim().slice(0, max); } function fromPanel(m) { if (!m || m.from !== "panel") throw new Error("panel-only message"); } +// The origin a page's permissions are filed under, and what the approval +// overlay names. A page served from BNS and a page served from the ordinary +// web are different sites even when the name is the same — Theseus reports +// both as "https://name", and BNS carries registrations under ICANN TLDs on +// purpose, so whoever registers a colliding name in BNS used to inherit the +// real site's wallet connection and its silent-payment allowance (and the +// other way round). A BNS page is filed under "bns://name" instead. Hosts +// that do not say which registry served the page keep the old behaviour. function fromPage(m) { if (!m || m.from !== "page" || !m.origin) throw new Error("page-only message"); + if (m.registry === "bns") return String(m.origin).replace(/^https?:\/\//i, "bns://"); return m.origin; } @@ -2770,6 +2779,7 @@ function registerPanelMessages(api) { syncInjectPolicy(api); return injectState(); }); + try { migrateRegistryOrigins(api); } catch {} try { syncInjectPolicy(api); } catch {} api.onMessage("permissions", (_p, m) => { fromPanel(m); return grantsForPanel(api); }); @@ -3365,9 +3375,28 @@ function injectAllowList(api) { } function injectMode(api) { return api.storage.get(INJECT_MODE_KEY, "all") === "allowed" ? "allowed" : "all"; } function syncInjectPolicy(api) { - const origins = Array.from(new Set([...injectAllowList(api), ...Object.keys(permissions(api))])); + // Visibility is by host name: the host matches a bns:// page against the + // https form, so a grant filed under "bns://name" is listed as that. + const origins = Array.from(new Set([...injectAllowList(api), ...Object.keys(permissions(api))].map(normalizeOrigin).filter(Boolean))); api.storage.set("__pageInjectPolicy", { mode: injectMode(api), origins }); } +// Once, when the host first tells BNS pages apart: grants saved before then +// are filed under "https://name" and may have been given to either registry's +// site. A saved connection only discloses an address and still prompts for +// everything else, so those stay (the BNS site of that name simply asks +// again). A payment allowance spends without a prompt — those are dropped, +// because nobody can say which site it was really granted to. +function migrateRegistryOrigins(api) { + if (!(api.features && api.features.pageRegistry)) return; + if (api.storage.get("aegis/migrations/registryOrigins", false)) return; + const perms = permissions(api); + let dropped = 0; + for (const o of Object.keys(perms)) { + if (perms[o] && perms[o].sendTx) { delete perms[o].sendTx; dropped++; } + } + if (dropped) { api.storage.set("permissions", perms); api.log(`registry-separated origins: ${dropped} payment allowance(s) reset`); } + api.storage.set("aegis/migrations/registryOrigins", { at: Date.now(), dropped }); +} function normalizeOrigin(raw) { try { const u = new URL(String(raw || "").replace(/^bns:\/\//i, "https://")); @@ -3771,7 +3800,7 @@ async function withOriginLock(origin, fn) { // protection is downstream and unchanged: an approval overlay on every // action plus per-origin permissions. This only keeps non-web schemes out. function isDappOrigin(origin) { - try { const u = new URL(origin); return u.protocol === "https:" || u.protocol === "http:"; } + try { const u = new URL(origin); return u.protocol === "https:" || u.protocol === "http:" || u.protocol === "bns:"; } catch { return false; } } function legacyBchRuntime() {