feat(aegis): the BCH bridge works on any site, not just .x

window.bitcoincash was gated on a ".x" hostname in both the preload and the
host handlers — inherited from the pre-multi-wallet build, when every dapp
Aegis knew about was a Silent Mode one. It was never a security boundary: the
TRX / ETH / SOL bridges have always injected everywhere, and every BCH call
still crosses into main, raises an approval overlay and honours per-origin
permissions. The preload cannot read an address or sign anything by itself.

What the gate actually cost was Cauldron, bch.guru and our own dapps on other
TLDs (potidaea.asm) seeing no BCH provider at all, on a wallet whose entire
subject is BCH. WizardConnect was never gated this way, so the two halves of
the same bridge disagreed about who could talk to it.

isBchOrigin becomes isDappOrigin and now only keeps non-web schemes out —
file://, chrome://, data:, javascript: and anything unparseable.

Separately: page-inject declared https://*/* only, so a dapp served from
http://127.0.0.1 got no preload at all, which is a different cause from the
.x gate and would have survived removing it. localhost, 127.0.0.1 and [::1]
are now matched over http as well (the host's compiler makes the port
optional, so :3000 and :5173 are covered). Plain http on any other host stays
out, since an http page is tamperable in transit and only local development
needs the exception.
This commit is contained in:
Local Dev 2026-10-03 09:45:01 +02:00
parent 40e7fe2ae9
commit 64b75c5bf4
3 changed files with 38 additions and 22 deletions

View file

@ -1,9 +1,9 @@
{
"id": "aegis",
"name": "Aegis Wallet",
"version": "0.26.1",
"version": "0.27.0",
"category": "plugin",
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash on .x sites; window.tronWeb / window.tronLink / window.ethereum / window.solana on any https page.",
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash and window.wizardconnect on any site; window.tronWeb / window.tronLink / window.ethereum / window.solana too. Every call needs your approval.",
"author": "Silent Mode",
"icon": "data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' fill='none'%3E%3Cpolygon points='16,2 28,9 28,23 16,30 4,23 4,9' fill='%230a0a0d' stroke='%23D6FF3D' stroke-width='1.6' stroke-linejoin='round'/%3E%3Ccircle cx='16' cy='16' r='4.5' fill='none' stroke='%23D6FF3D' stroke-width='1.4'/%3E%3Ccircle cx='16' cy='16' r='1.6' fill='%23D6FF3D'/%3E%3C/svg%3E",
"main": "index.js",
@ -23,7 +23,10 @@
"page-inject": {
"preload": "wallet-inject.js",
"origins": [
"https://*/*"
"https://*/*",
"http://localhost/*",
"http://127.0.0.1/*",
"http://[::1]/*"
]
}
}

View file

@ -2492,10 +2492,15 @@ async function withOriginLock(origin, fn) {
// Only .x sites (BCNR-native TLD) get the BCH bridge, matching the pre-
// multi-wallet gate. Widening the manifest to https://*/* makes the Tron
// bridge available everywhere; the BCH side enforces its narrower rule
// inside the handlers.
function isBchOrigin(origin) {
try { const h = new URL(origin).hostname; return /\.x$/.test(h); }
// bridge available everywhere.
// Any ordinary web origin may talk to the BCH bridge. The old test here
// required a ".x" hostname, which locked out every third-party BCH dapp
// (Cauldron, bch.guru) and our own dapps on other TLDs — while the TRX / ETH
// / SOL bridges and WizardConnect accepted any origin all along. The real
// 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:"; }
catch { return false; }
}
function legacyBchRuntime() {
@ -2518,7 +2523,7 @@ function registerPageMessages(api) {
// ---- BCH bridge (unchanged behavior; wallet source is legacy default) ----
api.onMessage("getAddress", async (_p, m) => {
const origin = fromPage(m);
if (!isBchOrigin(origin)) throw new Error("this site is not on a Bitcoin Cash origin");
if (!isDappOrigin(origin)) throw new Error("this page cannot use the wallet bridge");
const rt = legacyBchRuntime();
const perms = permissions(api);
if (perms[origin] && perms[origin].readAddress) return rt.adapter.current().address;
@ -2538,7 +2543,7 @@ function registerPageMessages(api) {
});
api.onMessage("signAndSend", async (p, m) => {
const origin = fromPage(m);
if (!isBchOrigin(origin)) throw new Error("this site is not on a Bitcoin Cash origin");
if (!isDappOrigin(origin)) throw new Error("this page cannot use the wallet bridge");
const rt = legacyBchRuntime();
return withOriginLock(origin, async () => {
let plan;
@ -2592,7 +2597,7 @@ function registerPageMessages(api) {
});
api.onMessage("signMessage", async (p, m) => {
const origin = fromPage(m);
if (!isBchOrigin(origin)) throw new Error("this site is not on a Bitcoin Cash origin");
if (!isDappOrigin(origin)) throw new Error("this page cannot use the wallet bridge");
const rt = legacyBchRuntime();
const message = String(p && p.message != null ? p.message : "");
if (message.length > 4096) throw new Error("message too long");

View file

@ -19,18 +19,26 @@ const call = (msg, payload) =>
throw new Error(text);
});
// -------- BCH bridge (window.bitcoincash), only on .x pages ------------------
const isBchOrigin = (() => { try { return /\.x$/i.test(location.hostname); } catch { return false; } })();
if (isBchOrigin) {
theseus.contextBridge.exposeInMainWorld("bitcoincash", {
// -------- BCH bridge (window.bitcoincash), everywhere -----------------------
//
// This used to be gated on a `.x` hostname, inherited from the
// pre-multi-wallet build where every dapp Aegis knew about was a Silent Mode
// one. It was never a security boundary: the TRX / ETH / SOL bridges have
// always injected on every page, and every call here still crosses into
// main, raises an approval overlay and honours per-origin permissions.
// Nothing in this file can read an address or sign anything on its own.
//
// Keeping it meant Cauldron, bch.guru and our own non-.x dapps (potidaea.asm)
// saw no BCH provider at all, on a wallet whose whole point is BCH.
// WizardConnect below was never gated this way, so the two halves disagreed.
theseus.contextBridge.exposeInMainWorld("bitcoincash", {
isTheseus: true,
version: "0.2.0",
network: "mainnet",
getAddress: () => call("getAddress"),
signAndSend: (txSpec) => call("signAndSend", txSpec && typeof txSpec === "object" ? txSpec : {}),
signMessage: (message) => call("signMessage", { message: String(message ?? "") }),
});
}
});
// -------- Tron bridge (window.tronWeb, window.tronLink), everywhere ---------
//