fix: strip wildcard prefixes from vendored blocklist entries
All checks were successful
check / check (push) Successful in 13s

The MetaMask blocklist contains 2 entries with '*.' wildcard prefixes
(e.g. *.coinbase-563513.com). These were stored literally and never
matched because hostnameVariants() doesn't generate '*.' prefixed
strings. Fix: normalizeDomain() strips the '*.' prefix at load time
and during delta computation. The subdomain matching in
hostnameVariants() already handles child domains correctly.

Found during review.
This commit is contained in:
clawbot 2026-03-01 07:38:01 -08:00
parent 0d06df6cbe
commit 6bafb18ebd
3 changed files with 48 additions and 10 deletions

View File

@ -69,8 +69,6 @@
"web3modal.org"
],
"blacklist": [
"*.chrom-coinbse-extenson.pages.dev",
"*.coinbase-563513.com",
"0-0.coinbase-com.info",
"0-coinbase.com",
"0-cz.com",

View File

@ -33,6 +33,19 @@ let lastFetchTime = 0;
let fetchPromise = null;
let persistedDeltaLoaded = false;
/**
* Normalize a domain entry: lowercase and strip wildcard prefix ("*.").
* Wildcard domains like "*.evil.com" become "evil.com" our subdomain
* matching in hostnameVariants() already covers child domains.
*
* @param {string} domain
* @returns {string}
*/
function normalizeDomain(domain) {
const d = domain.toLowerCase();
return d.startsWith("*.") ? d.slice(2) : d;
}
/**
* Binary search on a sorted string array.
*
@ -197,7 +210,7 @@ async function updatePhishingList() {
// Compute blacklist delta: remote items not in vendored baseline
const newDeltaBl = new Set();
for (const domain of config.blacklist || []) {
const d = domain.toLowerCase();
const d = normalizeDomain(domain);
if (!binarySearch(vendoredBlacklist, d)) {
newDeltaBl.add(d);
}
@ -206,7 +219,7 @@ async function updatePhishingList() {
// Compute whitelist delta: remote items not in vendored whitelist
const newDeltaWl = new Set();
for (const domain of config.whitelist || []) {
const d = domain.toLowerCase();
const d = normalizeDomain(domain);
if (!vendoredWhitelist.has(d)) {
newDeltaWl.add(d);
}
@ -236,12 +249,8 @@ async function updatePhishingList() {
function loadConfig(config) {
// For tests: treat the entire config as delta (overlaid on vendored).
// Clear existing delta first.
deltaBlacklistSet = new Set(
(config.blacklist || []).map((d) => d.toLowerCase()),
);
deltaWhitelistSet = new Set(
(config.whitelist || []).map((d) => d.toLowerCase()),
);
deltaBlacklistSet = new Set((config.blacklist || []).map(normalizeDomain));
deltaWhitelistSet = new Set((config.whitelist || []).map(normalizeDomain));
lastFetchTime = Date.now();
persistedDeltaLoaded = true;
}
@ -283,5 +292,6 @@ module.exports = {
getDeltaSize,
hostnameVariants,
binarySearch,
normalizeDomain,
_reset,
};

View File

@ -5,6 +5,7 @@ const {
getDeltaSize,
hostnameVariants,
binarySearch,
normalizeDomain,
_reset,
} = require("../src/shared/phishingDomains");
@ -67,6 +68,35 @@ describe("phishingDomains", () => {
});
});
describe("normalizeDomain", () => {
test("strips *. wildcard prefix", () => {
expect(normalizeDomain("*.evil.com")).toBe("evil.com");
expect(normalizeDomain("*.sub.evil.com")).toBe("sub.evil.com");
});
test("lowercases domains", () => {
expect(normalizeDomain("Evil.COM")).toBe("evil.com");
expect(normalizeDomain("*.Evil.COM")).toBe("evil.com");
});
test("passes through normal domains unchanged", () => {
expect(normalizeDomain("example.com")).toBe("example.com");
});
});
describe("wildcard domain handling", () => {
test("wildcard blacklist entries match via loadConfig", () => {
loadConfig({
blacklist: ["*.scam-site.com", "normal-scam.com"],
whitelist: [],
});
// *.scam-site.com is normalized to scam-site.com
expect(isPhishingDomain("scam-site.com")).toBe(true);
expect(isPhishingDomain("sub.scam-site.com")).toBe(true);
expect(isPhishingDomain("normal-scam.com")).toBe(true);
});
});
describe("vendored baseline detection", () => {
// These tests verify that the vendored phishing-domains.json
// is loaded and searchable without any delta loaded.