@metamask-previews/phishing-controller 17.4.0-preview-8c2a2eeb2 → 17.4.0-preview-b13d916

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Changed
11
11
 
12
+ - Optimize C2 domain blocklist lookups by switching internal storage from `Array` to `Set`, reducing per-lookup complexity from O(n) to O(1) ([#6388](https://github.com/MetaMask/core/pull/6388))
12
13
  - Bump `@metamask/transaction-controller` from `^69.5.2` to `^69.6.1` ([#9960](https://github.com/MetaMask/core/pull/9960), [#9969](https://github.com/MetaMask/core/pull/9969))
13
14
 
14
15
  ## [17.4.0]
@@ -259,6 +260,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
259
260
 
260
261
  - Bump `@metamask/base-controller` from `^8.0.1` to `^8.4.0` ([#6284](https://github.com/MetaMask/core/pull/6284), [#6355](https://github.com/MetaMask/core/pull/6355), [#6465](https://github.com/MetaMask/core/pull/6465), [#6632](https://github.com/MetaMask/core/pull/6632))
261
262
  - Bump `@metamask/controller-utils` from `^11.11.0` to `^11.14.0` ([#6303](https://github.com/MetaMask/core/pull/6303), [#6620](https://github.com/MetaMask/core/pull/6620), [#6629](https://github.com/MetaMask/core/pull/6629))
263
+
262
264
  - Bump `@noble/hashes` from `^1.4.0` to `^1.8.0` ([#6101](https://github.com/MetaMask/core/pull/6101))
263
265
 
264
266
  ## [13.1.0]
@@ -34,18 +34,24 @@ class PhishingDetector {
34
34
  _PhishingDetector_legacyConfig.set(this, void 0);
35
35
  // recommended configuration
36
36
  if (Array.isArray(opts)) {
37
- __classPrivateFieldSet(this, _PhishingDetector_configs, (0, utils_js_1.processConfigs)(opts), "f");
37
+ __classPrivateFieldSet(this, _PhishingDetector_configs, (0, utils_js_1.processConfigs)(opts).map((config) => ({
38
+ ...config,
39
+ c2DomainBlocklist: new Set(config.c2DomainBlocklist),
40
+ })), "f");
38
41
  __classPrivateFieldSet(this, _PhishingDetector_legacyConfig, false, "f");
39
42
  // legacy configuration
40
43
  }
41
44
  else {
42
45
  __classPrivateFieldSet(this, _PhishingDetector_configs, [
43
- (0, utils_js_1.getDefaultPhishingDetectorConfig)({
44
- allowlist: opts.whitelist,
45
- blocklist: opts.blacklist,
46
- fuzzylist: opts.fuzzylist,
47
- tolerance: opts.tolerance,
48
- }),
46
+ {
47
+ ...(0, utils_js_1.getDefaultPhishingDetectorConfig)({
48
+ allowlist: opts.whitelist,
49
+ blocklist: opts.blacklist,
50
+ fuzzylist: opts.fuzzylist,
51
+ tolerance: opts.tolerance,
52
+ }),
53
+ c2DomainBlocklist: new Set(),
54
+ },
49
55
  ], "f");
50
56
  __classPrivateFieldSet(this, _PhishingDetector_legacyConfig, true, "f");
51
57
  }
@@ -128,10 +134,10 @@ class PhishingDetector {
128
134
  const hostnameHash = (0, utils_js_1.sha256Hash)(hostname.toLowerCase());
129
135
  const domainsToCheck = (0, utils_js_1.generateParentDomains)(sourceParts.reverse(), 5);
130
136
  for (const { c2DomainBlocklist, name, version } of __classPrivateFieldGet(this, _PhishingDetector_configs, "f")) {
131
- if (!c2DomainBlocklist || c2DomainBlocklist.length === 0) {
137
+ if (!c2DomainBlocklist || c2DomainBlocklist.size === 0) {
132
138
  continue;
133
139
  }
134
- if (c2DomainBlocklist.includes(hostnameHash)) {
140
+ if (c2DomainBlocklist.has(hostnameHash)) {
135
141
  return {
136
142
  name,
137
143
  result: true,
@@ -141,7 +147,7 @@ class PhishingDetector {
141
147
  }
142
148
  for (const domain of domainsToCheck) {
143
149
  const domainHash = (0, utils_js_1.sha256Hash)(domain);
144
- if (c2DomainBlocklist.includes(domainHash)) {
150
+ if (c2DomainBlocklist.has(domainHash)) {
145
151
  return {
146
152
  name,
147
153
  result: true,
@@ -1 +1 @@
1
- {"version":3,"file":"PhishingDetector.cjs","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"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","sourcesContent":["import { distance } from 'fastest-levenshtein';\n\nimport { matchedPathPrefix } from './PathTrie.js';\nimport type { PathTrie } from './PathTrie.js';\nimport { PhishingDetectorResultType } from './types.js';\nimport type { PhishingDetectorResult } from './types.js';\nimport {\n domainPartsToDomain,\n domainPartsToFuzzyForm,\n domainToParts,\n generateParentDomains,\n getDefaultPhishingDetectorConfig,\n getHostnameFromUrl,\n matchPartsAgainstList,\n processConfigs,\n sha256Hash,\n} from './utils.js';\n\nexport type LegacyPhishingDetectorList = {\n whitelist?: string[];\n blacklist?: string[];\n c2DomainBlocklist?: string[];\n} & FuzzyTolerance;\n\nexport type PhishingDetectorList = {\n allowlist?: string[];\n blocklist?: string[];\n blocklistPaths?: PathTrie;\n c2DomainBlocklist?: string[];\n name?: string;\n version?: string | number;\n tolerance?: number;\n} & FuzzyTolerance;\n\nexport type FuzzyTolerance =\n | {\n tolerance?: number;\n fuzzylist: string[];\n }\n | {\n tolerance?: never;\n fuzzylist?: never;\n };\n\nexport type PhishingDetectorOptions =\n | LegacyPhishingDetectorList\n | PhishingDetectorList[];\n\nexport type PhishingDetectorConfiguration = {\n name?: string;\n version?: number | string;\n allowlist: string[][];\n blocklist: string[][];\n blocklistPaths?: PathTrie;\n c2DomainBlocklist?: string[];\n fuzzylist: string[][];\n tolerance: number;\n};\n\nexport class PhishingDetector {\n readonly #configs: PhishingDetectorConfiguration[];\n\n readonly #legacyConfig: boolean;\n\n /**\n * Construct a phishing detector, which can check whether origins are known\n * to be malicious or similar to common phishing targets.\n *\n * A list of configurations is accepted. Each origin checked is processed\n * using each configuration in sequence, so the order defines which\n * configurations take precedence.\n *\n * @param opts - Phishing detection options\n */\n constructor(opts: PhishingDetectorOptions) {\n // recommended configuration\n if (Array.isArray(opts)) {\n this.#configs = processConfigs(opts);\n this.#legacyConfig = false;\n // legacy configuration\n } else {\n this.#configs = [\n getDefaultPhishingDetectorConfig({\n allowlist: opts.whitelist,\n blocklist: opts.blacklist,\n fuzzylist: opts.fuzzylist,\n tolerance: opts.tolerance,\n }),\n ];\n this.#legacyConfig = true;\n }\n }\n\n /**\n * Check if a url is known to be malicious or similar to a common phishing\n * target. This will check the hostname and IPFS CID that is sometimes\n * located in the path.\n *\n * @param url - The url to check.\n * @returns The result of the check.\n */\n check(url: string): PhishingDetectorResult {\n const result = this.#check(url);\n\n if (this.#legacyConfig) {\n let legacyType = result.type;\n if (legacyType === PhishingDetectorResultType.Allowlist) {\n legacyType = PhishingDetectorResultType.Whitelist;\n } else if (legacyType === PhishingDetectorResultType.Blocklist) {\n legacyType = PhishingDetectorResultType.Blacklist;\n }\n return {\n match: result.match,\n result: result.result,\n type: legacyType,\n };\n }\n return result;\n }\n\n #check(url: string): PhishingDetectorResult {\n const ipfsCidMatch = url.match(ipfsCidRegex());\n\n // Check for IPFS CID related blocklist entries\n if (ipfsCidMatch !== null) {\n // there is a cID string somewhere\n // Determine if any of the entries are ipfs cids\n // Depending on the gateway, the CID is in the path OR a subdomain, so we do a regex match on it all\n const cID = ipfsCidMatch[0];\n for (const { blocklist, name, version } of this.#configs) {\n const blocklistMatch = blocklist\n .filter((entries) => entries.length === 1)\n .find((entries) => {\n return entries[0] === cID;\n });\n if (blocklistMatch) {\n return {\n name,\n match: cID,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n\n let domain;\n try {\n domain = new URL(url).hostname;\n } catch {\n return {\n result: false,\n type: PhishingDetectorResultType.All,\n };\n }\n\n const fqdn = domain.endsWith('.') ? domain.slice(0, -1) : domain;\n\n const source = domainToParts(fqdn);\n\n for (const { blocklistPaths, name, version } of this.#configs) {\n if (!blocklistPaths || Object.keys(blocklistPaths).length === 0) {\n continue;\n }\n const pathMatch = matchedPathPrefix(url, blocklistPaths);\n if (pathMatch) {\n return {\n match: pathMatch,\n name,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n for (const { allowlist, name, version } of this.#configs) {\n // if source matches allowlist hostname (or subdomain thereof), PASS\n const allowlistMatch = matchPartsAgainstList(source, allowlist);\n if (allowlistMatch) {\n const match = domainPartsToDomain(allowlistMatch);\n return {\n match,\n name,\n result: false,\n type: PhishingDetectorResultType.Allowlist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n for (const {\n blocklist,\n fuzzylist,\n name,\n tolerance,\n version,\n } of this.#configs) {\n // if source matches blocklist hostname (or subdomain thereof), FAIL\n const blocklistMatch = matchPartsAgainstList(source, blocklist);\n if (blocklistMatch) {\n const match = domainPartsToDomain(blocklistMatch);\n return {\n match,\n name,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n\n if (tolerance > 0) {\n // check if near-match of whitelist domain, FAIL\n let fuzzyForm = domainPartsToFuzzyForm(source);\n // strip www\n fuzzyForm = fuzzyForm.replace(/^www\\./u, '');\n // check against fuzzylist\n const levenshteinMatched = fuzzylist.find((targetParts) => {\n const fuzzyTarget = domainPartsToFuzzyForm(targetParts);\n const dist = distance(fuzzyForm, fuzzyTarget);\n return dist <= tolerance;\n });\n if (levenshteinMatched) {\n const match = domainPartsToDomain(levenshteinMatched);\n return {\n name,\n match,\n result: true,\n type: PhishingDetectorResultType.Fuzzy,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n\n // matched nothing, PASS\n return { result: false, type: PhishingDetectorResultType.All };\n }\n\n /**\n * Gets the specific terminal path from blocklistPaths that is blocking a URL.\n *\n * @param url - The URL to check.\n * @returns The terminal path that is blocking the URL, or null if not blocked.\n */\n blockingPath(url: string): string | null {\n for (const { blocklistPaths } of this.#configs) {\n if (!blocklistPaths || Object.keys(blocklistPaths).length === 0) {\n continue;\n }\n const matchedPath = matchedPathPrefix(url, blocklistPaths);\n if (matchedPath) {\n return matchedPath;\n }\n }\n\n return null;\n }\n\n /**\n * Checks if a URL is blocked against the hashed request blocklist.\n * This is done by hashing the URL's hostname and checking it against the hashed request blocklist.\n *\n * @param urlString - The URL to check.\n * @returns An object indicating if the URL is blocked and relevant metadata.\n */\n isMaliciousC2Domain(urlString: string): PhishingDetectorResult {\n const hostname = getHostnameFromUrl(urlString);\n if (!hostname) {\n return {\n result: false,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n };\n }\n\n const fqdn = hostname.endsWith('.') ? hostname.slice(0, -1) : hostname;\n const sourceParts = domainToParts(fqdn);\n\n for (const { allowlist, name, version } of this.#configs) {\n // if source matches allowlist hostname (or subdomain thereof), PASS\n const allowlistMatch = matchPartsAgainstList(sourceParts, allowlist);\n if (allowlistMatch) {\n const match = domainPartsToDomain(allowlistMatch);\n return {\n match,\n name,\n result: false,\n type: PhishingDetectorResultType.Allowlist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n const hostnameHash = sha256Hash(hostname.toLowerCase());\n const domainsToCheck = generateParentDomains(sourceParts.reverse(), 5);\n\n for (const { c2DomainBlocklist, name, version } of this.#configs) {\n if (!c2DomainBlocklist || c2DomainBlocklist.length === 0) {\n continue;\n }\n\n if (c2DomainBlocklist.includes(hostnameHash)) {\n return {\n name,\n result: true,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n version: version === undefined ? version : String(version),\n };\n }\n\n for (const domain of domainsToCheck) {\n const domainHash = sha256Hash(domain);\n if (c2DomainBlocklist.includes(domainHash)) {\n return {\n name,\n result: true,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n // did not match, PASS\n return {\n result: false,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n };\n }\n}\n\n/**\n * Runs a regex match to determine if a string is a IPFS CID\n *\n * @returns Regex string for IPFS CID\n */\nfunction ipfsCidRegex() {\n // regex from https://stackoverflow.com/a/67176726\n const reg =\n 'Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}';\n return new RegExp(reg, 'u');\n}\n"]}
1
+ {"version":3,"file":"PhishingDetector.cjs","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"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","sourcesContent":["import { distance } from 'fastest-levenshtein';\n\nimport { matchedPathPrefix } from './PathTrie.js';\nimport type { PathTrie } from './PathTrie.js';\nimport { PhishingDetectorResultType } from './types.js';\nimport type { PhishingDetectorResult } from './types.js';\nimport {\n domainPartsToDomain,\n domainPartsToFuzzyForm,\n domainToParts,\n generateParentDomains,\n getDefaultPhishingDetectorConfig,\n getHostnameFromUrl,\n matchPartsAgainstList,\n processConfigs,\n sha256Hash,\n} from './utils.js';\n\nexport type LegacyPhishingDetectorList = {\n whitelist?: string[];\n blacklist?: string[];\n c2DomainBlocklist?: string[];\n} & FuzzyTolerance;\n\nexport type PhishingDetectorList = {\n allowlist?: string[];\n blocklist?: string[];\n blocklistPaths?: PathTrie;\n c2DomainBlocklist?: string[];\n name?: string;\n version?: string | number;\n tolerance?: number;\n} & FuzzyTolerance;\n\nexport type FuzzyTolerance =\n | {\n tolerance?: number;\n fuzzylist: string[];\n }\n | {\n tolerance?: never;\n fuzzylist?: never;\n };\n\nexport type PhishingDetectorOptions =\n | LegacyPhishingDetectorList\n | PhishingDetectorList[];\n\nexport type PhishingDetectorConfiguration = {\n name?: string;\n version?: number | string;\n allowlist: string[][];\n blocklist: string[][];\n c2DomainBlocklist?: string[];\n blocklistPaths?: PathTrie;\n fuzzylist: string[][];\n tolerance: number;\n};\n\ntype InternalPhishingDetectorConfiguration = Omit<\n PhishingDetectorConfiguration,\n 'c2DomainBlocklist'\n> & {\n c2DomainBlocklist?: Set<string>;\n};\n\nexport class PhishingDetector {\n readonly #configs: InternalPhishingDetectorConfiguration[];\n\n readonly #legacyConfig: boolean;\n\n /**\n * Construct a phishing detector, which can check whether origins are known\n * to be malicious or similar to common phishing targets.\n *\n * A list of configurations is accepted. Each origin checked is processed\n * using each configuration in sequence, so the order defines which\n * configurations take precedence.\n *\n * @param opts - Phishing detection options\n */\n constructor(opts: PhishingDetectorOptions) {\n // recommended configuration\n if (Array.isArray(opts)) {\n this.#configs = processConfigs(opts).map((config) => ({\n ...config,\n c2DomainBlocklist: new Set<string>(config.c2DomainBlocklist),\n }));\n this.#legacyConfig = false;\n // legacy configuration\n } else {\n this.#configs = [\n {\n ...getDefaultPhishingDetectorConfig({\n allowlist: opts.whitelist,\n blocklist: opts.blacklist,\n fuzzylist: opts.fuzzylist,\n tolerance: opts.tolerance,\n }),\n c2DomainBlocklist: new Set<string>(),\n },\n ];\n this.#legacyConfig = true;\n }\n }\n\n /**\n * Check if a url is known to be malicious or similar to a common phishing\n * target. This will check the hostname and IPFS CID that is sometimes\n * located in the path.\n *\n * @param url - The url to check.\n * @returns The result of the check.\n */\n check(url: string): PhishingDetectorResult {\n const result = this.#check(url);\n\n if (this.#legacyConfig) {\n let legacyType = result.type;\n if (legacyType === PhishingDetectorResultType.Allowlist) {\n legacyType = PhishingDetectorResultType.Whitelist;\n } else if (legacyType === PhishingDetectorResultType.Blocklist) {\n legacyType = PhishingDetectorResultType.Blacklist;\n }\n return {\n match: result.match,\n result: result.result,\n type: legacyType,\n };\n }\n return result;\n }\n\n #check(url: string): PhishingDetectorResult {\n const ipfsCidMatch = url.match(ipfsCidRegex());\n\n // Check for IPFS CID related blocklist entries\n if (ipfsCidMatch !== null) {\n // there is a cID string somewhere\n // Determine if any of the entries are ipfs cids\n // Depending on the gateway, the CID is in the path OR a subdomain, so we do a regex match on it all\n const cID = ipfsCidMatch[0];\n for (const { blocklist, name, version } of this.#configs) {\n const blocklistMatch = blocklist\n .filter((entries) => entries.length === 1)\n .find((entries) => {\n return entries[0] === cID;\n });\n if (blocklistMatch) {\n return {\n name,\n match: cID,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n\n let domain;\n try {\n domain = new URL(url).hostname;\n } catch {\n return {\n result: false,\n type: PhishingDetectorResultType.All,\n };\n }\n\n const fqdn = domain.endsWith('.') ? domain.slice(0, -1) : domain;\n\n const source = domainToParts(fqdn);\n\n for (const { blocklistPaths, name, version } of this.#configs) {\n if (!blocklistPaths || Object.keys(blocklistPaths).length === 0) {\n continue;\n }\n const pathMatch = matchedPathPrefix(url, blocklistPaths);\n if (pathMatch) {\n return {\n match: pathMatch,\n name,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n for (const { allowlist, name, version } of this.#configs) {\n // if source matches allowlist hostname (or subdomain thereof), PASS\n const allowlistMatch = matchPartsAgainstList(source, allowlist);\n if (allowlistMatch) {\n const match = domainPartsToDomain(allowlistMatch);\n return {\n match,\n name,\n result: false,\n type: PhishingDetectorResultType.Allowlist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n for (const {\n blocklist,\n fuzzylist,\n name,\n tolerance,\n version,\n } of this.#configs) {\n // if source matches blocklist hostname (or subdomain thereof), FAIL\n const blocklistMatch = matchPartsAgainstList(source, blocklist);\n if (blocklistMatch) {\n const match = domainPartsToDomain(blocklistMatch);\n return {\n match,\n name,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n\n if (tolerance > 0) {\n // check if near-match of whitelist domain, FAIL\n let fuzzyForm = domainPartsToFuzzyForm(source);\n // strip www\n fuzzyForm = fuzzyForm.replace(/^www\\./u, '');\n // check against fuzzylist\n const levenshteinMatched = fuzzylist.find((targetParts) => {\n const fuzzyTarget = domainPartsToFuzzyForm(targetParts);\n const dist = distance(fuzzyForm, fuzzyTarget);\n return dist <= tolerance;\n });\n if (levenshteinMatched) {\n const match = domainPartsToDomain(levenshteinMatched);\n return {\n name,\n match,\n result: true,\n type: PhishingDetectorResultType.Fuzzy,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n\n // matched nothing, PASS\n return { result: false, type: PhishingDetectorResultType.All };\n }\n\n /**\n * Gets the specific terminal path from blocklistPaths that is blocking a URL.\n *\n * @param url - The URL to check.\n * @returns The terminal path that is blocking the URL, or null if not blocked.\n */\n blockingPath(url: string): string | null {\n for (const { blocklistPaths } of this.#configs) {\n if (!blocklistPaths || Object.keys(blocklistPaths).length === 0) {\n continue;\n }\n const matchedPath = matchedPathPrefix(url, blocklistPaths);\n if (matchedPath) {\n return matchedPath;\n }\n }\n\n return null;\n }\n\n /**\n * Checks if a URL is blocked against the hashed request blocklist.\n * This is done by hashing the URL's hostname and checking it against the hashed request blocklist.\n *\n * @param urlString - The URL to check.\n * @returns An object indicating if the URL is blocked and relevant metadata.\n */\n isMaliciousC2Domain(urlString: string): PhishingDetectorResult {\n const hostname = getHostnameFromUrl(urlString);\n if (!hostname) {\n return {\n result: false,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n };\n }\n\n const fqdn = hostname.endsWith('.') ? hostname.slice(0, -1) : hostname;\n const sourceParts = domainToParts(fqdn);\n\n for (const { allowlist, name, version } of this.#configs) {\n // if source matches allowlist hostname (or subdomain thereof), PASS\n const allowlistMatch = matchPartsAgainstList(sourceParts, allowlist);\n if (allowlistMatch) {\n const match = domainPartsToDomain(allowlistMatch);\n return {\n match,\n name,\n result: false,\n type: PhishingDetectorResultType.Allowlist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n const hostnameHash = sha256Hash(hostname.toLowerCase());\n const domainsToCheck = generateParentDomains(sourceParts.reverse(), 5);\n\n for (const { c2DomainBlocklist, name, version } of this.#configs) {\n if (!c2DomainBlocklist || c2DomainBlocklist.size === 0) {\n continue;\n }\n\n if (c2DomainBlocklist.has(hostnameHash)) {\n return {\n name,\n result: true,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n version: version === undefined ? version : String(version),\n };\n }\n\n for (const domain of domainsToCheck) {\n const domainHash = sha256Hash(domain);\n if (c2DomainBlocklist.has(domainHash)) {\n return {\n name,\n result: true,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n // did not match, PASS\n return {\n result: false,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n };\n }\n}\n\n/**\n * Runs a regex match to determine if a string is a IPFS CID\n *\n * @returns Regex string for IPFS CID\n */\nfunction ipfsCidRegex() {\n // regex from https://stackoverflow.com/a/67176726\n const reg =\n 'Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}';\n return new RegExp(reg, 'u');\n}\n"]}
@@ -27,8 +27,8 @@ export type PhishingDetectorConfiguration = {
27
27
  version?: number | string;
28
28
  allowlist: string[][];
29
29
  blocklist: string[][];
30
- blocklistPaths?: PathTrie;
31
30
  c2DomainBlocklist?: string[];
31
+ blocklistPaths?: PathTrie;
32
32
  fuzzylist: string[][];
33
33
  tolerance: number;
34
34
  };
@@ -1 +1 @@
1
- {"version":3,"file":"PhishingDetector.d.cts","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,uBAAsB;AAE9C,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAmB;AAazD,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B,GAAG,cAAc,CAAC;AAEnB,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,cAAc,CAAC;AAEnB,MAAM,MAAM,cAAc,GACtB;IACE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,GACD;IACE,SAAS,CAAC,EAAE,KAAK,CAAC;IAClB,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB,CAAC;AAEN,MAAM,MAAM,uBAAuB,GAC/B,0BAA0B,GAC1B,oBAAoB,EAAE,CAAC;AAE3B,MAAM,MAAM,6BAA6B,GAAG;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,qBAAa,gBAAgB;;IAK3B;;;;;;;;;OASG;gBACS,IAAI,EAAE,uBAAuB;IAmBzC;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB;IA2I1C;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAcxC;;;;;;OAMG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,sBAAsB;CA8D/D"}
1
+ {"version":3,"file":"PhishingDetector.d.cts","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,uBAAsB;AAE9C,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAmB;AAazD,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B,GAAG,cAAc,CAAC;AAEnB,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,cAAc,CAAC;AAEnB,MAAM,MAAM,cAAc,GACtB;IACE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,GACD;IACE,SAAS,CAAC,EAAE,KAAK,CAAC;IAClB,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB,CAAC;AAEN,MAAM,MAAM,uBAAuB,GAC/B,0BAA0B,GAC1B,oBAAoB,EAAE,CAAC;AAE3B,MAAM,MAAM,6BAA6B,GAAG;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AASF,qBAAa,gBAAgB;;IAK3B;;;;;;;;;OASG;gBACS,IAAI,EAAE,uBAAuB;IAyBzC;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB;IA2I1C;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAcxC;;;;;;OAMG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,sBAAsB;CA8D/D"}
@@ -27,8 +27,8 @@ export type PhishingDetectorConfiguration = {
27
27
  version?: number | string;
28
28
  allowlist: string[][];
29
29
  blocklist: string[][];
30
- blocklistPaths?: PathTrie;
31
30
  c2DomainBlocklist?: string[];
31
+ blocklistPaths?: PathTrie;
32
32
  fuzzylist: string[][];
33
33
  tolerance: number;
34
34
  };
@@ -1 +1 @@
1
- {"version":3,"file":"PhishingDetector.d.mts","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,uBAAsB;AAE9C,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAmB;AAazD,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B,GAAG,cAAc,CAAC;AAEnB,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,cAAc,CAAC;AAEnB,MAAM,MAAM,cAAc,GACtB;IACE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,GACD;IACE,SAAS,CAAC,EAAE,KAAK,CAAC;IAClB,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB,CAAC;AAEN,MAAM,MAAM,uBAAuB,GAC/B,0BAA0B,GAC1B,oBAAoB,EAAE,CAAC;AAE3B,MAAM,MAAM,6BAA6B,GAAG;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,qBAAa,gBAAgB;;IAK3B;;;;;;;;;OASG;gBACS,IAAI,EAAE,uBAAuB;IAmBzC;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB;IA2I1C;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAcxC;;;;;;OAMG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,sBAAsB;CA8D/D"}
1
+ {"version":3,"file":"PhishingDetector.d.mts","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,uBAAsB;AAE9C,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAmB;AAazD,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B,GAAG,cAAc,CAAC;AAEnB,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,cAAc,CAAC;AAEnB,MAAM,MAAM,cAAc,GACtB;IACE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB,GACD;IACE,SAAS,CAAC,EAAE,KAAK,CAAC;IAClB,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB,CAAC;AAEN,MAAM,MAAM,uBAAuB,GAC/B,0BAA0B,GAC1B,oBAAoB,EAAE,CAAC;AAE3B,MAAM,MAAM,6BAA6B,GAAG;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,cAAc,CAAC,EAAE,QAAQ,CAAC;IAC1B,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AASF,qBAAa,gBAAgB;;IAK3B;;;;;;;;;OASG;gBACS,IAAI,EAAE,uBAAuB;IAyBzC;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB;IA2I1C;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAcxC;;;;;;OAMG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,sBAAsB;CA8D/D"}
@@ -31,18 +31,24 @@ export class PhishingDetector {
31
31
  _PhishingDetector_legacyConfig.set(this, void 0);
32
32
  // recommended configuration
33
33
  if (Array.isArray(opts)) {
34
- __classPrivateFieldSet(this, _PhishingDetector_configs, processConfigs(opts), "f");
34
+ __classPrivateFieldSet(this, _PhishingDetector_configs, processConfigs(opts).map((config) => ({
35
+ ...config,
36
+ c2DomainBlocklist: new Set(config.c2DomainBlocklist),
37
+ })), "f");
35
38
  __classPrivateFieldSet(this, _PhishingDetector_legacyConfig, false, "f");
36
39
  // legacy configuration
37
40
  }
38
41
  else {
39
42
  __classPrivateFieldSet(this, _PhishingDetector_configs, [
40
- getDefaultPhishingDetectorConfig({
41
- allowlist: opts.whitelist,
42
- blocklist: opts.blacklist,
43
- fuzzylist: opts.fuzzylist,
44
- tolerance: opts.tolerance,
45
- }),
43
+ {
44
+ ...getDefaultPhishingDetectorConfig({
45
+ allowlist: opts.whitelist,
46
+ blocklist: opts.blacklist,
47
+ fuzzylist: opts.fuzzylist,
48
+ tolerance: opts.tolerance,
49
+ }),
50
+ c2DomainBlocklist: new Set(),
51
+ },
46
52
  ], "f");
47
53
  __classPrivateFieldSet(this, _PhishingDetector_legacyConfig, true, "f");
48
54
  }
@@ -125,10 +131,10 @@ export class PhishingDetector {
125
131
  const hostnameHash = sha256Hash(hostname.toLowerCase());
126
132
  const domainsToCheck = generateParentDomains(sourceParts.reverse(), 5);
127
133
  for (const { c2DomainBlocklist, name, version } of __classPrivateFieldGet(this, _PhishingDetector_configs, "f")) {
128
- if (!c2DomainBlocklist || c2DomainBlocklist.length === 0) {
134
+ if (!c2DomainBlocklist || c2DomainBlocklist.size === 0) {
129
135
  continue;
130
136
  }
131
- if (c2DomainBlocklist.includes(hostnameHash)) {
137
+ if (c2DomainBlocklist.has(hostnameHash)) {
132
138
  return {
133
139
  name,
134
140
  result: true,
@@ -138,7 +144,7 @@ export class PhishingDetector {
138
144
  }
139
145
  for (const domain of domainsToCheck) {
140
146
  const domainHash = sha256Hash(domain);
141
- if (c2DomainBlocklist.includes(domainHash)) {
147
+ if (c2DomainBlocklist.has(domainHash)) {
142
148
  return {
143
149
  name,
144
150
  result: true,
@@ -1 +1 @@
1
- {"version":3,"file":"PhishingDetector.mjs","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,4BAA4B;AAE/C,OAAO,EAAE,iBAAiB,EAAE,uBAAsB;AAElD,OAAO,EAAE,0BAA0B,EAAE,oBAAmB;AAExD,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,aAAa,EACb,qBAAqB,EACrB,gCAAgC,EAChC,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,UAAU,EACX,oBAAmB;AA2CpB,MAAM,OAAO,gBAAgB;IAK3B;;;;;;;;;OASG;IACH,YAAY,IAA6B;;QAdhC,4CAA0C;QAE1C,iDAAuB;QAa9B,4BAA4B;QAC5B,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,uBAAA,IAAI,6BAAY,cAAc,CAAC,IAAI,CAAC,MAAA,CAAC;YACrC,uBAAA,IAAI,kCAAiB,KAAK,MAAA,CAAC;YAC3B,uBAAuB;QACzB,CAAC;aAAM,CAAC;YACN,uBAAA,IAAI,6BAAY;gBACd,gCAAgC,CAAC;oBAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,SAAS,EAAE,IAAI,CAAC,SAAS;iBAC1B,CAAC;aACH,MAAA,CAAC;YACF,uBAAA,IAAI,kCAAiB,IAAI,MAAA,CAAC;QAC5B,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,GAAW;QACf,MAAM,MAAM,GAAG,uBAAA,IAAI,4DAAO,MAAX,IAAI,EAAQ,GAAG,CAAC,CAAC;QAEhC,IAAI,uBAAA,IAAI,sCAAc,EAAE,CAAC;YACvB,IAAI,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC;YAC7B,IAAI,UAAU,KAAK,0BAA0B,CAAC,SAAS,EAAE,CAAC;gBACxD,UAAU,GAAG,0BAA0B,CAAC,SAAS,CAAC;YACpD,CAAC;iBAAM,IAAI,UAAU,KAAK,0BAA0B,CAAC,SAAS,EAAE,CAAC;gBAC/D,UAAU,GAAG,0BAA0B,CAAC,SAAS,CAAC;YACpD,CAAC;YACD,OAAO;gBACL,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,IAAI,EAAE,UAAU;aACjB,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IA0HD;;;;;OAKG;IACH,YAAY,CAAC,GAAW;QACtB,KAAK,MAAM,EAAE,cAAc,EAAE,IAAI,uBAAA,IAAI,iCAAS,EAAE,CAAC;YAC/C,IAAI,CAAC,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAChE,SAAS;YACX,CAAC;YACD,MAAM,WAAW,GAAG,iBAAiB,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;YAC3D,IAAI,WAAW,EAAE,CAAC;gBAChB,OAAO,WAAW,CAAC;YACrB,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,mBAAmB,CAAC,SAAiB;QACnC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC/C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO;gBACL,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,0BAA0B,CAAC,iBAAiB;aACnD,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACvE,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QAExC,KAAK,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,uBAAA,IAAI,iCAAS,EAAE,CAAC;YACzD,oEAAoE;YACpE,MAAM,cAAc,GAAG,qBAAqB,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;YACrE,IAAI,cAAc,EAAE,CAAC;gBACnB,MAAM,KAAK,GAAG,mBAAmB,CAAC,cAAc,CAAC,CAAC;gBAClD,OAAO;oBACL,KAAK;oBACL,IAAI;oBACJ,MAAM,EAAE,KAAK;oBACb,IAAI,EAAE,0BAA0B,CAAC,SAAS;oBAC1C,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;iBAC3D,CAAC;YACJ,CAAC;QACH,CAAC;QAED,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;QACxD,MAAM,cAAc,GAAG,qBAAqB,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;QAEvE,KAAK,MAAM,EAAE,iBAAiB,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,uBAAA,IAAI,iCAAS,EAAE,CAAC;YACjE,IAAI,CAAC,iBAAiB,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzD,SAAS;YACX,CAAC;YAED,IAAI,iBAAiB,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC7C,OAAO;oBACL,IAAI;oBACJ,MAAM,EAAE,IAAI;oBACZ,IAAI,EAAE,0BAA0B,CAAC,iBAAiB;oBAClD,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;iBAC3D,CAAC;YACJ,CAAC;YAED,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;gBACpC,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;gBACtC,IAAI,iBAAiB,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;oBAC3C,OAAO;wBACL,IAAI;wBACJ,MAAM,EAAE,IAAI;wBACZ,IAAI,EAAE,0BAA0B,CAAC,iBAAiB;wBAClD,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;qBAC3D,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QACD,sBAAsB;QACtB,OAAO;YACL,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,0BAA0B,CAAC,iBAAiB;SACnD,CAAC;IACJ,CAAC;CACF;mMAjNQ,GAAW;IAChB,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;IAE/C,+CAA+C;IAC/C,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1B,kCAAkC;QAClC,gDAAgD;QAChD,oGAAoG;QACpG,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;QAC5B,KAAK,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,uBAAA,IAAI,iCAAS,EAAE,CAAC;YACzD,MAAM,cAAc,GAAG,SAAS;iBAC7B,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;iBACzC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;gBAChB,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;YAC5B,CAAC,CAAC,CAAC;YACL,IAAI,cAAc,EAAE,CAAC;gBACnB,OAAO;oBACL,IAAI;oBACJ,KAAK,EAAE,GAAG;oBACV,MAAM,EAAE,IAAI;oBACZ,IAAI,EAAE,0BAA0B,CAAC,SAAS;oBAC1C,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;iBAC3D,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,0BAA0B,CAAC,GAAG;SACrC,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAEjE,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAEnC,KAAK,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,uBAAA,IAAI,iCAAS,EAAE,CAAC;QAC9D,IAAI,CAAC,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChE,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,iBAAiB,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QACzD,IAAI,SAAS,EAAE,CAAC;YACd,OAAO;gBACL,KAAK,EAAE,SAAS;gBAChB,IAAI;gBACJ,MAAM,EAAE,IAAI;gBACZ,IAAI,EAAE,0BAA0B,CAAC,SAAS;gBAC1C,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;aAC3D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,uBAAA,IAAI,iCAAS,EAAE,CAAC;QACzD,oEAAoE;QACpE,MAAM,cAAc,GAAG,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAChE,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,KAAK,GAAG,mBAAmB,CAAC,cAAc,CAAC,CAAC;YAClD,OAAO;gBACL,KAAK;gBACL,IAAI;gBACJ,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,0BAA0B,CAAC,SAAS;gBAC1C,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;aAC3D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,MAAM,EACT,SAAS,EACT,SAAS,EACT,IAAI,EACJ,SAAS,EACT,OAAO,GACR,IAAI,uBAAA,IAAI,iCAAS,EAAE,CAAC;QACnB,oEAAoE;QACpE,MAAM,cAAc,GAAG,qBAAqB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAChE,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,KAAK,GAAG,mBAAmB,CAAC,cAAc,CAAC,CAAC;YAClD,OAAO;gBACL,KAAK;gBACL,IAAI;gBACJ,MAAM,EAAE,IAAI;gBACZ,IAAI,EAAE,0BAA0B,CAAC,SAAS;gBAC1C,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;aAC3D,CAAC;QACJ,CAAC;QAED,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,gDAAgD;YAChD,IAAI,SAAS,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;YAC/C,YAAY;YACZ,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;YAC7C,0BAA0B;YAC1B,MAAM,kBAAkB,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE;gBACxD,MAAM,WAAW,GAAG,sBAAsB,CAAC,WAAW,CAAC,CAAC;gBACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;gBAC9C,OAAO,IAAI,IAAI,SAAS,CAAC;YAC3B,CAAC,CAAC,CAAC;YACH,IAAI,kBAAkB,EAAE,CAAC;gBACvB,MAAM,KAAK,GAAG,mBAAmB,CAAC,kBAAkB,CAAC,CAAC;gBACtD,OAAO;oBACL,IAAI;oBACJ,KAAK;oBACL,MAAM,EAAE,IAAI;oBACZ,IAAI,EAAE,0BAA0B,CAAC,KAAK;oBACtC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;iBAC3D,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,wBAAwB;IACxB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,0BAA0B,CAAC,GAAG,EAAE,CAAC;AACjE,CAAC;AA6FH;;;;GAIG;AACH,SAAS,YAAY;IACnB,kDAAkD;IAClD,MAAM,GAAG,GACP,wGAAwG,CAAC;IAC3G,OAAO,IAAI,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAC9B,CAAC","sourcesContent":["import { distance } from 'fastest-levenshtein';\n\nimport { matchedPathPrefix } from './PathTrie.js';\nimport type { PathTrie } from './PathTrie.js';\nimport { PhishingDetectorResultType } from './types.js';\nimport type { PhishingDetectorResult } from './types.js';\nimport {\n domainPartsToDomain,\n domainPartsToFuzzyForm,\n domainToParts,\n generateParentDomains,\n getDefaultPhishingDetectorConfig,\n getHostnameFromUrl,\n matchPartsAgainstList,\n processConfigs,\n sha256Hash,\n} from './utils.js';\n\nexport type LegacyPhishingDetectorList = {\n whitelist?: string[];\n blacklist?: string[];\n c2DomainBlocklist?: string[];\n} & FuzzyTolerance;\n\nexport type PhishingDetectorList = {\n allowlist?: string[];\n blocklist?: string[];\n blocklistPaths?: PathTrie;\n c2DomainBlocklist?: string[];\n name?: string;\n version?: string | number;\n tolerance?: number;\n} & FuzzyTolerance;\n\nexport type FuzzyTolerance =\n | {\n tolerance?: number;\n fuzzylist: string[];\n }\n | {\n tolerance?: never;\n fuzzylist?: never;\n };\n\nexport type PhishingDetectorOptions =\n | LegacyPhishingDetectorList\n | PhishingDetectorList[];\n\nexport type PhishingDetectorConfiguration = {\n name?: string;\n version?: number | string;\n allowlist: string[][];\n blocklist: string[][];\n blocklistPaths?: PathTrie;\n c2DomainBlocklist?: string[];\n fuzzylist: string[][];\n tolerance: number;\n};\n\nexport class PhishingDetector {\n readonly #configs: PhishingDetectorConfiguration[];\n\n readonly #legacyConfig: boolean;\n\n /**\n * Construct a phishing detector, which can check whether origins are known\n * to be malicious or similar to common phishing targets.\n *\n * A list of configurations is accepted. Each origin checked is processed\n * using each configuration in sequence, so the order defines which\n * configurations take precedence.\n *\n * @param opts - Phishing detection options\n */\n constructor(opts: PhishingDetectorOptions) {\n // recommended configuration\n if (Array.isArray(opts)) {\n this.#configs = processConfigs(opts);\n this.#legacyConfig = false;\n // legacy configuration\n } else {\n this.#configs = [\n getDefaultPhishingDetectorConfig({\n allowlist: opts.whitelist,\n blocklist: opts.blacklist,\n fuzzylist: opts.fuzzylist,\n tolerance: opts.tolerance,\n }),\n ];\n this.#legacyConfig = true;\n }\n }\n\n /**\n * Check if a url is known to be malicious or similar to a common phishing\n * target. This will check the hostname and IPFS CID that is sometimes\n * located in the path.\n *\n * @param url - The url to check.\n * @returns The result of the check.\n */\n check(url: string): PhishingDetectorResult {\n const result = this.#check(url);\n\n if (this.#legacyConfig) {\n let legacyType = result.type;\n if (legacyType === PhishingDetectorResultType.Allowlist) {\n legacyType = PhishingDetectorResultType.Whitelist;\n } else if (legacyType === PhishingDetectorResultType.Blocklist) {\n legacyType = PhishingDetectorResultType.Blacklist;\n }\n return {\n match: result.match,\n result: result.result,\n type: legacyType,\n };\n }\n return result;\n }\n\n #check(url: string): PhishingDetectorResult {\n const ipfsCidMatch = url.match(ipfsCidRegex());\n\n // Check for IPFS CID related blocklist entries\n if (ipfsCidMatch !== null) {\n // there is a cID string somewhere\n // Determine if any of the entries are ipfs cids\n // Depending on the gateway, the CID is in the path OR a subdomain, so we do a regex match on it all\n const cID = ipfsCidMatch[0];\n for (const { blocklist, name, version } of this.#configs) {\n const blocklistMatch = blocklist\n .filter((entries) => entries.length === 1)\n .find((entries) => {\n return entries[0] === cID;\n });\n if (blocklistMatch) {\n return {\n name,\n match: cID,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n\n let domain;\n try {\n domain = new URL(url).hostname;\n } catch {\n return {\n result: false,\n type: PhishingDetectorResultType.All,\n };\n }\n\n const fqdn = domain.endsWith('.') ? domain.slice(0, -1) : domain;\n\n const source = domainToParts(fqdn);\n\n for (const { blocklistPaths, name, version } of this.#configs) {\n if (!blocklistPaths || Object.keys(blocklistPaths).length === 0) {\n continue;\n }\n const pathMatch = matchedPathPrefix(url, blocklistPaths);\n if (pathMatch) {\n return {\n match: pathMatch,\n name,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n for (const { allowlist, name, version } of this.#configs) {\n // if source matches allowlist hostname (or subdomain thereof), PASS\n const allowlistMatch = matchPartsAgainstList(source, allowlist);\n if (allowlistMatch) {\n const match = domainPartsToDomain(allowlistMatch);\n return {\n match,\n name,\n result: false,\n type: PhishingDetectorResultType.Allowlist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n for (const {\n blocklist,\n fuzzylist,\n name,\n tolerance,\n version,\n } of this.#configs) {\n // if source matches blocklist hostname (or subdomain thereof), FAIL\n const blocklistMatch = matchPartsAgainstList(source, blocklist);\n if (blocklistMatch) {\n const match = domainPartsToDomain(blocklistMatch);\n return {\n match,\n name,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n\n if (tolerance > 0) {\n // check if near-match of whitelist domain, FAIL\n let fuzzyForm = domainPartsToFuzzyForm(source);\n // strip www\n fuzzyForm = fuzzyForm.replace(/^www\\./u, '');\n // check against fuzzylist\n const levenshteinMatched = fuzzylist.find((targetParts) => {\n const fuzzyTarget = domainPartsToFuzzyForm(targetParts);\n const dist = distance(fuzzyForm, fuzzyTarget);\n return dist <= tolerance;\n });\n if (levenshteinMatched) {\n const match = domainPartsToDomain(levenshteinMatched);\n return {\n name,\n match,\n result: true,\n type: PhishingDetectorResultType.Fuzzy,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n\n // matched nothing, PASS\n return { result: false, type: PhishingDetectorResultType.All };\n }\n\n /**\n * Gets the specific terminal path from blocklistPaths that is blocking a URL.\n *\n * @param url - The URL to check.\n * @returns The terminal path that is blocking the URL, or null if not blocked.\n */\n blockingPath(url: string): string | null {\n for (const { blocklistPaths } of this.#configs) {\n if (!blocklistPaths || Object.keys(blocklistPaths).length === 0) {\n continue;\n }\n const matchedPath = matchedPathPrefix(url, blocklistPaths);\n if (matchedPath) {\n return matchedPath;\n }\n }\n\n return null;\n }\n\n /**\n * Checks if a URL is blocked against the hashed request blocklist.\n * This is done by hashing the URL's hostname and checking it against the hashed request blocklist.\n *\n * @param urlString - The URL to check.\n * @returns An object indicating if the URL is blocked and relevant metadata.\n */\n isMaliciousC2Domain(urlString: string): PhishingDetectorResult {\n const hostname = getHostnameFromUrl(urlString);\n if (!hostname) {\n return {\n result: false,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n };\n }\n\n const fqdn = hostname.endsWith('.') ? hostname.slice(0, -1) : hostname;\n const sourceParts = domainToParts(fqdn);\n\n for (const { allowlist, name, version } of this.#configs) {\n // if source matches allowlist hostname (or subdomain thereof), PASS\n const allowlistMatch = matchPartsAgainstList(sourceParts, allowlist);\n if (allowlistMatch) {\n const match = domainPartsToDomain(allowlistMatch);\n return {\n match,\n name,\n result: false,\n type: PhishingDetectorResultType.Allowlist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n const hostnameHash = sha256Hash(hostname.toLowerCase());\n const domainsToCheck = generateParentDomains(sourceParts.reverse(), 5);\n\n for (const { c2DomainBlocklist, name, version } of this.#configs) {\n if (!c2DomainBlocklist || c2DomainBlocklist.length === 0) {\n continue;\n }\n\n if (c2DomainBlocklist.includes(hostnameHash)) {\n return {\n name,\n result: true,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n version: version === undefined ? version : String(version),\n };\n }\n\n for (const domain of domainsToCheck) {\n const domainHash = sha256Hash(domain);\n if (c2DomainBlocklist.includes(domainHash)) {\n return {\n name,\n result: true,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n // did not match, PASS\n return {\n result: false,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n };\n }\n}\n\n/**\n * Runs a regex match to determine if a string is a IPFS CID\n *\n * @returns Regex string for IPFS CID\n */\nfunction ipfsCidRegex() {\n // regex from https://stackoverflow.com/a/67176726\n const reg =\n 'Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}';\n return new RegExp(reg, 'u');\n}\n"]}
1
+ {"version":3,"file":"PhishingDetector.mjs","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"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","sourcesContent":["import { distance } from 'fastest-levenshtein';\n\nimport { matchedPathPrefix } from './PathTrie.js';\nimport type { PathTrie } from './PathTrie.js';\nimport { PhishingDetectorResultType } from './types.js';\nimport type { PhishingDetectorResult } from './types.js';\nimport {\n domainPartsToDomain,\n domainPartsToFuzzyForm,\n domainToParts,\n generateParentDomains,\n getDefaultPhishingDetectorConfig,\n getHostnameFromUrl,\n matchPartsAgainstList,\n processConfigs,\n sha256Hash,\n} from './utils.js';\n\nexport type LegacyPhishingDetectorList = {\n whitelist?: string[];\n blacklist?: string[];\n c2DomainBlocklist?: string[];\n} & FuzzyTolerance;\n\nexport type PhishingDetectorList = {\n allowlist?: string[];\n blocklist?: string[];\n blocklistPaths?: PathTrie;\n c2DomainBlocklist?: string[];\n name?: string;\n version?: string | number;\n tolerance?: number;\n} & FuzzyTolerance;\n\nexport type FuzzyTolerance =\n | {\n tolerance?: number;\n fuzzylist: string[];\n }\n | {\n tolerance?: never;\n fuzzylist?: never;\n };\n\nexport type PhishingDetectorOptions =\n | LegacyPhishingDetectorList\n | PhishingDetectorList[];\n\nexport type PhishingDetectorConfiguration = {\n name?: string;\n version?: number | string;\n allowlist: string[][];\n blocklist: string[][];\n c2DomainBlocklist?: string[];\n blocklistPaths?: PathTrie;\n fuzzylist: string[][];\n tolerance: number;\n};\n\ntype InternalPhishingDetectorConfiguration = Omit<\n PhishingDetectorConfiguration,\n 'c2DomainBlocklist'\n> & {\n c2DomainBlocklist?: Set<string>;\n};\n\nexport class PhishingDetector {\n readonly #configs: InternalPhishingDetectorConfiguration[];\n\n readonly #legacyConfig: boolean;\n\n /**\n * Construct a phishing detector, which can check whether origins are known\n * to be malicious or similar to common phishing targets.\n *\n * A list of configurations is accepted. Each origin checked is processed\n * using each configuration in sequence, so the order defines which\n * configurations take precedence.\n *\n * @param opts - Phishing detection options\n */\n constructor(opts: PhishingDetectorOptions) {\n // recommended configuration\n if (Array.isArray(opts)) {\n this.#configs = processConfigs(opts).map((config) => ({\n ...config,\n c2DomainBlocklist: new Set<string>(config.c2DomainBlocklist),\n }));\n this.#legacyConfig = false;\n // legacy configuration\n } else {\n this.#configs = [\n {\n ...getDefaultPhishingDetectorConfig({\n allowlist: opts.whitelist,\n blocklist: opts.blacklist,\n fuzzylist: opts.fuzzylist,\n tolerance: opts.tolerance,\n }),\n c2DomainBlocklist: new Set<string>(),\n },\n ];\n this.#legacyConfig = true;\n }\n }\n\n /**\n * Check if a url is known to be malicious or similar to a common phishing\n * target. This will check the hostname and IPFS CID that is sometimes\n * located in the path.\n *\n * @param url - The url to check.\n * @returns The result of the check.\n */\n check(url: string): PhishingDetectorResult {\n const result = this.#check(url);\n\n if (this.#legacyConfig) {\n let legacyType = result.type;\n if (legacyType === PhishingDetectorResultType.Allowlist) {\n legacyType = PhishingDetectorResultType.Whitelist;\n } else if (legacyType === PhishingDetectorResultType.Blocklist) {\n legacyType = PhishingDetectorResultType.Blacklist;\n }\n return {\n match: result.match,\n result: result.result,\n type: legacyType,\n };\n }\n return result;\n }\n\n #check(url: string): PhishingDetectorResult {\n const ipfsCidMatch = url.match(ipfsCidRegex());\n\n // Check for IPFS CID related blocklist entries\n if (ipfsCidMatch !== null) {\n // there is a cID string somewhere\n // Determine if any of the entries are ipfs cids\n // Depending on the gateway, the CID is in the path OR a subdomain, so we do a regex match on it all\n const cID = ipfsCidMatch[0];\n for (const { blocklist, name, version } of this.#configs) {\n const blocklistMatch = blocklist\n .filter((entries) => entries.length === 1)\n .find((entries) => {\n return entries[0] === cID;\n });\n if (blocklistMatch) {\n return {\n name,\n match: cID,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n\n let domain;\n try {\n domain = new URL(url).hostname;\n } catch {\n return {\n result: false,\n type: PhishingDetectorResultType.All,\n };\n }\n\n const fqdn = domain.endsWith('.') ? domain.slice(0, -1) : domain;\n\n const source = domainToParts(fqdn);\n\n for (const { blocklistPaths, name, version } of this.#configs) {\n if (!blocklistPaths || Object.keys(blocklistPaths).length === 0) {\n continue;\n }\n const pathMatch = matchedPathPrefix(url, blocklistPaths);\n if (pathMatch) {\n return {\n match: pathMatch,\n name,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n for (const { allowlist, name, version } of this.#configs) {\n // if source matches allowlist hostname (or subdomain thereof), PASS\n const allowlistMatch = matchPartsAgainstList(source, allowlist);\n if (allowlistMatch) {\n const match = domainPartsToDomain(allowlistMatch);\n return {\n match,\n name,\n result: false,\n type: PhishingDetectorResultType.Allowlist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n for (const {\n blocklist,\n fuzzylist,\n name,\n tolerance,\n version,\n } of this.#configs) {\n // if source matches blocklist hostname (or subdomain thereof), FAIL\n const blocklistMatch = matchPartsAgainstList(source, blocklist);\n if (blocklistMatch) {\n const match = domainPartsToDomain(blocklistMatch);\n return {\n match,\n name,\n result: true,\n type: PhishingDetectorResultType.Blocklist,\n version: version === undefined ? version : String(version),\n };\n }\n\n if (tolerance > 0) {\n // check if near-match of whitelist domain, FAIL\n let fuzzyForm = domainPartsToFuzzyForm(source);\n // strip www\n fuzzyForm = fuzzyForm.replace(/^www\\./u, '');\n // check against fuzzylist\n const levenshteinMatched = fuzzylist.find((targetParts) => {\n const fuzzyTarget = domainPartsToFuzzyForm(targetParts);\n const dist = distance(fuzzyForm, fuzzyTarget);\n return dist <= tolerance;\n });\n if (levenshteinMatched) {\n const match = domainPartsToDomain(levenshteinMatched);\n return {\n name,\n match,\n result: true,\n type: PhishingDetectorResultType.Fuzzy,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n\n // matched nothing, PASS\n return { result: false, type: PhishingDetectorResultType.All };\n }\n\n /**\n * Gets the specific terminal path from blocklistPaths that is blocking a URL.\n *\n * @param url - The URL to check.\n * @returns The terminal path that is blocking the URL, or null if not blocked.\n */\n blockingPath(url: string): string | null {\n for (const { blocklistPaths } of this.#configs) {\n if (!blocklistPaths || Object.keys(blocklistPaths).length === 0) {\n continue;\n }\n const matchedPath = matchedPathPrefix(url, blocklistPaths);\n if (matchedPath) {\n return matchedPath;\n }\n }\n\n return null;\n }\n\n /**\n * Checks if a URL is blocked against the hashed request blocklist.\n * This is done by hashing the URL's hostname and checking it against the hashed request blocklist.\n *\n * @param urlString - The URL to check.\n * @returns An object indicating if the URL is blocked and relevant metadata.\n */\n isMaliciousC2Domain(urlString: string): PhishingDetectorResult {\n const hostname = getHostnameFromUrl(urlString);\n if (!hostname) {\n return {\n result: false,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n };\n }\n\n const fqdn = hostname.endsWith('.') ? hostname.slice(0, -1) : hostname;\n const sourceParts = domainToParts(fqdn);\n\n for (const { allowlist, name, version } of this.#configs) {\n // if source matches allowlist hostname (or subdomain thereof), PASS\n const allowlistMatch = matchPartsAgainstList(sourceParts, allowlist);\n if (allowlistMatch) {\n const match = domainPartsToDomain(allowlistMatch);\n return {\n match,\n name,\n result: false,\n type: PhishingDetectorResultType.Allowlist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n\n const hostnameHash = sha256Hash(hostname.toLowerCase());\n const domainsToCheck = generateParentDomains(sourceParts.reverse(), 5);\n\n for (const { c2DomainBlocklist, name, version } of this.#configs) {\n if (!c2DomainBlocklist || c2DomainBlocklist.size === 0) {\n continue;\n }\n\n if (c2DomainBlocklist.has(hostnameHash)) {\n return {\n name,\n result: true,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n version: version === undefined ? version : String(version),\n };\n }\n\n for (const domain of domainsToCheck) {\n const domainHash = sha256Hash(domain);\n if (c2DomainBlocklist.has(domainHash)) {\n return {\n name,\n result: true,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n version: version === undefined ? version : String(version),\n };\n }\n }\n }\n // did not match, PASS\n return {\n result: false,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n };\n }\n}\n\n/**\n * Runs a regex match to determine if a string is a IPFS CID\n *\n * @returns Regex string for IPFS CID\n */\nfunction ipfsCidRegex() {\n // regex from https://stackoverflow.com/a/67176726\n const reg =\n 'Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}';\n return new RegExp(reg, 'u');\n}\n"]}
package/dist/utils.cjs CHANGED
@@ -186,18 +186,18 @@ exports.processDomainList = processDomainList;
186
186
  * @param override.blocklist - the optional blocklist to override.
187
187
  * @param override.fuzzylist - the optional fuzzylist to override.
188
188
  * @param override.tolerance - the optional tolerance to override.
189
+ * @param override.c2DomainBlocklist - the optional c2DomainBlocklist to override.
189
190
  * @returns the default phishing detector configuration.
190
191
  */
191
- const getDefaultPhishingDetectorConfig = ({ allowlist = [], blocklist = [], fuzzylist = [], tolerance = DEFAULT_TOLERANCE, }) => {
192
- return {
193
- allowlist: (0, exports.processDomainList)(allowlist),
194
- // We can assume that blocklist is already separated into hostname-only entries
195
- // and hostname+path entries so we do not need to separate it again.
196
- blocklist: (0, exports.processDomainList)(blocklist),
197
- fuzzylist: (0, exports.processDomainList)(fuzzylist),
198
- tolerance,
199
- };
200
- };
192
+ const getDefaultPhishingDetectorConfig = ({ allowlist = [], blocklist = [], c2DomainBlocklist = [], fuzzylist = [], tolerance = DEFAULT_TOLERANCE, }) => ({
193
+ allowlist: (0, exports.processDomainList)(allowlist),
194
+ // We can assume that blocklist is already separated into hostname-only entries
195
+ // and hostname+path entries so we do not need to separate it again.
196
+ blocklist: (0, exports.processDomainList)(blocklist),
197
+ c2DomainBlocklist,
198
+ fuzzylist: (0, exports.processDomainList)(fuzzylist),
199
+ tolerance,
200
+ });
201
201
  exports.getDefaultPhishingDetectorConfig = getDefaultPhishingDetectorConfig;
202
202
  /**
203
203
  * Processes the configurations for the phishing detector, filtering out any invalid configs.
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex } from '@noble/hashes/utils';\nimport { sha256 } from 'ethereum-cryptography/sha256';\n\nimport { deleteFromTrie, insertToTrie, deepCopyPathTrie } from './PathTrie.js';\nimport type { Hotlist, PhishingListState } from './PhishingController.js';\nimport { ListKeys, phishingListKeyNameMap } from './PhishingController.js';\nimport type {\n PhishingDetectorList,\n PhishingDetectorConfiguration,\n} from './PhishingDetector.js';\nimport {\n ADDRESS_SCAN_SUPPORTED_CHAINS,\n APPROVAL_SUPPORTED_CHAINS,\n DEFAULT_CHAIN_ID_TO_NAME,\n TOKEN_SCAN_SUPPORTED_CHAINS,\n} from './types.js';\nimport type {\n AddressScanSupportedChain,\n ApprovalSupportedChain,\n TokenScanCacheData,\n TokenScanResult,\n TokenScanSupportedChain,\n} from './types.js';\n\nconst DEFAULT_TOLERANCE = 3;\n\n/**\n * Fetches current epoch time in seconds.\n *\n * @returns the Date.now() time in seconds instead of miliseconds. backend files rely on timestamps in seconds since epoch.\n */\nexport const fetchTimeNow = (): number => Math.round(Date.now() / 1000);\n\n/**\n * Rounds a Unix timestamp down to the nearest minute.\n *\n * @param unixTimestamp - The Unix timestamp to be rounded.\n * @returns The rounded Unix timestamp.\n */\nexport function roundToNearestMinute(unixTimestamp: number): number {\n return Math.floor(unixTimestamp / 60) * 60;\n}\n\n/**\n * Split a string into two pieces, using the first period as the delimiter.\n *\n * @param stringToSplit - The string to split.\n * @returns An array of length two containing the beginning and end of the string.\n */\nconst splitStringByPeriod = <Start extends string, End extends string>(\n stringToSplit: `${Start}.${End}`,\n): [Start, End] => {\n const periodIndex = stringToSplit.indexOf('.');\n return [\n stringToSplit.slice(0, periodIndex) as Start,\n stringToSplit.slice(periodIndex + 1) as End,\n ];\n};\n\nexport const getHostnameAndPathComponents = (\n url: string,\n): { hostname: string; pathComponents: string[] } => {\n const urlWithProtocol = url.startsWith('http') ? url : `https://${url}`;\n try {\n const { hostname, pathname } = new URL(urlWithProtocol);\n return {\n hostname: hostname.toLowerCase(),\n pathComponents: pathname\n .split('/')\n .filter(Boolean)\n .map((component) => decodeURIComponent(component)),\n };\n } catch {\n return {\n hostname: '',\n pathComponents: [],\n };\n }\n};\n\n/**\n * Determines which diffs are applicable to the listState, then applies those diffs.\n *\n * @param listState - the stalelist or the existing liststate that diffs will be applied to.\n * @param hotlistDiffs - the diffs to apply to the listState if valid.\n * @param listKey - the key associated with the input/output phishing list state.\n * @param recentlyAddedC2Domains - list of hashed C2 domains to add to the local c2 domain blocklist\n * @param recentlyRemovedC2Domains - list of hashed C2 domains to remove from the local c2 domain blocklist\n * @returns the new list state\n */\nexport const applyDiffs = (\n listState: PhishingListState,\n hotlistDiffs: Hotlist,\n listKey: ListKeys,\n recentlyAddedC2Domains: string[] = [],\n recentlyRemovedC2Domains: string[] = [],\n): PhishingListState => {\n // filter to remove diffs that were added before the lastUpdate time.\n // filter to remove diffs that aren't applicable to the specified list (by listKey).\n const diffsToApply = hotlistDiffs.filter(\n ({ timestamp, targetList }) =>\n timestamp > listState.lastUpdated &&\n splitStringByPeriod(targetList)[0] === listKey,\n );\n\n // the reason behind using latestDiffTimestamp as the lastUpdated time\n // is so that we can benefit server-side from memoization due to end client's\n // `GET /v1/diffSince/:timestamp` requests lining up with\n // our periodic updates (which create diffs at specific timestamps).\n let latestDiffTimestamp = listState.lastUpdated;\n\n const listSets = {\n allowlist: new Set(listState.allowlist),\n blocklist: new Set(listState.blocklist),\n fuzzylist: new Set(listState.fuzzylist),\n c2DomainBlocklist: new Set(listState.c2DomainBlocklist),\n };\n\n // deep copy of blocklistPaths to avoid mutating the original\n const newBlocklistPaths = deepCopyPathTrie(listState.blocklistPaths);\n\n for (const { isRemoval, targetList, url, timestamp } of diffsToApply) {\n const targetListType = splitStringByPeriod(targetList)[1];\n if (timestamp > latestDiffTimestamp) {\n latestDiffTimestamp = timestamp;\n }\n\n if (isRemoval) {\n if (targetListType === 'blocklistPaths') {\n deleteFromTrie(url, newBlocklistPaths);\n } else {\n listSets[targetListType].delete(url);\n }\n continue;\n }\n\n if (targetListType === 'blocklistPaths') {\n insertToTrie(url, newBlocklistPaths);\n } else {\n listSets[targetListType].add(url);\n }\n }\n\n if (listKey === ListKeys.EthPhishingDetectConfig) {\n for (const hash of recentlyAddedC2Domains) {\n listSets.c2DomainBlocklist.add(hash);\n }\n for (const hash of recentlyRemovedC2Domains) {\n listSets.c2DomainBlocklist.delete(hash);\n }\n }\n\n return {\n c2DomainBlocklist: Array.from(listSets.c2DomainBlocklist),\n allowlist: Array.from(listSets.allowlist),\n blocklist: Array.from(listSets.blocklist),\n fuzzylist: Array.from(listSets.fuzzylist),\n blocklistPaths: newBlocklistPaths,\n version: listState.version,\n name: phishingListKeyNameMap[listKey],\n tolerance: listState.tolerance,\n lastUpdated: latestDiffTimestamp,\n };\n};\n\n/**\n * Validates the configuration object for the phishing detector.\n *\n * @param config - the configuration object to validate.\n * @throws an error if the configuration is invalid.\n */\nexport function validateConfig(\n config: unknown,\n): asserts config is PhishingListState {\n if (config === null || typeof config !== 'object') {\n throw new Error('Invalid config');\n }\n\n if ('tolerance' in config && !('fuzzylist' in config)) {\n throw new Error('Fuzzylist tolerance provided without fuzzylist');\n }\n\n if (\n 'name' in config &&\n (typeof config.name !== 'string' || config.name === '')\n ) {\n throw new Error(\"Invalid config parameter: 'name'\");\n }\n\n if (\n 'version' in config &&\n (!['number', 'string'].includes(typeof config.version) ||\n config.version === '')\n ) {\n throw new Error(\"Invalid config parameter: 'version'\");\n }\n}\n\n/**\n * Converts a domain string to a list of domain parts.\n *\n * @param domain - the domain string to convert.\n * @returns the list of domain parts.\n */\nexport const domainToParts = (domain: string) => {\n return domain.split('.').reverse();\n};\n\n/**\n * Converts a list of domain strings to a list of domain parts.\n *\n * @param list - the list of domain strings to convert.\n * @returns the list of domain parts.\n */\nexport const processDomainList = (list: string[]): string[][] => {\n return list.reduce<string[][]>((acc, domain) => {\n if (typeof domain !== 'string') {\n console.warn(`Invalid domain value in list: ${JSON.stringify(domain)}`);\n return acc;\n }\n acc.push(domainToParts(domain));\n return acc;\n }, []);\n};\n\n/**\n * Gets the default phishing detector configuration.\n *\n * @param override - the optional override for the configuration.\n * @param override.allowlist - the optional allowlist to override.\n * @param override.blocklist - the optional blocklist to override.\n * @param override.fuzzylist - the optional fuzzylist to override.\n * @param override.tolerance - the optional tolerance to override.\n * @returns the default phishing detector configuration.\n */\nexport const getDefaultPhishingDetectorConfig = ({\n allowlist = [],\n blocklist = [],\n fuzzylist = [],\n tolerance = DEFAULT_TOLERANCE,\n}: {\n allowlist?: string[];\n blocklist?: string[];\n fuzzylist?: string[];\n tolerance?: number;\n}): PhishingDetectorConfiguration => {\n return {\n allowlist: processDomainList(allowlist),\n // We can assume that blocklist is already separated into hostname-only entries\n // and hostname+path entries so we do not need to separate it again.\n blocklist: processDomainList(blocklist),\n fuzzylist: processDomainList(fuzzylist),\n tolerance,\n };\n};\n\n/**\n * Processes the configurations for the phishing detector, filtering out any invalid configs.\n *\n * @param configs - The configurations to process.\n * @returns An array of processed and valid configurations.\n */\nexport const processConfigs = (\n configs: PhishingDetectorList[] = [],\n): PhishingDetectorConfiguration[] => {\n return configs\n .filter((config) => {\n try {\n validateConfig(config);\n return true;\n } catch (error) {\n console.error(error);\n return false;\n }\n })\n .map((config) => ({\n ...config,\n ...getDefaultPhishingDetectorConfig(config),\n }));\n};\n\n/**\n * Converts a list of domain parts to a domain string.\n *\n * @param domainParts - the list of domain parts.\n * @returns the domain string.\n */\nexport const domainPartsToDomain = (domainParts: string[]) => {\n return domainParts.slice().reverse().join('.');\n};\n\n/**\n * Converts a list of domain parts to a fuzzy form.\n *\n * @param domainParts - the list of domain parts.\n * @returns the fuzzy form of the domain.\n */\nexport const domainPartsToFuzzyForm = (domainParts: string[]) => {\n return domainParts.slice(1).reverse().join('.');\n};\n\n/**\n * Matches the target parts, ignoring extra subdomains on source.\n *\n * @param source - the source domain parts.\n * @param list - the list of domain parts to match against.\n * @returns the parts for the first found matching entry.\n */\nexport const matchPartsAgainstList = (source: string[], list: string[][]) => {\n return list.find((target) => {\n // target domain has more parts than source, fail\n if (target.length > source.length) {\n return false;\n }\n // source matches target or (is deeper subdomain)\n return target.every((part, index) => source[index] === part);\n });\n};\n\n/**\n * Generate the SHA-256 hash of a hostname.\n *\n * @param hostname - The hostname to hash.\n * @returns The SHA-256 hash of the hostname.\n */\nexport const sha256Hash = (hostname: string): string => {\n const hashBuffer = sha256(new TextEncoder().encode(hostname.toLowerCase()));\n return bytesToHex(hashBuffer);\n};\n\n/**\n * Extracts the hostname from a URL.\n *\n * @param url - The URL to extract the hostname from.\n * @returns The hostname extracted from the URL, or null if the URL is invalid.\n */\nexport const getHostnameFromUrl = (url: string): string | null => {\n let hostname;\n try {\n hostname = new URL(url).hostname;\n // above will not throw if 'http://.' is passed. in fact, any string with a dot will pass.\n if (!hostname || hostname.split('.').join('') === '') {\n return null;\n }\n } catch {\n return null;\n }\n return hostname;\n};\n\n/**\n * getHostnameFromWebUrl returns the hostname from a web URL.\n * It returns the hostname and a boolean indicating if the hostname is valid.\n *\n * @param url - The web URL to extract the hostname from.\n * @returns A tuple containing the extracted hostname and a boolean indicating if the hostname is valid.\n * @example\n * getHostnameFromWebUrl('https://example.com') // Returns: ['example.com', true]\n * getHostnameFromWebUrl('example.com') // Returns: ['', false]\n * getHostnameFromWebUrl('https://') // Returns: ['', false]\n * getHostnameFromWebUrl('') // Returns: ['', false]\n */\nexport const getHostnameFromWebUrl = (url: string): [string, boolean] => {\n if (\n !url.toLowerCase().startsWith('http://') &&\n !url.toLowerCase().startsWith('https://')\n ) {\n return ['', false];\n }\n\n const hostname = getHostnameFromUrl(url);\n return [hostname || '', Boolean(hostname)];\n};\n\n/**\n * Hosts where PDS single-URL scans include the URL path (shared gateways / hosts where many sites\n * share one origin). For all other hosts, only the hostname is sent.\n */\nexport const PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS = [\n 'ipfs.io',\n 'dweb.link',\n 'cf-ipfs.com',\n 'cloudflare-ipfs.com',\n 'irys.xyz',\n 'sites.google.com',\n] as const;\n\n/**\n * @param hostname - Lowercase normalization is applied for matching registered roots and subdomains.\n * @returns Whether {@link getPhishingDetectionScanUrlParam} appends pathname for this hostname.\n */\nexport function isPhishingDetectionPathBasedHostname(\n hostname: string,\n): boolean {\n const normalizedHost = hostname.toLowerCase();\n return PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS.some(\n (root) => normalizedHost === root || normalizedHost.endsWith(`.${root}`),\n );\n}\n\n/**\n * Builds the `url` query parameter for {@link PhishingController.scanUrl}. For hosts in\n * {@link PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS} (and their subdomains), the value is hostname\n * plus pathname, without protocol, query, or fragment. For all other hosts, only hostname is used.\n *\n * @param url - A web URL string (must use `http:` or `https:` — same rules as {@link getHostnameFromWebUrl}).\n * @returns A tuple of `[scanUrlParam, ok]` where `ok` is false when the URL is not a valid web URL.\n */\nexport const getPhishingDetectionScanUrlParam = (\n url: string,\n): [scanUrlParam: string, ok: boolean] => {\n const [hostname, ok] = getHostnameFromWebUrl(url);\n if (!ok) {\n return ['', false];\n }\n\n if (!isPhishingDetectionPathBasedHostname(hostname)) {\n return [hostname, true];\n }\n\n // `getHostnameFromWebUrl` already required a successful `new URL(url)` parse.\n const { pathname } = new URL(url);\n const pathSuffix = pathname === '/' ? '' : pathname;\n const scanUrlParam = pathSuffix ? `${hostname}${pathSuffix}` : hostname;\n\n return [scanUrlParam, true];\n};\n\nexport const getPathnameFromUrl = (url: string): string => {\n try {\n const { pathname } = new URL(url);\n return pathname;\n } catch {\n return '';\n }\n};\n\n/**\n * Generates all possible parent domains up to a specified limit.\n *\n * @param sourceParts - The list of domain parts in normal order (e.g., ['evil', 'domain', 'co', 'uk']).\n * @param limit - The maximum number of parent domains to generate (default is 5).\n * @returns An array of parent domains starting from the base TLD to the most specific subdomain.\n * @example\n * generateParentDomains(['evil', 'domain', 'co', 'uk'], 5)\n * // Returns: ['co.uk', 'domain.co.uk', 'evil.domain.co.uk']\n *\n * generateParentDomains(['uk'], 5)\n * // Returns: ['uk']\n *\n * generateParentDomains(['sub', 'example', 'com'], 5)\n * // Returns: ['example.com', 'sub.example.com']\n */\nexport const generateParentDomains = (\n sourceParts: string[],\n limit = 5,\n): string[] => {\n const domains: string[] = [];\n\n if (sourceParts.length === 0) {\n return domains;\n }\n\n if (sourceParts.length === 1) {\n // Single-segment hostname (e.g., 'uk')\n domains.push(sourceParts[0].toLowerCase());\n } else {\n // Start with the base domain or TLD (last two labels, e.g., 'co.uk' or 'example.com')\n const baseDomain = sourceParts.slice(-2).join('.');\n domains.push(baseDomain.toLowerCase());\n\n // Iteratively add one subdomain level at a time, up to the specified limit\n for (\n let i = sourceParts.length - 3;\n i >= 0 && domains.length < limit;\n i--\n ) {\n const domain = sourceParts.slice(i).join('.');\n domains.push(domain.toLowerCase());\n }\n }\n\n return domains;\n};\n\n/**\n * Builds a cache key for a token scan result.\n *\n * @param chainId - The chain ID.\n * @param address - The token address.\n * @param caseSensitive - When `true`, the address is kept as-is (for chains\n * like Solana where addresses are case-sensitive). When `false` (default),\n * the address is lowercased (appropriate for EVM).\n * @returns The cache key.\n */\nexport const buildCacheKey = (\n chainId: string,\n address: string,\n caseSensitive = false,\n) => {\n const normalizedAddress = caseSensitive ? address : address.toLowerCase();\n return `${chainId.toLowerCase()}:${normalizedAddress}`;\n};\n\n/**\n * Determines whether a chain name is supported for token approval scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isApprovalSupportedChain = (\n chain: string,\n): chain is ApprovalSupportedChain =>\n (APPROVAL_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Determines whether a chain name is supported for bulk token scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isTokenScanSupportedChain = (\n chain: string,\n): chain is TokenScanSupportedChain =>\n (TOKEN_SCAN_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Determines whether a chain name is supported for address scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isAddressScanSupportedChain = (\n chain: string,\n): chain is AddressScanSupportedChain =>\n (ADDRESS_SCAN_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Resolves the chain name from a chain ID.\n *\n * @param chainId - The chain ID.\n * @param mapping - The mapping of chain IDs to chain names.\n * @returns The chain name.\n */\nexport const resolveChainName = (\n chainId: string,\n mapping = DEFAULT_CHAIN_ID_TO_NAME,\n): string | null => {\n return mapping[chainId.toLowerCase() as keyof typeof mapping] ?? null;\n};\n\n/**\n * Resolves a chain ID to a Blockaid address-scan chain name, or `null` if\n * `scanAddress` would not call the Security Alerts API for this chain.\n *\n * @param chainId - Hex chain ID for EVM chains (e.g. `'0x1'`) or a chain\n * name for non-EVM chains (e.g. `'solana'`).\n * @returns The address-scan chain name, or `null` if unsupported.\n */\nexport const getAddressScanSupportedChain = (\n chainId: string,\n): AddressScanSupportedChain | null => {\n const chain = resolveChainName(chainId);\n if (!chain || !isAddressScanSupportedChain(chain)) {\n return null;\n }\n return chain;\n};\n\n/**\n * Determines whether `scanAddress` will call the Security Alerts API for\n * this chain, rather than immediately returning `ErrorResult`.\n *\n * Matches the gate inside `scanAddress`: the chain ID must resolve via\n * {@link resolveChainName}, and that name must be in\n * `ADDRESS_SCAN_SUPPORTED_CHAINS`.\n *\n * @param chainId - Hex chain ID for EVM chains (e.g. `'0x1'`) or a chain\n * name for non-EVM chains (e.g. `'solana'`).\n * @returns `true` if an address scan would hit the API.\n */\nexport const isAddressScanSupportedChainId = (chainId: string): boolean =>\n getAddressScanSupportedChain(chainId) !== null;\n\n/**\n * Split tokens into cached results and tokens that need to be fetched.\n *\n * @param cache - Cache-like object with get method.\n * @param cache.get - Method to retrieve cached data by key.\n * @param chainId - The chain ID.\n * @param tokens - Array of token addresses.\n * @param caseSensitive - When `true`, token addresses are kept as-is (for\n * chains like Solana where addresses are case-sensitive). When `false`\n * (default), addresses are lowercased (appropriate for EVM).\n * @returns Object containing cached results and tokens to fetch.\n */\nexport const splitCacheHits = (\n cache: { get: (key: string) => TokenScanCacheData | undefined },\n chainId: string,\n tokens: string[],\n caseSensitive = false,\n): {\n cachedResults: Record<string, TokenScanResult>;\n tokensToFetch: string[];\n} => {\n const cachedResults: Record<string, TokenScanResult> = {};\n const tokensToFetch: string[] = [];\n\n for (const address of tokens) {\n const normalizedAddress = caseSensitive ? address : address.toLowerCase();\n const key = buildCacheKey(chainId, normalizedAddress, caseSensitive);\n const hit = cache.get(key);\n if (hit) {\n cachedResults[normalizedAddress] = {\n result_type: hit.result_type,\n chain: chainId,\n address: normalizedAddress,\n };\n } else {\n tokensToFetch.push(normalizedAddress);\n }\n }\n\n return { cachedResults, tokensToFetch };\n};\n"]}
1
+ {"version":3,"file":"utils.cjs","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex } from '@noble/hashes/utils';\nimport { sha256 } from 'ethereum-cryptography/sha256';\n\nimport { deleteFromTrie, insertToTrie, deepCopyPathTrie } from './PathTrie.js';\nimport type { Hotlist, PhishingListState } from './PhishingController.js';\nimport { ListKeys, phishingListKeyNameMap } from './PhishingController.js';\nimport type {\n PhishingDetectorList,\n PhishingDetectorConfiguration,\n} from './PhishingDetector.js';\nimport {\n ADDRESS_SCAN_SUPPORTED_CHAINS,\n APPROVAL_SUPPORTED_CHAINS,\n DEFAULT_CHAIN_ID_TO_NAME,\n TOKEN_SCAN_SUPPORTED_CHAINS,\n} from './types.js';\nimport type {\n AddressScanSupportedChain,\n ApprovalSupportedChain,\n TokenScanCacheData,\n TokenScanResult,\n TokenScanSupportedChain,\n} from './types.js';\n\nconst DEFAULT_TOLERANCE = 3;\n\n/**\n * Fetches current epoch time in seconds.\n *\n * @returns the Date.now() time in seconds instead of miliseconds. backend files rely on timestamps in seconds since epoch.\n */\nexport const fetchTimeNow = (): number => Math.round(Date.now() / 1000);\n\n/**\n * Rounds a Unix timestamp down to the nearest minute.\n *\n * @param unixTimestamp - The Unix timestamp to be rounded.\n * @returns The rounded Unix timestamp.\n */\nexport function roundToNearestMinute(unixTimestamp: number): number {\n return Math.floor(unixTimestamp / 60) * 60;\n}\n\n/**\n * Split a string into two pieces, using the first period as the delimiter.\n *\n * @param stringToSplit - The string to split.\n * @returns An array of length two containing the beginning and end of the string.\n */\nconst splitStringByPeriod = <Start extends string, End extends string>(\n stringToSplit: `${Start}.${End}`,\n): [Start, End] => {\n const periodIndex = stringToSplit.indexOf('.');\n return [\n stringToSplit.slice(0, periodIndex) as Start,\n stringToSplit.slice(periodIndex + 1) as End,\n ];\n};\n\nexport const getHostnameAndPathComponents = (\n url: string,\n): { hostname: string; pathComponents: string[] } => {\n const urlWithProtocol = url.startsWith('http') ? url : `https://${url}`;\n try {\n const { hostname, pathname } = new URL(urlWithProtocol);\n return {\n hostname: hostname.toLowerCase(),\n pathComponents: pathname\n .split('/')\n .filter(Boolean)\n .map((component) => decodeURIComponent(component)),\n };\n } catch {\n return {\n hostname: '',\n pathComponents: [],\n };\n }\n};\n\n/**\n * Determines which diffs are applicable to the listState, then applies those diffs.\n *\n * @param listState - the stalelist or the existing liststate that diffs will be applied to.\n * @param hotlistDiffs - the diffs to apply to the listState if valid.\n * @param listKey - the key associated with the input/output phishing list state.\n * @param recentlyAddedC2Domains - list of hashed C2 domains to add to the local c2 domain blocklist\n * @param recentlyRemovedC2Domains - list of hashed C2 domains to remove from the local c2 domain blocklist\n * @returns the new list state\n */\nexport const applyDiffs = (\n listState: PhishingListState,\n hotlistDiffs: Hotlist,\n listKey: ListKeys,\n recentlyAddedC2Domains: string[] = [],\n recentlyRemovedC2Domains: string[] = [],\n): PhishingListState => {\n // filter to remove diffs that were added before the lastUpdate time.\n // filter to remove diffs that aren't applicable to the specified list (by listKey).\n const diffsToApply = hotlistDiffs.filter(\n ({ timestamp, targetList }) =>\n timestamp > listState.lastUpdated &&\n splitStringByPeriod(targetList)[0] === listKey,\n );\n\n // the reason behind using latestDiffTimestamp as the lastUpdated time\n // is so that we can benefit server-side from memoization due to end client's\n // `GET /v1/diffSince/:timestamp` requests lining up with\n // our periodic updates (which create diffs at specific timestamps).\n let latestDiffTimestamp = listState.lastUpdated;\n\n const listSets = {\n allowlist: new Set(listState.allowlist),\n blocklist: new Set(listState.blocklist),\n fuzzylist: new Set(listState.fuzzylist),\n c2DomainBlocklist: new Set(listState.c2DomainBlocklist),\n };\n\n // deep copy of blocklistPaths to avoid mutating the original\n const newBlocklistPaths = deepCopyPathTrie(listState.blocklistPaths);\n\n for (const { isRemoval, targetList, url, timestamp } of diffsToApply) {\n const targetListType = splitStringByPeriod(targetList)[1];\n if (timestamp > latestDiffTimestamp) {\n latestDiffTimestamp = timestamp;\n }\n\n if (isRemoval) {\n if (targetListType === 'blocklistPaths') {\n deleteFromTrie(url, newBlocklistPaths);\n } else {\n listSets[targetListType].delete(url);\n }\n continue;\n }\n\n if (targetListType === 'blocklistPaths') {\n insertToTrie(url, newBlocklistPaths);\n } else {\n listSets[targetListType].add(url);\n }\n }\n\n if (listKey === ListKeys.EthPhishingDetectConfig) {\n for (const hash of recentlyAddedC2Domains) {\n listSets.c2DomainBlocklist.add(hash);\n }\n for (const hash of recentlyRemovedC2Domains) {\n listSets.c2DomainBlocklist.delete(hash);\n }\n }\n\n return {\n c2DomainBlocklist: Array.from(listSets.c2DomainBlocklist),\n allowlist: Array.from(listSets.allowlist),\n blocklist: Array.from(listSets.blocklist),\n fuzzylist: Array.from(listSets.fuzzylist),\n blocklistPaths: newBlocklistPaths,\n version: listState.version,\n name: phishingListKeyNameMap[listKey],\n tolerance: listState.tolerance,\n lastUpdated: latestDiffTimestamp,\n };\n};\n\n/**\n * Validates the configuration object for the phishing detector.\n *\n * @param config - the configuration object to validate.\n * @throws an error if the configuration is invalid.\n */\nexport function validateConfig(\n config: unknown,\n): asserts config is PhishingListState {\n if (config === null || typeof config !== 'object') {\n throw new Error('Invalid config');\n }\n\n if ('tolerance' in config && !('fuzzylist' in config)) {\n throw new Error('Fuzzylist tolerance provided without fuzzylist');\n }\n\n if (\n 'name' in config &&\n (typeof config.name !== 'string' || config.name === '')\n ) {\n throw new Error(\"Invalid config parameter: 'name'\");\n }\n\n if (\n 'version' in config &&\n (!['number', 'string'].includes(typeof config.version) ||\n config.version === '')\n ) {\n throw new Error(\"Invalid config parameter: 'version'\");\n }\n}\n\n/**\n * Converts a domain string to a list of domain parts.\n *\n * @param domain - the domain string to convert.\n * @returns the list of domain parts.\n */\nexport const domainToParts = (domain: string) => {\n return domain.split('.').reverse();\n};\n\n/**\n * Converts a list of domain strings to a list of domain parts.\n *\n * @param list - the list of domain strings to convert.\n * @returns the list of domain parts.\n */\nexport const processDomainList = (list: string[]): string[][] => {\n return list.reduce<string[][]>((acc, domain) => {\n if (typeof domain !== 'string') {\n console.warn(`Invalid domain value in list: ${JSON.stringify(domain)}`);\n return acc;\n }\n acc.push(domainToParts(domain));\n return acc;\n }, []);\n};\n\n/**\n * Gets the default phishing detector configuration.\n *\n * @param override - the optional override for the configuration.\n * @param override.allowlist - the optional allowlist to override.\n * @param override.blocklist - the optional blocklist to override.\n * @param override.fuzzylist - the optional fuzzylist to override.\n * @param override.tolerance - the optional tolerance to override.\n * @param override.c2DomainBlocklist - the optional c2DomainBlocklist to override.\n * @returns the default phishing detector configuration.\n */\nexport const getDefaultPhishingDetectorConfig = ({\n allowlist = [],\n blocklist = [],\n c2DomainBlocklist = [],\n fuzzylist = [],\n tolerance = DEFAULT_TOLERANCE,\n}: {\n allowlist?: string[];\n blocklist?: string[];\n c2DomainBlocklist?: string[];\n fuzzylist?: string[];\n tolerance?: number;\n}): PhishingDetectorConfiguration => ({\n allowlist: processDomainList(allowlist),\n // We can assume that blocklist is already separated into hostname-only entries\n // and hostname+path entries so we do not need to separate it again.\n blocklist: processDomainList(blocklist),\n c2DomainBlocklist,\n fuzzylist: processDomainList(fuzzylist),\n tolerance,\n});\n\n/**\n * Processes the configurations for the phishing detector, filtering out any invalid configs.\n *\n * @param configs - The configurations to process.\n * @returns An array of processed and valid configurations.\n */\nexport const processConfigs = (\n configs: PhishingDetectorList[] = [],\n): PhishingDetectorConfiguration[] => {\n return configs\n .filter((config) => {\n try {\n validateConfig(config);\n return true;\n } catch (error) {\n console.error(error);\n return false;\n }\n })\n .map((config) => ({\n ...config,\n ...getDefaultPhishingDetectorConfig(config),\n }));\n};\n\n/**\n * Converts a list of domain parts to a domain string.\n *\n * @param domainParts - the list of domain parts.\n * @returns the domain string.\n */\nexport const domainPartsToDomain = (domainParts: string[]) => {\n return domainParts.slice().reverse().join('.');\n};\n\n/**\n * Converts a list of domain parts to a fuzzy form.\n *\n * @param domainParts - the list of domain parts.\n * @returns the fuzzy form of the domain.\n */\nexport const domainPartsToFuzzyForm = (domainParts: string[]) => {\n return domainParts.slice(1).reverse().join('.');\n};\n\n/**\n * Matches the target parts, ignoring extra subdomains on source.\n *\n * @param source - the source domain parts.\n * @param list - the list of domain parts to match against.\n * @returns the parts for the first found matching entry.\n */\nexport const matchPartsAgainstList = (source: string[], list: string[][]) => {\n return list.find((target) => {\n // target domain has more parts than source, fail\n if (target.length > source.length) {\n return false;\n }\n // source matches target or (is deeper subdomain)\n return target.every((part, index) => source[index] === part);\n });\n};\n\n/**\n * Generate the SHA-256 hash of a hostname.\n *\n * @param hostname - The hostname to hash.\n * @returns The SHA-256 hash of the hostname.\n */\nexport const sha256Hash = (hostname: string): string => {\n const hashBuffer = sha256(new TextEncoder().encode(hostname.toLowerCase()));\n return bytesToHex(hashBuffer);\n};\n\n/**\n * Extracts the hostname from a URL.\n *\n * @param url - The URL to extract the hostname from.\n * @returns The hostname extracted from the URL, or null if the URL is invalid.\n */\nexport const getHostnameFromUrl = (url: string): string | null => {\n let hostname;\n try {\n hostname = new URL(url).hostname;\n // above will not throw if 'http://.' is passed. in fact, any string with a dot will pass.\n if (!hostname || hostname.split('.').join('') === '') {\n return null;\n }\n } catch {\n return null;\n }\n return hostname;\n};\n\n/**\n * getHostnameFromWebUrl returns the hostname from a web URL.\n * It returns the hostname and a boolean indicating if the hostname is valid.\n *\n * @param url - The web URL to extract the hostname from.\n * @returns A tuple containing the extracted hostname and a boolean indicating if the hostname is valid.\n * @example\n * getHostnameFromWebUrl('https://example.com') // Returns: ['example.com', true]\n * getHostnameFromWebUrl('example.com') // Returns: ['', false]\n * getHostnameFromWebUrl('https://') // Returns: ['', false]\n * getHostnameFromWebUrl('') // Returns: ['', false]\n */\nexport const getHostnameFromWebUrl = (url: string): [string, boolean] => {\n if (\n !url.toLowerCase().startsWith('http://') &&\n !url.toLowerCase().startsWith('https://')\n ) {\n return ['', false];\n }\n\n const hostname = getHostnameFromUrl(url);\n return [hostname || '', Boolean(hostname)];\n};\n\n/**\n * Hosts where PDS single-URL scans include the URL path (shared gateways / hosts where many sites\n * share one origin). For all other hosts, only the hostname is sent.\n */\nexport const PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS = [\n 'ipfs.io',\n 'dweb.link',\n 'cf-ipfs.com',\n 'cloudflare-ipfs.com',\n 'irys.xyz',\n 'sites.google.com',\n] as const;\n\n/**\n * @param hostname - Lowercase normalization is applied for matching registered roots and subdomains.\n * @returns Whether {@link getPhishingDetectionScanUrlParam} appends pathname for this hostname.\n */\nexport function isPhishingDetectionPathBasedHostname(\n hostname: string,\n): boolean {\n const normalizedHost = hostname.toLowerCase();\n return PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS.some(\n (root) => normalizedHost === root || normalizedHost.endsWith(`.${root}`),\n );\n}\n\n/**\n * Builds the `url` query parameter for {@link PhishingController.scanUrl}. For hosts in\n * {@link PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS} (and their subdomains), the value is hostname\n * plus pathname, without protocol, query, or fragment. For all other hosts, only hostname is used.\n *\n * @param url - A web URL string (must use `http:` or `https:` — same rules as {@link getHostnameFromWebUrl}).\n * @returns A tuple of `[scanUrlParam, ok]` where `ok` is false when the URL is not a valid web URL.\n */\nexport const getPhishingDetectionScanUrlParam = (\n url: string,\n): [scanUrlParam: string, ok: boolean] => {\n const [hostname, ok] = getHostnameFromWebUrl(url);\n if (!ok) {\n return ['', false];\n }\n\n if (!isPhishingDetectionPathBasedHostname(hostname)) {\n return [hostname, true];\n }\n\n // `getHostnameFromWebUrl` already required a successful `new URL(url)` parse.\n const { pathname } = new URL(url);\n const pathSuffix = pathname === '/' ? '' : pathname;\n const scanUrlParam = pathSuffix ? `${hostname}${pathSuffix}` : hostname;\n\n return [scanUrlParam, true];\n};\n\nexport const getPathnameFromUrl = (url: string): string => {\n try {\n const { pathname } = new URL(url);\n return pathname;\n } catch {\n return '';\n }\n};\n\n/**\n * Generates all possible parent domains up to a specified limit.\n *\n * @param sourceParts - The list of domain parts in normal order (e.g., ['evil', 'domain', 'co', 'uk']).\n * @param limit - The maximum number of parent domains to generate (default is 5).\n * @returns An array of parent domains starting from the base TLD to the most specific subdomain.\n * @example\n * generateParentDomains(['evil', 'domain', 'co', 'uk'], 5)\n * // Returns: ['co.uk', 'domain.co.uk', 'evil.domain.co.uk']\n *\n * generateParentDomains(['uk'], 5)\n * // Returns: ['uk']\n *\n * generateParentDomains(['sub', 'example', 'com'], 5)\n * // Returns: ['example.com', 'sub.example.com']\n */\nexport const generateParentDomains = (\n sourceParts: string[],\n limit = 5,\n): string[] => {\n const domains: string[] = [];\n\n if (sourceParts.length === 0) {\n return domains;\n }\n\n if (sourceParts.length === 1) {\n // Single-segment hostname (e.g., 'uk')\n domains.push(sourceParts[0].toLowerCase());\n } else {\n // Start with the base domain or TLD (last two labels, e.g., 'co.uk' or 'example.com')\n const baseDomain = sourceParts.slice(-2).join('.');\n domains.push(baseDomain.toLowerCase());\n\n // Iteratively add one subdomain level at a time, up to the specified limit\n for (\n let i = sourceParts.length - 3;\n i >= 0 && domains.length < limit;\n i--\n ) {\n const domain = sourceParts.slice(i).join('.');\n domains.push(domain.toLowerCase());\n }\n }\n\n return domains;\n};\n\n/**\n * Builds a cache key for a token scan result.\n *\n * @param chainId - The chain ID.\n * @param address - The token address.\n * @param caseSensitive - When `true`, the address is kept as-is (for chains\n * like Solana where addresses are case-sensitive). When `false` (default),\n * the address is lowercased (appropriate for EVM).\n * @returns The cache key.\n */\nexport const buildCacheKey = (\n chainId: string,\n address: string,\n caseSensitive = false,\n) => {\n const normalizedAddress = caseSensitive ? address : address.toLowerCase();\n return `${chainId.toLowerCase()}:${normalizedAddress}`;\n};\n\n/**\n * Determines whether a chain name is supported for token approval scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isApprovalSupportedChain = (\n chain: string,\n): chain is ApprovalSupportedChain =>\n (APPROVAL_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Determines whether a chain name is supported for bulk token scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isTokenScanSupportedChain = (\n chain: string,\n): chain is TokenScanSupportedChain =>\n (TOKEN_SCAN_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Determines whether a chain name is supported for address scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isAddressScanSupportedChain = (\n chain: string,\n): chain is AddressScanSupportedChain =>\n (ADDRESS_SCAN_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Resolves the chain name from a chain ID.\n *\n * @param chainId - The chain ID.\n * @param mapping - The mapping of chain IDs to chain names.\n * @returns The chain name.\n */\nexport const resolveChainName = (\n chainId: string,\n mapping = DEFAULT_CHAIN_ID_TO_NAME,\n): string | null => {\n return mapping[chainId.toLowerCase() as keyof typeof mapping] ?? null;\n};\n\n/**\n * Resolves a chain ID to a Blockaid address-scan chain name, or `null` if\n * `scanAddress` would not call the Security Alerts API for this chain.\n *\n * @param chainId - Hex chain ID for EVM chains (e.g. `'0x1'`) or a chain\n * name for non-EVM chains (e.g. `'solana'`).\n * @returns The address-scan chain name, or `null` if unsupported.\n */\nexport const getAddressScanSupportedChain = (\n chainId: string,\n): AddressScanSupportedChain | null => {\n const chain = resolveChainName(chainId);\n if (!chain || !isAddressScanSupportedChain(chain)) {\n return null;\n }\n return chain;\n};\n\n/**\n * Determines whether `scanAddress` will call the Security Alerts API for\n * this chain, rather than immediately returning `ErrorResult`.\n *\n * Matches the gate inside `scanAddress`: the chain ID must resolve via\n * {@link resolveChainName}, and that name must be in\n * `ADDRESS_SCAN_SUPPORTED_CHAINS`.\n *\n * @param chainId - Hex chain ID for EVM chains (e.g. `'0x1'`) or a chain\n * name for non-EVM chains (e.g. `'solana'`).\n * @returns `true` if an address scan would hit the API.\n */\nexport const isAddressScanSupportedChainId = (chainId: string): boolean =>\n getAddressScanSupportedChain(chainId) !== null;\n\n/**\n * Split tokens into cached results and tokens that need to be fetched.\n *\n * @param cache - Cache-like object with get method.\n * @param cache.get - Method to retrieve cached data by key.\n * @param chainId - The chain ID.\n * @param tokens - Array of token addresses.\n * @param caseSensitive - When `true`, token addresses are kept as-is (for\n * chains like Solana where addresses are case-sensitive). When `false`\n * (default), addresses are lowercased (appropriate for EVM).\n * @returns Object containing cached results and tokens to fetch.\n */\nexport const splitCacheHits = (\n cache: { get: (key: string) => TokenScanCacheData | undefined },\n chainId: string,\n tokens: string[],\n caseSensitive = false,\n): {\n cachedResults: Record<string, TokenScanResult>;\n tokensToFetch: string[];\n} => {\n const cachedResults: Record<string, TokenScanResult> = {};\n const tokensToFetch: string[] = [];\n\n for (const address of tokens) {\n const normalizedAddress = caseSensitive ? address : address.toLowerCase();\n const key = buildCacheKey(chainId, normalizedAddress, caseSensitive);\n const hit = cache.get(key);\n if (hit) {\n cachedResults[normalizedAddress] = {\n result_type: hit.result_type,\n chain: chainId,\n address: normalizedAddress,\n };\n } else {\n tokensToFetch.push(normalizedAddress);\n }\n }\n\n return { cachedResults, tokensToFetch };\n};\n"]}
package/dist/utils.d.cts CHANGED
@@ -59,11 +59,13 @@ export declare const processDomainList: (list: string[]) => string[][];
59
59
  * @param override.blocklist - the optional blocklist to override.
60
60
  * @param override.fuzzylist - the optional fuzzylist to override.
61
61
  * @param override.tolerance - the optional tolerance to override.
62
+ * @param override.c2DomainBlocklist - the optional c2DomainBlocklist to override.
62
63
  * @returns the default phishing detector configuration.
63
64
  */
64
- export declare const getDefaultPhishingDetectorConfig: ({ allowlist, blocklist, fuzzylist, tolerance, }: {
65
+ export declare const getDefaultPhishingDetectorConfig: ({ allowlist, blocklist, c2DomainBlocklist, fuzzylist, tolerance, }: {
65
66
  allowlist?: string[] | undefined;
66
67
  blocklist?: string[] | undefined;
68
+ c2DomainBlocklist?: string[] | undefined;
67
69
  fuzzylist?: string[] | undefined;
68
70
  tolerance?: number | undefined;
69
71
  }) => PhishingDetectorConfiguration;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.cts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.d.cts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/dist/utils.d.mts CHANGED
@@ -59,11 +59,13 @@ export declare const processDomainList: (list: string[]) => string[][];
59
59
  * @param override.blocklist - the optional blocklist to override.
60
60
  * @param override.fuzzylist - the optional fuzzylist to override.
61
61
  * @param override.tolerance - the optional tolerance to override.
62
+ * @param override.c2DomainBlocklist - the optional c2DomainBlocklist to override.
62
63
  * @returns the default phishing detector configuration.
63
64
  */
64
- export declare const getDefaultPhishingDetectorConfig: ({ allowlist, blocklist, fuzzylist, tolerance, }: {
65
+ export declare const getDefaultPhishingDetectorConfig: ({ allowlist, blocklist, c2DomainBlocklist, fuzzylist, tolerance, }: {
65
66
  allowlist?: string[] | undefined;
66
67
  blocklist?: string[] | undefined;
68
+ c2DomainBlocklist?: string[] | undefined;
67
69
  fuzzylist?: string[] | undefined;
68
70
  tolerance?: number | undefined;
69
71
  }) => PhishingDetectorConfiguration;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.mts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.d.mts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/dist/utils.mjs CHANGED
@@ -176,18 +176,18 @@ export const processDomainList = (list) => {
176
176
  * @param override.blocklist - the optional blocklist to override.
177
177
  * @param override.fuzzylist - the optional fuzzylist to override.
178
178
  * @param override.tolerance - the optional tolerance to override.
179
+ * @param override.c2DomainBlocklist - the optional c2DomainBlocklist to override.
179
180
  * @returns the default phishing detector configuration.
180
181
  */
181
- export const getDefaultPhishingDetectorConfig = ({ allowlist = [], blocklist = [], fuzzylist = [], tolerance = DEFAULT_TOLERANCE, }) => {
182
- return {
183
- allowlist: processDomainList(allowlist),
184
- // We can assume that blocklist is already separated into hostname-only entries
185
- // and hostname+path entries so we do not need to separate it again.
186
- blocklist: processDomainList(blocklist),
187
- fuzzylist: processDomainList(fuzzylist),
188
- tolerance,
189
- };
190
- };
182
+ export const getDefaultPhishingDetectorConfig = ({ allowlist = [], blocklist = [], c2DomainBlocklist = [], fuzzylist = [], tolerance = DEFAULT_TOLERANCE, }) => ({
183
+ allowlist: processDomainList(allowlist),
184
+ // We can assume that blocklist is already separated into hostname-only entries
185
+ // and hostname+path entries so we do not need to separate it again.
186
+ blocklist: processDomainList(blocklist),
187
+ c2DomainBlocklist,
188
+ fuzzylist: processDomainList(fuzzylist),
189
+ tolerance,
190
+ });
191
191
  /**
192
192
  * Processes the configurations for the phishing detector, filtering out any invalid configs.
193
193
  *
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex } from '@noble/hashes/utils';\nimport { sha256 } from 'ethereum-cryptography/sha256';\n\nimport { deleteFromTrie, insertToTrie, deepCopyPathTrie } from './PathTrie.js';\nimport type { Hotlist, PhishingListState } from './PhishingController.js';\nimport { ListKeys, phishingListKeyNameMap } from './PhishingController.js';\nimport type {\n PhishingDetectorList,\n PhishingDetectorConfiguration,\n} from './PhishingDetector.js';\nimport {\n ADDRESS_SCAN_SUPPORTED_CHAINS,\n APPROVAL_SUPPORTED_CHAINS,\n DEFAULT_CHAIN_ID_TO_NAME,\n TOKEN_SCAN_SUPPORTED_CHAINS,\n} from './types.js';\nimport type {\n AddressScanSupportedChain,\n ApprovalSupportedChain,\n TokenScanCacheData,\n TokenScanResult,\n TokenScanSupportedChain,\n} from './types.js';\n\nconst DEFAULT_TOLERANCE = 3;\n\n/**\n * Fetches current epoch time in seconds.\n *\n * @returns the Date.now() time in seconds instead of miliseconds. backend files rely on timestamps in seconds since epoch.\n */\nexport const fetchTimeNow = (): number => Math.round(Date.now() / 1000);\n\n/**\n * Rounds a Unix timestamp down to the nearest minute.\n *\n * @param unixTimestamp - The Unix timestamp to be rounded.\n * @returns The rounded Unix timestamp.\n */\nexport function roundToNearestMinute(unixTimestamp: number): number {\n return Math.floor(unixTimestamp / 60) * 60;\n}\n\n/**\n * Split a string into two pieces, using the first period as the delimiter.\n *\n * @param stringToSplit - The string to split.\n * @returns An array of length two containing the beginning and end of the string.\n */\nconst splitStringByPeriod = <Start extends string, End extends string>(\n stringToSplit: `${Start}.${End}`,\n): [Start, End] => {\n const periodIndex = stringToSplit.indexOf('.');\n return [\n stringToSplit.slice(0, periodIndex) as Start,\n stringToSplit.slice(periodIndex + 1) as End,\n ];\n};\n\nexport const getHostnameAndPathComponents = (\n url: string,\n): { hostname: string; pathComponents: string[] } => {\n const urlWithProtocol = url.startsWith('http') ? url : `https://${url}`;\n try {\n const { hostname, pathname } = new URL(urlWithProtocol);\n return {\n hostname: hostname.toLowerCase(),\n pathComponents: pathname\n .split('/')\n .filter(Boolean)\n .map((component) => decodeURIComponent(component)),\n };\n } catch {\n return {\n hostname: '',\n pathComponents: [],\n };\n }\n};\n\n/**\n * Determines which diffs are applicable to the listState, then applies those diffs.\n *\n * @param listState - the stalelist or the existing liststate that diffs will be applied to.\n * @param hotlistDiffs - the diffs to apply to the listState if valid.\n * @param listKey - the key associated with the input/output phishing list state.\n * @param recentlyAddedC2Domains - list of hashed C2 domains to add to the local c2 domain blocklist\n * @param recentlyRemovedC2Domains - list of hashed C2 domains to remove from the local c2 domain blocklist\n * @returns the new list state\n */\nexport const applyDiffs = (\n listState: PhishingListState,\n hotlistDiffs: Hotlist,\n listKey: ListKeys,\n recentlyAddedC2Domains: string[] = [],\n recentlyRemovedC2Domains: string[] = [],\n): PhishingListState => {\n // filter to remove diffs that were added before the lastUpdate time.\n // filter to remove diffs that aren't applicable to the specified list (by listKey).\n const diffsToApply = hotlistDiffs.filter(\n ({ timestamp, targetList }) =>\n timestamp > listState.lastUpdated &&\n splitStringByPeriod(targetList)[0] === listKey,\n );\n\n // the reason behind using latestDiffTimestamp as the lastUpdated time\n // is so that we can benefit server-side from memoization due to end client's\n // `GET /v1/diffSince/:timestamp` requests lining up with\n // our periodic updates (which create diffs at specific timestamps).\n let latestDiffTimestamp = listState.lastUpdated;\n\n const listSets = {\n allowlist: new Set(listState.allowlist),\n blocklist: new Set(listState.blocklist),\n fuzzylist: new Set(listState.fuzzylist),\n c2DomainBlocklist: new Set(listState.c2DomainBlocklist),\n };\n\n // deep copy of blocklistPaths to avoid mutating the original\n const newBlocklistPaths = deepCopyPathTrie(listState.blocklistPaths);\n\n for (const { isRemoval, targetList, url, timestamp } of diffsToApply) {\n const targetListType = splitStringByPeriod(targetList)[1];\n if (timestamp > latestDiffTimestamp) {\n latestDiffTimestamp = timestamp;\n }\n\n if (isRemoval) {\n if (targetListType === 'blocklistPaths') {\n deleteFromTrie(url, newBlocklistPaths);\n } else {\n listSets[targetListType].delete(url);\n }\n continue;\n }\n\n if (targetListType === 'blocklistPaths') {\n insertToTrie(url, newBlocklistPaths);\n } else {\n listSets[targetListType].add(url);\n }\n }\n\n if (listKey === ListKeys.EthPhishingDetectConfig) {\n for (const hash of recentlyAddedC2Domains) {\n listSets.c2DomainBlocklist.add(hash);\n }\n for (const hash of recentlyRemovedC2Domains) {\n listSets.c2DomainBlocklist.delete(hash);\n }\n }\n\n return {\n c2DomainBlocklist: Array.from(listSets.c2DomainBlocklist),\n allowlist: Array.from(listSets.allowlist),\n blocklist: Array.from(listSets.blocklist),\n fuzzylist: Array.from(listSets.fuzzylist),\n blocklistPaths: newBlocklistPaths,\n version: listState.version,\n name: phishingListKeyNameMap[listKey],\n tolerance: listState.tolerance,\n lastUpdated: latestDiffTimestamp,\n };\n};\n\n/**\n * Validates the configuration object for the phishing detector.\n *\n * @param config - the configuration object to validate.\n * @throws an error if the configuration is invalid.\n */\nexport function validateConfig(\n config: unknown,\n): asserts config is PhishingListState {\n if (config === null || typeof config !== 'object') {\n throw new Error('Invalid config');\n }\n\n if ('tolerance' in config && !('fuzzylist' in config)) {\n throw new Error('Fuzzylist tolerance provided without fuzzylist');\n }\n\n if (\n 'name' in config &&\n (typeof config.name !== 'string' || config.name === '')\n ) {\n throw new Error(\"Invalid config parameter: 'name'\");\n }\n\n if (\n 'version' in config &&\n (!['number', 'string'].includes(typeof config.version) ||\n config.version === '')\n ) {\n throw new Error(\"Invalid config parameter: 'version'\");\n }\n}\n\n/**\n * Converts a domain string to a list of domain parts.\n *\n * @param domain - the domain string to convert.\n * @returns the list of domain parts.\n */\nexport const domainToParts = (domain: string) => {\n return domain.split('.').reverse();\n};\n\n/**\n * Converts a list of domain strings to a list of domain parts.\n *\n * @param list - the list of domain strings to convert.\n * @returns the list of domain parts.\n */\nexport const processDomainList = (list: string[]): string[][] => {\n return list.reduce<string[][]>((acc, domain) => {\n if (typeof domain !== 'string') {\n console.warn(`Invalid domain value in list: ${JSON.stringify(domain)}`);\n return acc;\n }\n acc.push(domainToParts(domain));\n return acc;\n }, []);\n};\n\n/**\n * Gets the default phishing detector configuration.\n *\n * @param override - the optional override for the configuration.\n * @param override.allowlist - the optional allowlist to override.\n * @param override.blocklist - the optional blocklist to override.\n * @param override.fuzzylist - the optional fuzzylist to override.\n * @param override.tolerance - the optional tolerance to override.\n * @returns the default phishing detector configuration.\n */\nexport const getDefaultPhishingDetectorConfig = ({\n allowlist = [],\n blocklist = [],\n fuzzylist = [],\n tolerance = DEFAULT_TOLERANCE,\n}: {\n allowlist?: string[];\n blocklist?: string[];\n fuzzylist?: string[];\n tolerance?: number;\n}): PhishingDetectorConfiguration => {\n return {\n allowlist: processDomainList(allowlist),\n // We can assume that blocklist is already separated into hostname-only entries\n // and hostname+path entries so we do not need to separate it again.\n blocklist: processDomainList(blocklist),\n fuzzylist: processDomainList(fuzzylist),\n tolerance,\n };\n};\n\n/**\n * Processes the configurations for the phishing detector, filtering out any invalid configs.\n *\n * @param configs - The configurations to process.\n * @returns An array of processed and valid configurations.\n */\nexport const processConfigs = (\n configs: PhishingDetectorList[] = [],\n): PhishingDetectorConfiguration[] => {\n return configs\n .filter((config) => {\n try {\n validateConfig(config);\n return true;\n } catch (error) {\n console.error(error);\n return false;\n }\n })\n .map((config) => ({\n ...config,\n ...getDefaultPhishingDetectorConfig(config),\n }));\n};\n\n/**\n * Converts a list of domain parts to a domain string.\n *\n * @param domainParts - the list of domain parts.\n * @returns the domain string.\n */\nexport const domainPartsToDomain = (domainParts: string[]) => {\n return domainParts.slice().reverse().join('.');\n};\n\n/**\n * Converts a list of domain parts to a fuzzy form.\n *\n * @param domainParts - the list of domain parts.\n * @returns the fuzzy form of the domain.\n */\nexport const domainPartsToFuzzyForm = (domainParts: string[]) => {\n return domainParts.slice(1).reverse().join('.');\n};\n\n/**\n * Matches the target parts, ignoring extra subdomains on source.\n *\n * @param source - the source domain parts.\n * @param list - the list of domain parts to match against.\n * @returns the parts for the first found matching entry.\n */\nexport const matchPartsAgainstList = (source: string[], list: string[][]) => {\n return list.find((target) => {\n // target domain has more parts than source, fail\n if (target.length > source.length) {\n return false;\n }\n // source matches target or (is deeper subdomain)\n return target.every((part, index) => source[index] === part);\n });\n};\n\n/**\n * Generate the SHA-256 hash of a hostname.\n *\n * @param hostname - The hostname to hash.\n * @returns The SHA-256 hash of the hostname.\n */\nexport const sha256Hash = (hostname: string): string => {\n const hashBuffer = sha256(new TextEncoder().encode(hostname.toLowerCase()));\n return bytesToHex(hashBuffer);\n};\n\n/**\n * Extracts the hostname from a URL.\n *\n * @param url - The URL to extract the hostname from.\n * @returns The hostname extracted from the URL, or null if the URL is invalid.\n */\nexport const getHostnameFromUrl = (url: string): string | null => {\n let hostname;\n try {\n hostname = new URL(url).hostname;\n // above will not throw if 'http://.' is passed. in fact, any string with a dot will pass.\n if (!hostname || hostname.split('.').join('') === '') {\n return null;\n }\n } catch {\n return null;\n }\n return hostname;\n};\n\n/**\n * getHostnameFromWebUrl returns the hostname from a web URL.\n * It returns the hostname and a boolean indicating if the hostname is valid.\n *\n * @param url - The web URL to extract the hostname from.\n * @returns A tuple containing the extracted hostname and a boolean indicating if the hostname is valid.\n * @example\n * getHostnameFromWebUrl('https://example.com') // Returns: ['example.com', true]\n * getHostnameFromWebUrl('example.com') // Returns: ['', false]\n * getHostnameFromWebUrl('https://') // Returns: ['', false]\n * getHostnameFromWebUrl('') // Returns: ['', false]\n */\nexport const getHostnameFromWebUrl = (url: string): [string, boolean] => {\n if (\n !url.toLowerCase().startsWith('http://') &&\n !url.toLowerCase().startsWith('https://')\n ) {\n return ['', false];\n }\n\n const hostname = getHostnameFromUrl(url);\n return [hostname || '', Boolean(hostname)];\n};\n\n/**\n * Hosts where PDS single-URL scans include the URL path (shared gateways / hosts where many sites\n * share one origin). For all other hosts, only the hostname is sent.\n */\nexport const PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS = [\n 'ipfs.io',\n 'dweb.link',\n 'cf-ipfs.com',\n 'cloudflare-ipfs.com',\n 'irys.xyz',\n 'sites.google.com',\n] as const;\n\n/**\n * @param hostname - Lowercase normalization is applied for matching registered roots and subdomains.\n * @returns Whether {@link getPhishingDetectionScanUrlParam} appends pathname for this hostname.\n */\nexport function isPhishingDetectionPathBasedHostname(\n hostname: string,\n): boolean {\n const normalizedHost = hostname.toLowerCase();\n return PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS.some(\n (root) => normalizedHost === root || normalizedHost.endsWith(`.${root}`),\n );\n}\n\n/**\n * Builds the `url` query parameter for {@link PhishingController.scanUrl}. For hosts in\n * {@link PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS} (and their subdomains), the value is hostname\n * plus pathname, without protocol, query, or fragment. For all other hosts, only hostname is used.\n *\n * @param url - A web URL string (must use `http:` or `https:` — same rules as {@link getHostnameFromWebUrl}).\n * @returns A tuple of `[scanUrlParam, ok]` where `ok` is false when the URL is not a valid web URL.\n */\nexport const getPhishingDetectionScanUrlParam = (\n url: string,\n): [scanUrlParam: string, ok: boolean] => {\n const [hostname, ok] = getHostnameFromWebUrl(url);\n if (!ok) {\n return ['', false];\n }\n\n if (!isPhishingDetectionPathBasedHostname(hostname)) {\n return [hostname, true];\n }\n\n // `getHostnameFromWebUrl` already required a successful `new URL(url)` parse.\n const { pathname } = new URL(url);\n const pathSuffix = pathname === '/' ? '' : pathname;\n const scanUrlParam = pathSuffix ? `${hostname}${pathSuffix}` : hostname;\n\n return [scanUrlParam, true];\n};\n\nexport const getPathnameFromUrl = (url: string): string => {\n try {\n const { pathname } = new URL(url);\n return pathname;\n } catch {\n return '';\n }\n};\n\n/**\n * Generates all possible parent domains up to a specified limit.\n *\n * @param sourceParts - The list of domain parts in normal order (e.g., ['evil', 'domain', 'co', 'uk']).\n * @param limit - The maximum number of parent domains to generate (default is 5).\n * @returns An array of parent domains starting from the base TLD to the most specific subdomain.\n * @example\n * generateParentDomains(['evil', 'domain', 'co', 'uk'], 5)\n * // Returns: ['co.uk', 'domain.co.uk', 'evil.domain.co.uk']\n *\n * generateParentDomains(['uk'], 5)\n * // Returns: ['uk']\n *\n * generateParentDomains(['sub', 'example', 'com'], 5)\n * // Returns: ['example.com', 'sub.example.com']\n */\nexport const generateParentDomains = (\n sourceParts: string[],\n limit = 5,\n): string[] => {\n const domains: string[] = [];\n\n if (sourceParts.length === 0) {\n return domains;\n }\n\n if (sourceParts.length === 1) {\n // Single-segment hostname (e.g., 'uk')\n domains.push(sourceParts[0].toLowerCase());\n } else {\n // Start with the base domain or TLD (last two labels, e.g., 'co.uk' or 'example.com')\n const baseDomain = sourceParts.slice(-2).join('.');\n domains.push(baseDomain.toLowerCase());\n\n // Iteratively add one subdomain level at a time, up to the specified limit\n for (\n let i = sourceParts.length - 3;\n i >= 0 && domains.length < limit;\n i--\n ) {\n const domain = sourceParts.slice(i).join('.');\n domains.push(domain.toLowerCase());\n }\n }\n\n return domains;\n};\n\n/**\n * Builds a cache key for a token scan result.\n *\n * @param chainId - The chain ID.\n * @param address - The token address.\n * @param caseSensitive - When `true`, the address is kept as-is (for chains\n * like Solana where addresses are case-sensitive). When `false` (default),\n * the address is lowercased (appropriate for EVM).\n * @returns The cache key.\n */\nexport const buildCacheKey = (\n chainId: string,\n address: string,\n caseSensitive = false,\n) => {\n const normalizedAddress = caseSensitive ? address : address.toLowerCase();\n return `${chainId.toLowerCase()}:${normalizedAddress}`;\n};\n\n/**\n * Determines whether a chain name is supported for token approval scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isApprovalSupportedChain = (\n chain: string,\n): chain is ApprovalSupportedChain =>\n (APPROVAL_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Determines whether a chain name is supported for bulk token scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isTokenScanSupportedChain = (\n chain: string,\n): chain is TokenScanSupportedChain =>\n (TOKEN_SCAN_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Determines whether a chain name is supported for address scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isAddressScanSupportedChain = (\n chain: string,\n): chain is AddressScanSupportedChain =>\n (ADDRESS_SCAN_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Resolves the chain name from a chain ID.\n *\n * @param chainId - The chain ID.\n * @param mapping - The mapping of chain IDs to chain names.\n * @returns The chain name.\n */\nexport const resolveChainName = (\n chainId: string,\n mapping = DEFAULT_CHAIN_ID_TO_NAME,\n): string | null => {\n return mapping[chainId.toLowerCase() as keyof typeof mapping] ?? null;\n};\n\n/**\n * Resolves a chain ID to a Blockaid address-scan chain name, or `null` if\n * `scanAddress` would not call the Security Alerts API for this chain.\n *\n * @param chainId - Hex chain ID for EVM chains (e.g. `'0x1'`) or a chain\n * name for non-EVM chains (e.g. `'solana'`).\n * @returns The address-scan chain name, or `null` if unsupported.\n */\nexport const getAddressScanSupportedChain = (\n chainId: string,\n): AddressScanSupportedChain | null => {\n const chain = resolveChainName(chainId);\n if (!chain || !isAddressScanSupportedChain(chain)) {\n return null;\n }\n return chain;\n};\n\n/**\n * Determines whether `scanAddress` will call the Security Alerts API for\n * this chain, rather than immediately returning `ErrorResult`.\n *\n * Matches the gate inside `scanAddress`: the chain ID must resolve via\n * {@link resolveChainName}, and that name must be in\n * `ADDRESS_SCAN_SUPPORTED_CHAINS`.\n *\n * @param chainId - Hex chain ID for EVM chains (e.g. `'0x1'`) or a chain\n * name for non-EVM chains (e.g. `'solana'`).\n * @returns `true` if an address scan would hit the API.\n */\nexport const isAddressScanSupportedChainId = (chainId: string): boolean =>\n getAddressScanSupportedChain(chainId) !== null;\n\n/**\n * Split tokens into cached results and tokens that need to be fetched.\n *\n * @param cache - Cache-like object with get method.\n * @param cache.get - Method to retrieve cached data by key.\n * @param chainId - The chain ID.\n * @param tokens - Array of token addresses.\n * @param caseSensitive - When `true`, token addresses are kept as-is (for\n * chains like Solana where addresses are case-sensitive). When `false`\n * (default), addresses are lowercased (appropriate for EVM).\n * @returns Object containing cached results and tokens to fetch.\n */\nexport const splitCacheHits = (\n cache: { get: (key: string) => TokenScanCacheData | undefined },\n chainId: string,\n tokens: string[],\n caseSensitive = false,\n): {\n cachedResults: Record<string, TokenScanResult>;\n tokensToFetch: string[];\n} => {\n const cachedResults: Record<string, TokenScanResult> = {};\n const tokensToFetch: string[] = [];\n\n for (const address of tokens) {\n const normalizedAddress = caseSensitive ? address : address.toLowerCase();\n const key = buildCacheKey(chainId, normalizedAddress, caseSensitive);\n const hit = cache.get(key);\n if (hit) {\n cachedResults[normalizedAddress] = {\n result_type: hit.result_type,\n chain: chainId,\n address: normalizedAddress,\n };\n } else {\n tokensToFetch.push(normalizedAddress);\n }\n }\n\n return { cachedResults, tokensToFetch };\n};\n"]}
1
+ {"version":3,"file":"utils.mjs","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex } from '@noble/hashes/utils';\nimport { sha256 } from 'ethereum-cryptography/sha256';\n\nimport { deleteFromTrie, insertToTrie, deepCopyPathTrie } from './PathTrie.js';\nimport type { Hotlist, PhishingListState } from './PhishingController.js';\nimport { ListKeys, phishingListKeyNameMap } from './PhishingController.js';\nimport type {\n PhishingDetectorList,\n PhishingDetectorConfiguration,\n} from './PhishingDetector.js';\nimport {\n ADDRESS_SCAN_SUPPORTED_CHAINS,\n APPROVAL_SUPPORTED_CHAINS,\n DEFAULT_CHAIN_ID_TO_NAME,\n TOKEN_SCAN_SUPPORTED_CHAINS,\n} from './types.js';\nimport type {\n AddressScanSupportedChain,\n ApprovalSupportedChain,\n TokenScanCacheData,\n TokenScanResult,\n TokenScanSupportedChain,\n} from './types.js';\n\nconst DEFAULT_TOLERANCE = 3;\n\n/**\n * Fetches current epoch time in seconds.\n *\n * @returns the Date.now() time in seconds instead of miliseconds. backend files rely on timestamps in seconds since epoch.\n */\nexport const fetchTimeNow = (): number => Math.round(Date.now() / 1000);\n\n/**\n * Rounds a Unix timestamp down to the nearest minute.\n *\n * @param unixTimestamp - The Unix timestamp to be rounded.\n * @returns The rounded Unix timestamp.\n */\nexport function roundToNearestMinute(unixTimestamp: number): number {\n return Math.floor(unixTimestamp / 60) * 60;\n}\n\n/**\n * Split a string into two pieces, using the first period as the delimiter.\n *\n * @param stringToSplit - The string to split.\n * @returns An array of length two containing the beginning and end of the string.\n */\nconst splitStringByPeriod = <Start extends string, End extends string>(\n stringToSplit: `${Start}.${End}`,\n): [Start, End] => {\n const periodIndex = stringToSplit.indexOf('.');\n return [\n stringToSplit.slice(0, periodIndex) as Start,\n stringToSplit.slice(periodIndex + 1) as End,\n ];\n};\n\nexport const getHostnameAndPathComponents = (\n url: string,\n): { hostname: string; pathComponents: string[] } => {\n const urlWithProtocol = url.startsWith('http') ? url : `https://${url}`;\n try {\n const { hostname, pathname } = new URL(urlWithProtocol);\n return {\n hostname: hostname.toLowerCase(),\n pathComponents: pathname\n .split('/')\n .filter(Boolean)\n .map((component) => decodeURIComponent(component)),\n };\n } catch {\n return {\n hostname: '',\n pathComponents: [],\n };\n }\n};\n\n/**\n * Determines which diffs are applicable to the listState, then applies those diffs.\n *\n * @param listState - the stalelist or the existing liststate that diffs will be applied to.\n * @param hotlistDiffs - the diffs to apply to the listState if valid.\n * @param listKey - the key associated with the input/output phishing list state.\n * @param recentlyAddedC2Domains - list of hashed C2 domains to add to the local c2 domain blocklist\n * @param recentlyRemovedC2Domains - list of hashed C2 domains to remove from the local c2 domain blocklist\n * @returns the new list state\n */\nexport const applyDiffs = (\n listState: PhishingListState,\n hotlistDiffs: Hotlist,\n listKey: ListKeys,\n recentlyAddedC2Domains: string[] = [],\n recentlyRemovedC2Domains: string[] = [],\n): PhishingListState => {\n // filter to remove diffs that were added before the lastUpdate time.\n // filter to remove diffs that aren't applicable to the specified list (by listKey).\n const diffsToApply = hotlistDiffs.filter(\n ({ timestamp, targetList }) =>\n timestamp > listState.lastUpdated &&\n splitStringByPeriod(targetList)[0] === listKey,\n );\n\n // the reason behind using latestDiffTimestamp as the lastUpdated time\n // is so that we can benefit server-side from memoization due to end client's\n // `GET /v1/diffSince/:timestamp` requests lining up with\n // our periodic updates (which create diffs at specific timestamps).\n let latestDiffTimestamp = listState.lastUpdated;\n\n const listSets = {\n allowlist: new Set(listState.allowlist),\n blocklist: new Set(listState.blocklist),\n fuzzylist: new Set(listState.fuzzylist),\n c2DomainBlocklist: new Set(listState.c2DomainBlocklist),\n };\n\n // deep copy of blocklistPaths to avoid mutating the original\n const newBlocklistPaths = deepCopyPathTrie(listState.blocklistPaths);\n\n for (const { isRemoval, targetList, url, timestamp } of diffsToApply) {\n const targetListType = splitStringByPeriod(targetList)[1];\n if (timestamp > latestDiffTimestamp) {\n latestDiffTimestamp = timestamp;\n }\n\n if (isRemoval) {\n if (targetListType === 'blocklistPaths') {\n deleteFromTrie(url, newBlocklistPaths);\n } else {\n listSets[targetListType].delete(url);\n }\n continue;\n }\n\n if (targetListType === 'blocklistPaths') {\n insertToTrie(url, newBlocklistPaths);\n } else {\n listSets[targetListType].add(url);\n }\n }\n\n if (listKey === ListKeys.EthPhishingDetectConfig) {\n for (const hash of recentlyAddedC2Domains) {\n listSets.c2DomainBlocklist.add(hash);\n }\n for (const hash of recentlyRemovedC2Domains) {\n listSets.c2DomainBlocklist.delete(hash);\n }\n }\n\n return {\n c2DomainBlocklist: Array.from(listSets.c2DomainBlocklist),\n allowlist: Array.from(listSets.allowlist),\n blocklist: Array.from(listSets.blocklist),\n fuzzylist: Array.from(listSets.fuzzylist),\n blocklistPaths: newBlocklistPaths,\n version: listState.version,\n name: phishingListKeyNameMap[listKey],\n tolerance: listState.tolerance,\n lastUpdated: latestDiffTimestamp,\n };\n};\n\n/**\n * Validates the configuration object for the phishing detector.\n *\n * @param config - the configuration object to validate.\n * @throws an error if the configuration is invalid.\n */\nexport function validateConfig(\n config: unknown,\n): asserts config is PhishingListState {\n if (config === null || typeof config !== 'object') {\n throw new Error('Invalid config');\n }\n\n if ('tolerance' in config && !('fuzzylist' in config)) {\n throw new Error('Fuzzylist tolerance provided without fuzzylist');\n }\n\n if (\n 'name' in config &&\n (typeof config.name !== 'string' || config.name === '')\n ) {\n throw new Error(\"Invalid config parameter: 'name'\");\n }\n\n if (\n 'version' in config &&\n (!['number', 'string'].includes(typeof config.version) ||\n config.version === '')\n ) {\n throw new Error(\"Invalid config parameter: 'version'\");\n }\n}\n\n/**\n * Converts a domain string to a list of domain parts.\n *\n * @param domain - the domain string to convert.\n * @returns the list of domain parts.\n */\nexport const domainToParts = (domain: string) => {\n return domain.split('.').reverse();\n};\n\n/**\n * Converts a list of domain strings to a list of domain parts.\n *\n * @param list - the list of domain strings to convert.\n * @returns the list of domain parts.\n */\nexport const processDomainList = (list: string[]): string[][] => {\n return list.reduce<string[][]>((acc, domain) => {\n if (typeof domain !== 'string') {\n console.warn(`Invalid domain value in list: ${JSON.stringify(domain)}`);\n return acc;\n }\n acc.push(domainToParts(domain));\n return acc;\n }, []);\n};\n\n/**\n * Gets the default phishing detector configuration.\n *\n * @param override - the optional override for the configuration.\n * @param override.allowlist - the optional allowlist to override.\n * @param override.blocklist - the optional blocklist to override.\n * @param override.fuzzylist - the optional fuzzylist to override.\n * @param override.tolerance - the optional tolerance to override.\n * @param override.c2DomainBlocklist - the optional c2DomainBlocklist to override.\n * @returns the default phishing detector configuration.\n */\nexport const getDefaultPhishingDetectorConfig = ({\n allowlist = [],\n blocklist = [],\n c2DomainBlocklist = [],\n fuzzylist = [],\n tolerance = DEFAULT_TOLERANCE,\n}: {\n allowlist?: string[];\n blocklist?: string[];\n c2DomainBlocklist?: string[];\n fuzzylist?: string[];\n tolerance?: number;\n}): PhishingDetectorConfiguration => ({\n allowlist: processDomainList(allowlist),\n // We can assume that blocklist is already separated into hostname-only entries\n // and hostname+path entries so we do not need to separate it again.\n blocklist: processDomainList(blocklist),\n c2DomainBlocklist,\n fuzzylist: processDomainList(fuzzylist),\n tolerance,\n});\n\n/**\n * Processes the configurations for the phishing detector, filtering out any invalid configs.\n *\n * @param configs - The configurations to process.\n * @returns An array of processed and valid configurations.\n */\nexport const processConfigs = (\n configs: PhishingDetectorList[] = [],\n): PhishingDetectorConfiguration[] => {\n return configs\n .filter((config) => {\n try {\n validateConfig(config);\n return true;\n } catch (error) {\n console.error(error);\n return false;\n }\n })\n .map((config) => ({\n ...config,\n ...getDefaultPhishingDetectorConfig(config),\n }));\n};\n\n/**\n * Converts a list of domain parts to a domain string.\n *\n * @param domainParts - the list of domain parts.\n * @returns the domain string.\n */\nexport const domainPartsToDomain = (domainParts: string[]) => {\n return domainParts.slice().reverse().join('.');\n};\n\n/**\n * Converts a list of domain parts to a fuzzy form.\n *\n * @param domainParts - the list of domain parts.\n * @returns the fuzzy form of the domain.\n */\nexport const domainPartsToFuzzyForm = (domainParts: string[]) => {\n return domainParts.slice(1).reverse().join('.');\n};\n\n/**\n * Matches the target parts, ignoring extra subdomains on source.\n *\n * @param source - the source domain parts.\n * @param list - the list of domain parts to match against.\n * @returns the parts for the first found matching entry.\n */\nexport const matchPartsAgainstList = (source: string[], list: string[][]) => {\n return list.find((target) => {\n // target domain has more parts than source, fail\n if (target.length > source.length) {\n return false;\n }\n // source matches target or (is deeper subdomain)\n return target.every((part, index) => source[index] === part);\n });\n};\n\n/**\n * Generate the SHA-256 hash of a hostname.\n *\n * @param hostname - The hostname to hash.\n * @returns The SHA-256 hash of the hostname.\n */\nexport const sha256Hash = (hostname: string): string => {\n const hashBuffer = sha256(new TextEncoder().encode(hostname.toLowerCase()));\n return bytesToHex(hashBuffer);\n};\n\n/**\n * Extracts the hostname from a URL.\n *\n * @param url - The URL to extract the hostname from.\n * @returns The hostname extracted from the URL, or null if the URL is invalid.\n */\nexport const getHostnameFromUrl = (url: string): string | null => {\n let hostname;\n try {\n hostname = new URL(url).hostname;\n // above will not throw if 'http://.' is passed. in fact, any string with a dot will pass.\n if (!hostname || hostname.split('.').join('') === '') {\n return null;\n }\n } catch {\n return null;\n }\n return hostname;\n};\n\n/**\n * getHostnameFromWebUrl returns the hostname from a web URL.\n * It returns the hostname and a boolean indicating if the hostname is valid.\n *\n * @param url - The web URL to extract the hostname from.\n * @returns A tuple containing the extracted hostname and a boolean indicating if the hostname is valid.\n * @example\n * getHostnameFromWebUrl('https://example.com') // Returns: ['example.com', true]\n * getHostnameFromWebUrl('example.com') // Returns: ['', false]\n * getHostnameFromWebUrl('https://') // Returns: ['', false]\n * getHostnameFromWebUrl('') // Returns: ['', false]\n */\nexport const getHostnameFromWebUrl = (url: string): [string, boolean] => {\n if (\n !url.toLowerCase().startsWith('http://') &&\n !url.toLowerCase().startsWith('https://')\n ) {\n return ['', false];\n }\n\n const hostname = getHostnameFromUrl(url);\n return [hostname || '', Boolean(hostname)];\n};\n\n/**\n * Hosts where PDS single-URL scans include the URL path (shared gateways / hosts where many sites\n * share one origin). For all other hosts, only the hostname is sent.\n */\nexport const PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS = [\n 'ipfs.io',\n 'dweb.link',\n 'cf-ipfs.com',\n 'cloudflare-ipfs.com',\n 'irys.xyz',\n 'sites.google.com',\n] as const;\n\n/**\n * @param hostname - Lowercase normalization is applied for matching registered roots and subdomains.\n * @returns Whether {@link getPhishingDetectionScanUrlParam} appends pathname for this hostname.\n */\nexport function isPhishingDetectionPathBasedHostname(\n hostname: string,\n): boolean {\n const normalizedHost = hostname.toLowerCase();\n return PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS.some(\n (root) => normalizedHost === root || normalizedHost.endsWith(`.${root}`),\n );\n}\n\n/**\n * Builds the `url` query parameter for {@link PhishingController.scanUrl}. For hosts in\n * {@link PHISHING_DETECTION_PATH_BASED_ROOT_DOMAINS} (and their subdomains), the value is hostname\n * plus pathname, without protocol, query, or fragment. For all other hosts, only hostname is used.\n *\n * @param url - A web URL string (must use `http:` or `https:` — same rules as {@link getHostnameFromWebUrl}).\n * @returns A tuple of `[scanUrlParam, ok]` where `ok` is false when the URL is not a valid web URL.\n */\nexport const getPhishingDetectionScanUrlParam = (\n url: string,\n): [scanUrlParam: string, ok: boolean] => {\n const [hostname, ok] = getHostnameFromWebUrl(url);\n if (!ok) {\n return ['', false];\n }\n\n if (!isPhishingDetectionPathBasedHostname(hostname)) {\n return [hostname, true];\n }\n\n // `getHostnameFromWebUrl` already required a successful `new URL(url)` parse.\n const { pathname } = new URL(url);\n const pathSuffix = pathname === '/' ? '' : pathname;\n const scanUrlParam = pathSuffix ? `${hostname}${pathSuffix}` : hostname;\n\n return [scanUrlParam, true];\n};\n\nexport const getPathnameFromUrl = (url: string): string => {\n try {\n const { pathname } = new URL(url);\n return pathname;\n } catch {\n return '';\n }\n};\n\n/**\n * Generates all possible parent domains up to a specified limit.\n *\n * @param sourceParts - The list of domain parts in normal order (e.g., ['evil', 'domain', 'co', 'uk']).\n * @param limit - The maximum number of parent domains to generate (default is 5).\n * @returns An array of parent domains starting from the base TLD to the most specific subdomain.\n * @example\n * generateParentDomains(['evil', 'domain', 'co', 'uk'], 5)\n * // Returns: ['co.uk', 'domain.co.uk', 'evil.domain.co.uk']\n *\n * generateParentDomains(['uk'], 5)\n * // Returns: ['uk']\n *\n * generateParentDomains(['sub', 'example', 'com'], 5)\n * // Returns: ['example.com', 'sub.example.com']\n */\nexport const generateParentDomains = (\n sourceParts: string[],\n limit = 5,\n): string[] => {\n const domains: string[] = [];\n\n if (sourceParts.length === 0) {\n return domains;\n }\n\n if (sourceParts.length === 1) {\n // Single-segment hostname (e.g., 'uk')\n domains.push(sourceParts[0].toLowerCase());\n } else {\n // Start with the base domain or TLD (last two labels, e.g., 'co.uk' or 'example.com')\n const baseDomain = sourceParts.slice(-2).join('.');\n domains.push(baseDomain.toLowerCase());\n\n // Iteratively add one subdomain level at a time, up to the specified limit\n for (\n let i = sourceParts.length - 3;\n i >= 0 && domains.length < limit;\n i--\n ) {\n const domain = sourceParts.slice(i).join('.');\n domains.push(domain.toLowerCase());\n }\n }\n\n return domains;\n};\n\n/**\n * Builds a cache key for a token scan result.\n *\n * @param chainId - The chain ID.\n * @param address - The token address.\n * @param caseSensitive - When `true`, the address is kept as-is (for chains\n * like Solana where addresses are case-sensitive). When `false` (default),\n * the address is lowercased (appropriate for EVM).\n * @returns The cache key.\n */\nexport const buildCacheKey = (\n chainId: string,\n address: string,\n caseSensitive = false,\n) => {\n const normalizedAddress = caseSensitive ? address : address.toLowerCase();\n return `${chainId.toLowerCase()}:${normalizedAddress}`;\n};\n\n/**\n * Determines whether a chain name is supported for token approval scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isApprovalSupportedChain = (\n chain: string,\n): chain is ApprovalSupportedChain =>\n (APPROVAL_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Determines whether a chain name is supported for bulk token scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isTokenScanSupportedChain = (\n chain: string,\n): chain is TokenScanSupportedChain =>\n (TOKEN_SCAN_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Determines whether a chain name is supported for address scanning.\n *\n * @param chain - The chain name to check.\n * @returns `true` if the chain is supported, `false` otherwise.\n */\nexport const isAddressScanSupportedChain = (\n chain: string,\n): chain is AddressScanSupportedChain =>\n (ADDRESS_SCAN_SUPPORTED_CHAINS as readonly string[]).includes(chain);\n\n/**\n * Resolves the chain name from a chain ID.\n *\n * @param chainId - The chain ID.\n * @param mapping - The mapping of chain IDs to chain names.\n * @returns The chain name.\n */\nexport const resolveChainName = (\n chainId: string,\n mapping = DEFAULT_CHAIN_ID_TO_NAME,\n): string | null => {\n return mapping[chainId.toLowerCase() as keyof typeof mapping] ?? null;\n};\n\n/**\n * Resolves a chain ID to a Blockaid address-scan chain name, or `null` if\n * `scanAddress` would not call the Security Alerts API for this chain.\n *\n * @param chainId - Hex chain ID for EVM chains (e.g. `'0x1'`) or a chain\n * name for non-EVM chains (e.g. `'solana'`).\n * @returns The address-scan chain name, or `null` if unsupported.\n */\nexport const getAddressScanSupportedChain = (\n chainId: string,\n): AddressScanSupportedChain | null => {\n const chain = resolveChainName(chainId);\n if (!chain || !isAddressScanSupportedChain(chain)) {\n return null;\n }\n return chain;\n};\n\n/**\n * Determines whether `scanAddress` will call the Security Alerts API for\n * this chain, rather than immediately returning `ErrorResult`.\n *\n * Matches the gate inside `scanAddress`: the chain ID must resolve via\n * {@link resolveChainName}, and that name must be in\n * `ADDRESS_SCAN_SUPPORTED_CHAINS`.\n *\n * @param chainId - Hex chain ID for EVM chains (e.g. `'0x1'`) or a chain\n * name for non-EVM chains (e.g. `'solana'`).\n * @returns `true` if an address scan would hit the API.\n */\nexport const isAddressScanSupportedChainId = (chainId: string): boolean =>\n getAddressScanSupportedChain(chainId) !== null;\n\n/**\n * Split tokens into cached results and tokens that need to be fetched.\n *\n * @param cache - Cache-like object with get method.\n * @param cache.get - Method to retrieve cached data by key.\n * @param chainId - The chain ID.\n * @param tokens - Array of token addresses.\n * @param caseSensitive - When `true`, token addresses are kept as-is (for\n * chains like Solana where addresses are case-sensitive). When `false`\n * (default), addresses are lowercased (appropriate for EVM).\n * @returns Object containing cached results and tokens to fetch.\n */\nexport const splitCacheHits = (\n cache: { get: (key: string) => TokenScanCacheData | undefined },\n chainId: string,\n tokens: string[],\n caseSensitive = false,\n): {\n cachedResults: Record<string, TokenScanResult>;\n tokensToFetch: string[];\n} => {\n const cachedResults: Record<string, TokenScanResult> = {};\n const tokensToFetch: string[] = [];\n\n for (const address of tokens) {\n const normalizedAddress = caseSensitive ? address : address.toLowerCase();\n const key = buildCacheKey(chainId, normalizedAddress, caseSensitive);\n const hit = cache.get(key);\n if (hit) {\n cachedResults[normalizedAddress] = {\n result_type: hit.result_type,\n chain: chainId,\n address: normalizedAddress,\n };\n } else {\n tokensToFetch.push(normalizedAddress);\n }\n }\n\n return { cachedResults, tokensToFetch };\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/phishing-controller",
3
- "version": "17.4.0-preview-8c2a2eeb2",
3
+ "version": "17.4.0-preview-b13d916",
4
4
  "description": "Maintains a periodically updated list of approved and unapproved website origins",
5
5
  "keywords": [
6
6
  "Ethereum",