Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
61 changes: 41 additions & 20 deletions src/pages/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
}
Expand Down Expand Up @@ -65,43 +64,69 @@ 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 };
}

const isTrusted = res.type === "allowed";
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 });
Expand All @@ -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) {
Expand All @@ -137,36 +161,34 @@ 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 {
await Browser.tabs.sendMessage(onActivatedInfo.tabId, { message: "TabActivated" });
} 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;
Expand All @@ -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
Expand Down
43 changes: 24 additions & 19 deletions src/pages/libs/phishing-detector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,43 +23,48 @@ export function clearDomainCheckCache() {

const defaultCheckResponse = { result: false, type: "unknown" } as CheckDomainResult;

export async function checkDomain(domain: string): Promise<CheckDomainResult> {
export async function checkDomain(domain: string, enableFuzzyMatch: boolean = true): Promise<CheckDomainResult> {
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;
}
82 changes: 56 additions & 26 deletions src/pages/popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ const Popup = () => {
setSearchResults([]);
return;
}

const results = protocolDirectory
.filter(item => {
const nameMatch = item.name.toLowerCase().includes(searchTerm.toLowerCase());
const urlMatch = item.url.toLowerCase().includes(searchTerm.toLowerCase());
return nameMatch || urlMatch;
})
.slice(0, 5); // Limit to 5 results

setSearchResults(results);
}, 200),
[searchTerm]
Expand All @@ -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",
Expand All @@ -74,53 +76,53 @@ const Popup = () => {
DefiLlama
</Text>
</VStack>

{/* Quick Links */}
<HStack my="3" w="full" justify="space-around" spacing={2}>
<Link href="https://defillama.com/" isExternal>
<VStack>
<Image
src="https://defillama.com/icons/favicon-32x32.png"
alt="DefiLlama"
w="8"
<Image
src="https://defillama.com/icons/favicon-32x32.png"
alt="DefiLlama"
w="8"
h="8"
borderRadius="full"
borderRadius="full"
/>
<Text fontSize="xs">DeFiLlama</Text>
</VStack>
</Link>
<Link href="https://swap.defillama.com/" isExternal>
<VStack>
<Image
src="https://swap.defillama.com/_next/static/media/loader.268d236d.png"
alt="LlamaSwap"
w="8"
<Image
src="https://swap.defillama.com/_next/static/media/loader.268d236d.png"
alt="LlamaSwap"
w="8"
h="8"
borderRadius="full"
borderRadius="full"
/>
<Text fontSize="xs">LlamaSwap</Text>
</VStack>
</Link>
<Link href="https://llamapay.io/dashboard" isExternal>
<VStack>
<Image
src="https://llamapay.io/favicon-32x32.png"
alt="LlamaPay"
w="8"
<Image
src="https://llamapay.io/favicon-32x32.png"
alt="LlamaPay"
w="8"
h="8"
borderRadius="full"
borderRadius="full"
/>
<Text fontSize="xs">LlamaPay</Text>
</VStack>
</Link>
<Link href="https://llamafeed.io/" isExternal>
<VStack>
<Image
src="https://llamafeed.io/_next/image?url=%2Flogo.webp&w=96&q=75"
alt="LlamaFeed"
w="8"
<Image
src="https://llamafeed.io/_next/image?url=%2Flogo.webp&w=96&q=75"
alt="LlamaFeed"
w="8"
h="8"
borderRadius="full"
borderRadius="full"
/>
<Text fontSize="xs">LlamaFeed</Text>
</VStack>
Expand Down Expand Up @@ -151,7 +153,7 @@ const Popup = () => {
bg={useColorModeValue("white", "gray.700")}
/>
</InputGroup>

{/* Search Results */}
{searchResults.length > 0 && (
<Box
Expand Down Expand Up @@ -196,7 +198,7 @@ const Popup = () => {

<VStack my="5" p="2" w="full" spacing="1.5" borderRadius="lg" bg={useColorModeValue("gray.100", "gray.900")}>
<HStack w="full">
<Text fontSize="l" fontWeight="bold">
<Text fontSize="xs" fontWeight="bold">
Twitter
</Text>
</HStack>
Expand Down Expand Up @@ -267,7 +269,7 @@ const Popup = () => {
/>
</HStack>
<HStack w="full">
<Text fontSize="l" fontWeight="bold">
<Text fontSize="xs" fontWeight="bold">
Explorer
</Text>
</HStack>
Expand Down Expand Up @@ -301,6 +303,34 @@ const Popup = () => {
}}
/>
</HStack>
<HStack w="full">
<Text fontSize="xs" fontWeight="bold">
Phishing Protection
</Text>
</HStack>
<HStack justify="space-between" w="full" pl={7}>
<Text fontSize="sm">Enable phishing detection</Text>
<Switch
size="sm"
isChecked={phishingDetector}
onChange={(e) => {
setPhishingDetector(e.target.checked);
if (!e.target.checked) {
Browser.action.setIcon({ path: cuteStatic });
}
}}
/>
</HStack>
<HStack justify="space-between" w="full" pl={7}>
<Text fontSize="sm">Enable fuzzy matching</Text>
<Switch
size="sm"
isChecked={phishingFuzzyMatch}
onChange={(e) => {
setPhishingFuzzyMatch(e.target.checked);
}}
/>
</HStack>
</VStack>
<VStack m="4">
<Link href="https://github.com/DefiLlama/url-directory" isExternal>
Expand Down