diff --git a/package.json b/package.json index 5743e9d..85202ec 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "defillama-extension", "private": true, - "version": "0.0.8.7", + "version": "0.0.8.8", "type": "module", "description": "DefiLlama Extension", "displayName": "DefiLlama", diff --git a/src/pages/background/index.ts b/src/pages/background/index.ts index 52f1415..2927cd6 100644 --- a/src/pages/background/index.ts +++ b/src/pages/background/index.ts @@ -24,8 +24,7 @@ initBackground(); Browser.runtime.onMessage.addListener((message, sender) => { try { if (message?.type === "CHECK_CURRENT_DOMAIN" && sender?.tab) { - handlePhishingCheck('contentScriptRequest', sender.tab).catch(() => { - }); + handlePhishingCheck("contentScriptRequest", sender.tab).catch(() => {}); } } catch (error) { } @@ -65,11 +64,24 @@ async function handleDomainCheck(trigger: string, tab?: Browser.Tabs.Tab) { } const parsed = psl.parse(hostname); - const domain = (parsed && 'domain' in parsed && parsed.domain) || hostname.replace("www.", ""); + const domain = (parsed && "domain" in parsed && parsed.domain) || hostname.replace("www.", ""); + + // Get fuzzy matching setting + const phishingFuzzyMatch = await getStorage("local", "settings:phishingFuzzyMatch", false); + const res = await checkDomain(domain, phishingFuzzyMatch); - const res = await checkDomain(domain); if (res.result) { - const reason = res.type === "blocked" ? "Website is blacklisted" : "Suspicious website detected"; + let reason: string; + switch (res.type) { + case "blocked": + reason = "Website is blacklisted"; + break; + case "fuzzy": + reason = `Website impersonating ${res.extra}`; + break; + default: + reason = "Suspicious website detected"; + } return { isBlocked: true, isTrusted: false, reason, tab }; } @@ -77,31 +89,44 @@ async function handleDomainCheck(trigger: string, tab?: Browser.Tabs.Tab) { const reason = isTrusted ? "Website is whitelisted" : "Unknown website"; return { isBlocked: false, isTrusted, reason, tab }; - } catch (error) { return { isBlocked: false, isTrusted: false, reason: "Error checking domain", tab }; } } async function handlePhishingCheck(trigger: string, tab?: Browser.Tabs.Tab) { + // Check if phishing detection is enabled + const phishingDetector = await getStorage("local", "settings:phishingDetector", true); + if (!phishingDetector) { + // Reset to default icon when phishing detection is disabled + if (!tab) { + tab = await getCurrentTab(); + } + if (tab?.active) { + Browser.action.setIcon({ path: cute }); + Browser.action.setTitle({ title: "DefiLlama" }); + } + return; + } + const domainResult = await handleDomainCheck(trigger, tab); const { isBlocked, isTrusted, reason } = domainResult; tab = domainResult.tab; - + if (isBlocked) { // Always send warning message for blocked sites, regardless of active status if (tab?.id) { try { - await Browser.tabs.sendMessage(tab.id, { + await Browser.tabs.sendMessage(tab.id, { type: "DOMAIN_STATUS", status: "blocked", - reason: reason + reason }); } catch (error) { // Tab might be closed or content script not ready - fail silently } } - + // Only update icon if this is the active tab if (tab?.active) { Browser.action.setIcon({ path: maxPain }); @@ -124,7 +149,6 @@ async function handlePhishingCheck(trigger: string, tab?: Browser.Tabs.Tab) { let lastCheckKey = ""; - Browser.tabs.onUpdated.addListener(async (tabId, onUpdatedInfo, tab) => { try { if (onUpdatedInfo.status === "complete" && tab.active) { @@ -137,20 +161,19 @@ Browser.tabs.onUpdated.addListener(async (tabId, onUpdatedInfo, tab) => { if (onUpdatedInfo.url || onUpdatedInfo.status === "complete") { if (!tab?.active) return; - + const key = `${tab.id}-${tab.url}`; if (lastCheckKey === key) { return; } lastCheckKey = key; - await handlePhishingCheck('tabUpdate', tab); + await handlePhishingCheck("tabUpdate", tab); } } catch (error) { // Silently handle any tab update errors } }); - Browser.tabs.onActivated.addListener(async (onActivatedInfo) => { try { try { @@ -158,15 +181,14 @@ Browser.tabs.onActivated.addListener(async (onActivatedInfo) => { } catch { // Content script might not be ready } - + const tab = await Browser.tabs.get(onActivatedInfo.tabId); - await handlePhishingCheck('tabActivated', tab); + await handlePhishingCheck("tabActivated", tab); } catch (error) { // Silently handle tab activation errors } }); - Browser.windows.onFocusChanged.addListener(async (windowId) => { try { if (windowId === Browser.windows.WINDOW_ID_NONE) return; @@ -177,18 +199,17 @@ Browser.windows.onFocusChanged.addListener(async (windowId) => { } catch { // Content script might not be ready } - await handlePhishingCheck('windowFocused', tab); + await handlePhishingCheck("windowFocused", tab); } } catch (error) { // Silently handle window focus errors } }); - Browser.tabs.onCreated.addListener(async (tab) => { try { if (tab.url && tab.active) { - await handlePhishingCheck('tabCreated', tab); + await handlePhishingCheck("tabCreated", tab); } } catch (error) { // Silently handle tab creation errors diff --git a/src/pages/libs/phishing-detector.ts b/src/pages/libs/phishing-detector.ts index e07d6b6..8d73f93 100644 --- a/src/pages/libs/phishing-detector.ts +++ b/src/pages/libs/phishing-detector.ts @@ -23,43 +23,48 @@ export function clearDomainCheckCache() { const defaultCheckResponse = { result: false, type: "unknown" } as CheckDomainResult; -export async function checkDomain(domain: string): Promise { +export async function checkDomain(domain: string, enableFuzzyMatch: boolean = true): Promise { if (!domain) return defaultCheckResponse; clearDomainCheckCache(); - if (!domainCheckCache.has(domain)) { - domainCheckCache.set(domain, _checkDomain(domain)); + const cacheKey = `${domain}-${enableFuzzyMatch}`; + if (!domainCheckCache.has(cacheKey)) { + domainCheckCache.set(cacheKey, _checkDomain(domain, enableFuzzyMatch)); } - return domainCheckCache.get(domain) || defaultCheckResponse; + return domainCheckCache.get(cacheKey) || defaultCheckResponse; } - -function _checkDomain(domain: string): CheckDomainResult { +function _checkDomain(domain: string, enableFuzzyMatch: boolean): CheckDomainResult { const parsed = psl.parse(domain); - if (!parsed || !('domain' in parsed) || !parsed.domain) { + if (!parsed || !("domain" in parsed) || !parsed.domain) { const fallbackDomain = domain.split(".").slice(-2).join("."); - return checkDomainInLists(domain, fallbackDomain); + return checkDomainInLists(domain, fallbackDomain, enableFuzzyMatch); } const rootDomain = parsed.domain; - return checkDomainInLists(domain, rootDomain); + return checkDomainInLists(domain, rootDomain, enableFuzzyMatch); } -function checkDomainInLists(fullDomain: string, rootDomain: string): CheckDomainResult { +function checkDomainInLists(fullDomain: string, rootDomain: string, enableFuzzyMatch: boolean): CheckDomainResult { const isAllowed = allowedDomainsDb.data.has(fullDomain) || allowedDomainsDb.data.has(rootDomain); if (isAllowed) return { result: false, type: "allowed" }; const isBlocked = blockedDomainsDb.data.has(fullDomain) || blockedDomainsDb.data.has(rootDomain); if (isBlocked) return { result: true, type: "blocked" }; - let fuzzyResult: CheckDomainResult | undefined; - for (const fuzzyDomain of fuzzyDomainsDb.data) { - const fullDistance = levenshtein.get(fuzzyDomain, fullDomain); - const rootDistance = levenshtein.get(fuzzyDomain, rootDomain); - const minDistance = Math.min(fullDistance, rootDistance); - if (minDistance <= DEFAULT_LEVENSHTEIN_TOLERANCE) { - fuzzyResult = { result: false, type: "unknown", extra: fuzzyDomain }; - break; // Found a match, exit early + // Only check fuzzy matching if enabled + if (enableFuzzyMatch) { + let fuzzyResult: CheckDomainResult | undefined; + for (const fuzzyDomain of fuzzyDomainsDb.data) { + const fullDistance = levenshtein.get(fuzzyDomain, fullDomain); + const rootDistance = levenshtein.get(fuzzyDomain, rootDomain); + const minDistance = Math.min(fullDistance, rootDistance); + if (minDistance <= DEFAULT_LEVENSHTEIN_TOLERANCE) { + fuzzyResult = { result: true, type: "fuzzy", extra: fuzzyDomain }; + break; // Found a match, exit early + } } + if (fuzzyResult) return fuzzyResult; } - return fuzzyResult ?? defaultCheckResponse; + + return defaultCheckResponse; } diff --git a/src/pages/popup/Popup.tsx b/src/pages/popup/Popup.tsx index 4054bde..d3cfb02 100644 --- a/src/pages/popup/Popup.tsx +++ b/src/pages/popup/Popup.tsx @@ -36,7 +36,7 @@ const Popup = () => { setSearchResults([]); return; } - + const results = protocolDirectory .filter(item => { const nameMatch = item.name.toLowerCase().includes(searchTerm.toLowerCase()); @@ -44,7 +44,7 @@ const Popup = () => { return nameMatch || urlMatch; }) .slice(0, 5); // Limit to 5 results - + setSearchResults(results); }, 200), [searchTerm] @@ -54,6 +54,8 @@ const Popup = () => { const [tagsInjector, setTagsInjector] = useBrowserStorage("local", "settings:tagsInjector", true); const [explorerSpamHide, setExplorerSpamHide] = useBrowserStorage("local", "settings:explorerSpamHide", false); + const [phishingDetector, setPhishingDetector] = useBrowserStorage("local", "settings:phishingDetector", true); + const [phishingFuzzyMatch, setPhishingFuzzyMatch] = useBrowserStorage("local", "settings:phishingFuzzyMatch", false); const [phishingHandleDetector, setPhishingHandleDetector] = useBrowserStorage( "local", "settings:phishingHandleDetector", @@ -74,53 +76,53 @@ const Popup = () => { DefiLlama - + {/* Quick Links */} - DefiLlama DeFiLlama - LlamaSwap LlamaSwap - LlamaPay LlamaPay - LlamaFeed LlamaFeed @@ -151,7 +153,7 @@ const Popup = () => { bg={useColorModeValue("white", "gray.700")} /> - + {/* Search Results */} {searchResults.length > 0 && ( { - + Twitter @@ -267,7 +269,7 @@ const Popup = () => { /> - + Explorer @@ -301,6 +303,34 @@ const Popup = () => { }} /> + + + Phishing Protection + + + + Enable phishing detection + { + setPhishingDetector(e.target.checked); + if (!e.target.checked) { + Browser.action.setIcon({ path: cuteStatic }); + } + }} + /> + + + Enable fuzzy matching + { + setPhishingFuzzyMatch(e.target.checked); + }} + /> +