Aegis: the ETH and SOL bridge went missing on most pages, for good

The main-world bridge is appended at document_start, which often runs
before the page has an <html> element. The append threw on null, and the
catch marked the origin as Trusted-Types-blocked in localStorage, so every
later visit skipped window.ethereum, window.solana and the EIP-6963
announcement on that site. On example.com it failed on 6 of 6 loads.

Wait for <html> with a MutationObserver (it fires at the microtask
checkpoint before the first parser-inserted script, so the bridge is still
first), remember only real Trusted Types refusals, and use a new key so the
origins wrongly marked by the old one get the bridge back.
This commit is contained in:
Local Dev 2026-10-03 21:17:38 +02:00
parent 278da658ce
commit 129e4c6729

View file

@ -560,14 +560,20 @@ const mainWorldSource = `(function () {
// the big enforcing sites (no report at all), plus a per-origin memory for // the big enforcing sites (no report at all), plus a per-origin memory for
// any other site that rejected us once (one report, never again). // any other site that rejected us once (one report, never again).
const NO_BRIDGE_HOSTS = /(^|\.)(google\.[a-z.]+|googleapis\.com|gstatic\.com|youtube\.com|googleusercontent\.com|microsoftonline\.com|microsoft\.com|live\.com|office\.com|github\.com|apple\.com|icloud\.com)$/i; const NO_BRIDGE_HOSTS = /(^|\.)(google\.[a-z.]+|googleapis\.com|gstatic\.com|youtube\.com|googleusercontent\.com|microsoftonline\.com|microsoft\.com|live\.com|office\.com|github\.com|apple\.com|icloud\.com)$/i;
//
// The memory used to be "aegis:bridge-blocked", set on ANY failure. The
// commonest failure was ours: this preload often runs before the page has an
// <html> element, so the append threw on null and the origin lost the bridge
// for good. That key is ignored now; only a Trusted Types refusal is kept.
const BLOCKED_KEY = "aegis:bridge-blocked-tt";
function bridgeAllowedHere() { function bridgeAllowedHere() {
try { try {
if (NO_BRIDGE_HOSTS.test(location.hostname)) return false; if (NO_BRIDGE_HOSTS.test(location.hostname)) return false;
if (localStorage.getItem("aegis:bridge-blocked") === "1") return false; if (localStorage.getItem(BLOCKED_KEY) === "1") return false;
} catch {} } catch {}
return true; return true;
} }
if (bridgeAllowedHere()) { function installBridge() {
try { try {
let src = mainWorldSource; let src = mainWorldSource;
// Where Trusted Types exist but are not enforced, a policy keeps the // Where Trusted Types exist but are not enforced, a policy keeps the
@ -581,7 +587,26 @@ if (bridgeAllowedHere()) {
(document.head || document.documentElement).appendChild(s); (document.head || document.documentElement).appendChild(s);
s.remove(); s.remove();
} catch (e) { } catch (e) {
try { localStorage.setItem("aegis:bridge-blocked", "1"); } catch {} const msg = String(e && e.message || e);
console.warn("[aegis] main-world bridge install failed (this origin is now skipped):", e && e.message || e); if (/trusted/i.test(msg)) {
try { localStorage.setItem(BLOCKED_KEY, "1"); } catch {}
console.warn("[aegis] this site enforces Trusted Types; the wallet bridge stays off here:", msg);
} else {
console.warn("[aegis] main-world bridge install failed:", msg);
}
}
}
if (bridgeAllowedHere()) {
if (document.documentElement) installBridge();
else {
// The parser inserts <html> before it runs any page script, and
// observer callbacks run at the microtask checkpoint that precedes each
// parser-inserted script, so the bridge is still first.
const mo = new MutationObserver(() => {
if (!document.documentElement) return;
mo.disconnect();
installBridge();
});
mo.observe(document, { childList: true });
} }
} }