fix: strip wildcard prefixes from vendored blocklist entries
All checks were successful
check / check (push) Successful in 13s
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:
@@ -69,8 +69,6 @@
|
|||||||
"web3modal.org"
|
"web3modal.org"
|
||||||
],
|
],
|
||||||
"blacklist": [
|
"blacklist": [
|
||||||
"*.chrom-coinbse-extenson.pages.dev",
|
|
||||||
"*.coinbase-563513.com",
|
|
||||||
"0-0.coinbase-com.info",
|
"0-0.coinbase-com.info",
|
||||||
"0-coinbase.com",
|
"0-coinbase.com",
|
||||||
"0-cz.com",
|
"0-cz.com",
|
||||||
|
|||||||
@@ -33,6 +33,19 @@ let lastFetchTime = 0;
|
|||||||
let fetchPromise = null;
|
let fetchPromise = null;
|
||||||
let persistedDeltaLoaded = false;
|
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.
|
* Binary search on a sorted string array.
|
||||||
*
|
*
|
||||||
@@ -197,7 +210,7 @@ async function updatePhishingList() {
|
|||||||
// Compute blacklist delta: remote items not in vendored baseline
|
// Compute blacklist delta: remote items not in vendored baseline
|
||||||
const newDeltaBl = new Set();
|
const newDeltaBl = new Set();
|
||||||
for (const domain of config.blacklist || []) {
|
for (const domain of config.blacklist || []) {
|
||||||
const d = domain.toLowerCase();
|
const d = normalizeDomain(domain);
|
||||||
if (!binarySearch(vendoredBlacklist, d)) {
|
if (!binarySearch(vendoredBlacklist, d)) {
|
||||||
newDeltaBl.add(d);
|
newDeltaBl.add(d);
|
||||||
}
|
}
|
||||||
@@ -206,7 +219,7 @@ async function updatePhishingList() {
|
|||||||
// Compute whitelist delta: remote items not in vendored whitelist
|
// Compute whitelist delta: remote items not in vendored whitelist
|
||||||
const newDeltaWl = new Set();
|
const newDeltaWl = new Set();
|
||||||
for (const domain of config.whitelist || []) {
|
for (const domain of config.whitelist || []) {
|
||||||
const d = domain.toLowerCase();
|
const d = normalizeDomain(domain);
|
||||||
if (!vendoredWhitelist.has(d)) {
|
if (!vendoredWhitelist.has(d)) {
|
||||||
newDeltaWl.add(d);
|
newDeltaWl.add(d);
|
||||||
}
|
}
|
||||||
@@ -236,12 +249,8 @@ async function updatePhishingList() {
|
|||||||
function loadConfig(config) {
|
function loadConfig(config) {
|
||||||
// For tests: treat the entire config as delta (overlaid on vendored).
|
// For tests: treat the entire config as delta (overlaid on vendored).
|
||||||
// Clear existing delta first.
|
// Clear existing delta first.
|
||||||
deltaBlacklistSet = new Set(
|
deltaBlacklistSet = new Set((config.blacklist || []).map(normalizeDomain));
|
||||||
(config.blacklist || []).map((d) => d.toLowerCase()),
|
deltaWhitelistSet = new Set((config.whitelist || []).map(normalizeDomain));
|
||||||
);
|
|
||||||
deltaWhitelistSet = new Set(
|
|
||||||
(config.whitelist || []).map((d) => d.toLowerCase()),
|
|
||||||
);
|
|
||||||
lastFetchTime = Date.now();
|
lastFetchTime = Date.now();
|
||||||
persistedDeltaLoaded = true;
|
persistedDeltaLoaded = true;
|
||||||
}
|
}
|
||||||
@@ -283,5 +292,6 @@ module.exports = {
|
|||||||
getDeltaSize,
|
getDeltaSize,
|
||||||
hostnameVariants,
|
hostnameVariants,
|
||||||
binarySearch,
|
binarySearch,
|
||||||
|
normalizeDomain,
|
||||||
_reset,
|
_reset,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const {
|
|||||||
getDeltaSize,
|
getDeltaSize,
|
||||||
hostnameVariants,
|
hostnameVariants,
|
||||||
binarySearch,
|
binarySearch,
|
||||||
|
normalizeDomain,
|
||||||
_reset,
|
_reset,
|
||||||
} = require("../src/shared/phishingDomains");
|
} = 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", () => {
|
describe("vendored baseline detection", () => {
|
||||||
// These tests verify that the vendored phishing-domains.json
|
// These tests verify that the vendored phishing-domains.json
|
||||||
// is loaded and searchable without any delta loaded.
|
// is loaded and searchable without any delta loaded.
|
||||||
|
|||||||
Reference in New Issue
Block a user