feat: vendor and censor the phishing blocklist at build time (closes #219)
The blocklist URL in shipped code named a competitor and pointed at a moving ref, and the extension re-fetched from it every 24 hours, which also meant a third party decided what this wallet warns about. All of that is gone. script/vendor-blocklist fetches upstream at a pinned commit, verifies the sha256 of the bytes that commit serves, and writes src/shared/phishingBlocklist.json. It is build-time tooling, never shipped, and the one place in the repo that names the upstream project; a source reference nobody can verify is not a source reference. The artifact stores truncated sha256 digests rather than domain names. That is what censors it: the previous file contained the competitor's name 6,475 times, as phishing domains impersonating them, and not one of those domains is dropped. It also makes lookups a binary search over a fixed-width string, so nothing is built at module load — which matters on MV3, where the worker re-evaluates the module on every wake — and takes the file from 8.7 MB to 1.7 MB. script/check-censored enforces the rest: it reads the name out of the vendoring script rather than repeating it, and fails on any occurrence in the working tree or under dist/ that is not one of the three literals shipped code cannot avoid — two provider-shim identifiers in src/content/inpage.js and one ERC-20's on-chain name in src/shared/tokenList.js. Each is permitted only at the path that carries it, and at the emitted paths that path is bundled into, so a literal appearing anywhere else fails like any other occurrence. It runs in make check, which inspects dist/ when there is one and says loudly when there is not, and again with --require-dist at the end of every make build. Removing the runtime fetch retires the delta, the extension-storage persistence and the 24-hour alarm from #158. A retired alarm is now cleared rather than left waking the worker forever on installs that already have it. The e2e suite drives the warning end to end from a real blocklisted origin served as a real http(s) site, with a control asserting the banner stays hidden for one that is not listed. Its service-worker interception canary needed a new anchor, since the startup fetch it used to watch for no longer happens: it now wakes the worker with a message and asks it for one throwaway fetch. LICENSE no longer cites a repository that returns 404. eslint.config.js gains one block: script/lib/ holds node programs the shell entrypoints call, and without it they lint with no globals at all.
This commit is contained in:
@@ -33,6 +33,7 @@ const {
|
||||
const {
|
||||
DAPP_ORIGIN,
|
||||
DAPP_URL,
|
||||
PHISHING_DAPP_URL,
|
||||
FEE_ESTIMATE_WEI,
|
||||
FEE_RESERVE_WEI,
|
||||
STUB_COUNTERPARTY,
|
||||
@@ -2069,9 +2070,9 @@ async function extensionActiveAddress(page) {
|
||||
return getAddress(address);
|
||||
}
|
||||
|
||||
async function openDapp(ctx) {
|
||||
async function openDapp(ctx, url = DAPP_URL) {
|
||||
const page = await ctx.newPage();
|
||||
await page.goto(DAPP_URL);
|
||||
await page.goto(url);
|
||||
// window.ethereum is not the fixture's doing — it is the shipped
|
||||
// MAIN-world content script. Waiting for it is waiting for the real
|
||||
// provider to have injected itself into a real http(s) origin.
|
||||
@@ -2543,6 +2544,15 @@ test("eth_requestAccounts rejected at the prompt returns a rejection (#183)", as
|
||||
JSON.stringify(hostname),
|
||||
);
|
||||
|
||||
// The control for the phishing test below: this origin is not on the
|
||||
// blocklist, so the banner must be absent here. Without it a banner
|
||||
// that was simply always visible would satisfy that test.
|
||||
assert(
|
||||
await popup.locator("#approve-site-phishing-warning").isHidden(),
|
||||
"the phishing warning is showing for an origin that is not on " +
|
||||
"the blocklist, so its appearance proves nothing",
|
||||
);
|
||||
|
||||
// Deliberately not remembered: a remembered rejection lands the
|
||||
// origin in deniedSites and every later test in this section is
|
||||
// auto-rejected with no prompt at all, which would look like a pass.
|
||||
@@ -2606,6 +2616,53 @@ test("eth_requestAccounts approved returns the selected address (#183)", async (
|
||||
);
|
||||
});
|
||||
|
||||
test("a connect request from a blocklisted site is flagged (#219)", async (env) => {
|
||||
// The vendored blocklist, end to end: a real entry from the shipped
|
||||
// artifact, served as a real http(s) origin, reaching the real background
|
||||
// check and the real approval screen. Nothing about the list is stubbed —
|
||||
// there is nothing left to stub, since the extension no longer fetches it.
|
||||
const phishingDapp = await openDapp(env.ctx, PHISHING_DAPP_URL);
|
||||
const hostname = new URL(PHISHING_DAPP_URL).hostname;
|
||||
try {
|
||||
await reserveApprovalTab(env);
|
||||
await startRequest(
|
||||
phishingDapp,
|
||||
"phishing-accounts",
|
||||
"eth_requestAccounts",
|
||||
[],
|
||||
);
|
||||
const popup = await openSiteApprovalPopup(env);
|
||||
try {
|
||||
await visible(popup, "#view-approve-site");
|
||||
|
||||
const shown = await popup.locator("#approve-hostname").innerText();
|
||||
assert(
|
||||
shown === hostname,
|
||||
"the site prompt names the wrong origin: " +
|
||||
JSON.stringify(shown),
|
||||
);
|
||||
|
||||
await visible(popup, "#approve-site-phishing-warning");
|
||||
console.log("# phishing warning shown for " + hostname);
|
||||
|
||||
// Not remembered: a remembered decision for this origin would
|
||||
// outlive the test.
|
||||
await popup.uncheck("#approve-remember");
|
||||
await popup.click("#btn-reject");
|
||||
|
||||
await assertUserRejection(
|
||||
phishingDapp,
|
||||
"phishing-accounts",
|
||||
"the blocklisted site's eth_requestAccounts",
|
||||
);
|
||||
} finally {
|
||||
await closeApprovalPages(env.ctx);
|
||||
}
|
||||
} finally {
|
||||
await phishingDapp.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("personal_sign signs, and the signature recovers to the address (#183)", async (env) => {
|
||||
await startRequest(env.dapp, "sign", "personal_sign", [
|
||||
SIGN_HEX,
|
||||
|
||||
Reference in New Issue
Block a user