@metamask-previews/phishing-controller 14.0.0-preview-e5ce1e86 → 14.1.0-preview-bc80f5a1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"PhishingDetector.d.cts","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,oBAAgB;AAajB,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,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,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,qBAAa,gBAAgB;;IAK3B;;;;;;;;;OASG;gBACS,IAAI,EAAE,uBAAuB;IAoBzC;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB;IAsH1C;;;;;;;OAOG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,sBAAsB;CA8D/D"}
1
+ {"version":3,"file":"PhishingDetector.d.cts","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,QAAQ,EAAE,uBAAmB;AAC9D,OAAO,EAEL,KAAK,sBAAsB,EAC5B,oBAAgB;AAajB,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;IAsI1C;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAcxC;;;;;;OAMG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,sBAAsB;CA8D/D"}
@@ -1,3 +1,4 @@
1
+ import { type PathTrie } from "./PathTrie.mjs";
1
2
  import { type PhishingDetectorResult } from "./types.mjs";
2
3
  export type LegacyPhishingDetectorList = {
3
4
  whitelist?: string[];
@@ -7,6 +8,7 @@ export type LegacyPhishingDetectorList = {
7
8
  export type PhishingDetectorList = {
8
9
  allowlist?: string[];
9
10
  blocklist?: string[];
11
+ blocklistPaths?: PathTrie;
10
12
  c2DomainBlocklist?: string[];
11
13
  name?: string;
12
14
  version?: string | number;
@@ -25,6 +27,7 @@ export type PhishingDetectorConfiguration = {
25
27
  version?: number | string;
26
28
  allowlist: string[][];
27
29
  blocklist: string[][];
30
+ blocklistPaths?: PathTrie;
28
31
  c2DomainBlocklist?: string[];
29
32
  fuzzylist: string[][];
30
33
  tolerance: number;
@@ -51,11 +54,17 @@ export declare class PhishingDetector {
51
54
  * @returns The result of the check.
52
55
  */
53
56
  check(url: string): PhishingDetectorResult;
57
+ /**
58
+ * Gets the specific terminal path from blocklistPaths that is blocking a URL.
59
+ *
60
+ * @param url - The URL to check.
61
+ * @returns The terminal path that is blocking the URL, or null if not blocked.
62
+ */
63
+ blockingPath(url: string): string | null;
54
64
  /**
55
65
  * Checks if a URL is blocked against the hashed request blocklist.
56
66
  * This is done by hashing the URL's hostname and checking it against the hashed request blocklist.
57
67
  *
58
- *
59
68
  * @param urlString - The URL to check.
60
69
  * @returns An object indicating if the URL is blocked and relevant metadata.
61
70
  */
@@ -1 +1 @@
1
- {"version":3,"file":"PhishingDetector.d.mts","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,oBAAgB;AAajB,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,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,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,qBAAa,gBAAgB;;IAK3B;;;;;;;;;OASG;gBACS,IAAI,EAAE,uBAAuB;IAoBzC;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB;IAsH1C;;;;;;;OAOG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,sBAAsB;CA8D/D"}
1
+ {"version":3,"file":"PhishingDetector.d.mts","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,QAAQ,EAAE,uBAAmB;AAC9D,OAAO,EAEL,KAAK,sBAAsB,EAC5B,oBAAgB;AAajB,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;IAsI1C;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAcxC;;;;;;OAMG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,sBAAsB;CA8D/D"}
@@ -11,6 +11,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
11
11
  };
12
12
  var _PhishingDetector_instances, _PhishingDetector_configs, _PhishingDetector_legacyConfig, _PhishingDetector_check;
13
13
  import { distance } from "fastest-levenshtein";
14
+ import { matchedPathPrefix } from "./PathTrie.mjs";
14
15
  import { PhishingDetectorResultType } from "./types.mjs";
15
16
  import { domainPartsToDomain, domainPartsToFuzzyForm, domainToParts, generateParentDomains, getDefaultPhishingDetectorConfig, getHostnameFromUrl, matchPartsAgainstList, processConfigs, sha256Hash } from "./utils.mjs";
16
17
  export class PhishingDetector {
@@ -39,7 +40,6 @@ export class PhishingDetector {
39
40
  getDefaultPhishingDetectorConfig({
40
41
  allowlist: opts.whitelist,
41
42
  blocklist: opts.blacklist,
42
- c2DomainBlocklist: opts.c2DomainBlocklist,
43
43
  fuzzylist: opts.fuzzylist,
44
44
  tolerance: opts.tolerance,
45
45
  }),
@@ -73,11 +73,28 @@ export class PhishingDetector {
73
73
  }
74
74
  return result;
75
75
  }
76
+ /**
77
+ * Gets the specific terminal path from blocklistPaths that is blocking a URL.
78
+ *
79
+ * @param url - The URL to check.
80
+ * @returns The terminal path that is blocking the URL, or null if not blocked.
81
+ */
82
+ blockingPath(url) {
83
+ for (const { blocklistPaths } of __classPrivateFieldGet(this, _PhishingDetector_configs, "f")) {
84
+ if (!blocklistPaths || Object.keys(blocklistPaths).length === 0) {
85
+ continue;
86
+ }
87
+ const matchedPath = matchedPathPrefix(url, blocklistPaths);
88
+ if (matchedPath) {
89
+ return matchedPath;
90
+ }
91
+ }
92
+ return null;
93
+ }
76
94
  /**
77
95
  * Checks if a URL is blocked against the hashed request blocklist.
78
96
  * This is done by hashing the URL's hostname and checking it against the hashed request blocklist.
79
97
  *
80
- *
81
98
  * @param urlString - The URL to check.
82
99
  * @returns An object indicating if the URL is blocked and relevant metadata.
83
100
  */
@@ -167,7 +184,7 @@ _PhishingDetector_configs = new WeakMap(), _PhishingDetector_legacyConfig = new
167
184
  try {
168
185
  domain = new URL(url).hostname;
169
186
  }
170
- catch (error) {
187
+ catch {
171
188
  return {
172
189
  result: false,
173
190
  type: PhishingDetectorResultType.All,
@@ -175,6 +192,21 @@ _PhishingDetector_configs = new WeakMap(), _PhishingDetector_legacyConfig = new
175
192
  }
176
193
  const fqdn = domain.endsWith('.') ? domain.slice(0, -1) : domain;
177
194
  const source = domainToParts(fqdn);
195
+ for (const { blocklistPaths, name, version } of __classPrivateFieldGet(this, _PhishingDetector_configs, "f")) {
196
+ if (!blocklistPaths || Object.keys(blocklistPaths).length === 0) {
197
+ continue;
198
+ }
199
+ const pathMatch = matchedPathPrefix(url, blocklistPaths);
200
+ if (pathMatch) {
201
+ return {
202
+ match: pathMatch,
203
+ name,
204
+ result: true,
205
+ type: PhishingDetectorResultType.Blocklist,
206
+ version: version === undefined ? version : String(version),
207
+ };
208
+ }
209
+ }
178
210
  for (const { allowlist, name, version } of __classPrivateFieldGet(this, _PhishingDetector_configs, "f")) {
179
211
  // if source matches allowlist hostname (or subdomain thereof), PASS
180
212
  const allowlistMatch = matchPartsAgainstList(source, allowlist);
@@ -230,6 +262,7 @@ _PhishingDetector_configs = new WeakMap(), _PhishingDetector_legacyConfig = new
230
262
  };
231
263
  /**
232
264
  * Runs a regex match to determine if a string is a IPFS CID
265
+ *
233
266
  * @returns Regex string for IPFS CID
234
267
  */
235
268
  function ipfsCidRegex() {
@@ -1 +1 @@
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 {\n PhishingDetectorResultType,\n type PhishingDetectorResult,\n} from './types';\nimport {\n domainPartsToDomain,\n domainPartsToFuzzyForm,\n domainToParts,\n generateParentDomains,\n getDefaultPhishingDetectorConfig,\n getHostnameFromUrl,\n matchPartsAgainstList,\n processConfigs,\n sha256Hash,\n} from './utils';\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 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 fuzzylist: string[][];\n tolerance: number;\n};\n\nexport class PhishingDetector {\n #configs: PhishingDetectorConfiguration[];\n\n #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 c2DomainBlocklist: opts.c2DomainBlocklist,\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 (error) {\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 { 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 { blocklist, fuzzylist, name, tolerance, version } of this\n .#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 * 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 *\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 * @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, type PathTrie } from './PathTrie';\nimport {\n PhishingDetectorResultType,\n type PhishingDetectorResult,\n} from './types';\nimport {\n domainPartsToDomain,\n domainPartsToFuzzyForm,\n domainToParts,\n generateParentDomains,\n getDefaultPhishingDetectorConfig,\n getHostnameFromUrl,\n matchPartsAgainstList,\n processConfigs,\n sha256Hash,\n} from './utils';\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 { blocklist, fuzzylist, name, tolerance, version } of this\n .#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"]}
@@ -16,7 +16,7 @@ const formatHostnameToUrl = (hostname) => {
16
16
  try {
17
17
  url = new URL(hostname).href;
18
18
  }
19
- catch (e) {
19
+ catch {
20
20
  url = new URL(['https://', hostname].join('')).href;
21
21
  }
22
22
  return url;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs","sourceRoot":"","sources":["../../src/tests/utils.ts"],"names":[],"mappings":";;;AACA,6EAI0C;AAE1C;;;;;;;;GAQG;AACI,MAAM,mBAAmB,GAAG,CAAC,QAAgB,EAAU,EAAE;IAC9D,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI;QACF,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;KAC9B;IAAC,OAAO,CAAC,EAAE;QACV,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;KACrD;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AARW,QAAA,mBAAmB,uBAQ9B;AAEF;;GAEG;AACU,QAAA,cAAc,GAAG;IAC5B,YAAY,EAAE,4CAA6D;IAC3E,IAAI,EAAE,4CAA6D;IACnE,YAAY,EAAE,4CAA6D;IAC3E,UAAU,EAAE,4CAA6D;CAC1E,CAAC;AAEF;;;;;;;;;;;GAWG;AACI,MAAM,4BAA4B,GAAG,CAC1C,OAAsB,EACtB,UAMI,EAAE,EACN,EAAE,CAAC,CAAC;IACJ,OAAO;IACP,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,gDAAuB,CAAC,KAAK;IAC3D,UAAU,EAAE,OAAO,CAAC,UAAU,IAAK,mBAAqC;IACxE,eAAe,EAAE,OAAO,CAAC,eAAe,IAAK,KAAuB;IACpE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAK,mBAAqC;IACxE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,KAAK;CACxC,CAAC,CAAC;AAhBU,QAAA,4BAA4B,gCAgBtC;AAEH;;;;;;;GAOG;AACI,MAAM,qBAAqB,GAAG,CACnC,EAAU,EACV,iBAAkC,EAAE,EACpC,YAAsC,EAAE,EACvB,EAAE;IACnB,MAAM,cAAc,GAClB,cAAc,CAAC,MAAM,GAAG,CAAC;QACvB,CAAC,CAAC;YACE,mBAAmB,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAClD,IAAA,oCAA4B,EAAC,OAAO,CAAC,CACtC;SACF;QACH,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC;IAE/B,OAAO;QACL,QAAQ,EAAE;YACR,IAAI,EAAE,sBAAc,CAAC,YAAY;YACjC,EAAE,EAAE,sBAAc,CAAC,UAAU;YAC7B,KAAK,EAAE,KAAsB;SAC9B;QACD,OAAO,EAAE,KAAsB;QAC/B,EAAE;QACF,eAAe,EAAE,SAAS;QAC1B,MAAM,EAAE,0CAAiB,CAAC,UAAU;QACpC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;QAChB,IAAI,EAAE,wCAAe,CAAC,mBAAmB;QACzC,MAAM,EAAE,qBAAqB;QAC7B,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;QACzB,cAAc;QACd,GAAG,SAAS;KACb,CAAC;AACJ,CAAC,CAAC;AA/BW,QAAA,qBAAqB,yBA+BhC;AAEF;;;;;GAKG;AACI,MAAM,4BAA4B,GAAG,CAC1C,YAA+B,EAC/B,EAAE,CAAC,CAAC;IACJ,YAAY;IACZ,kBAAkB,EAAE,EAAE;IACtB,UAAU,EAAE,EAAE;IACd,uBAAuB,EAAE,EAAE;IAC3B,aAAa,EAAE,EAAE;CAClB,CAAC,CAAC;AARU,QAAA,4BAA4B,gCAQtC","sourcesContent":["import type { TransactionMeta } from '@metamask/transaction-controller';\nimport {\n TransactionStatus,\n TransactionType,\n SimulationTokenStandard,\n} from '@metamask/transaction-controller';\n\n/**\n * Formats a hostname into a URL so we can parse it correctly\n * and pass full URLs into the PhishingDetector class. Previously\n * only hostnames were supported, but now only full URLs are\n * supported since we want to block IPFS CIDs.\n *\n * @param hostname - the hostname of the URL.\n * @returns the href property of a URL object.\n */\nexport const formatHostnameToUrl = (hostname: string): string => {\n let url = '';\n try {\n url = new URL(hostname).href;\n } catch (e) {\n url = new URL(['https://', hostname].join('')).href;\n }\n return url;\n};\n\n/**\n * Test addresses for consistent use in tests\n */\nexport const TEST_ADDRESSES = {\n MOCK_TOKEN_1: '0x1234567890123456789012345678901234567890' as `0x${string}`,\n USDC: '0xA0B86991c6218B36C1D19D4A2E9EB0CE3606EB48' as `0x${string}`,\n FROM_ADDRESS: '0x0987654321098765432109876543210987654321' as `0x${string}`,\n TO_ADDRESS: '0x1234567890123456789012345678901234567890' as `0x${string}`,\n};\n\n/**\n * Creates a mock token balance change object\n *\n * @param address - The address of the token\n * @param options - The options for the token balance change\n * @param options.difference - The difference in the token balance\n * @param options.previousBalance - The previous balance of the token\n * @param options.newBalance - The new balance of the token\n * @param options.isDecrease - Whether the token balance is decreasing\n * @param options.standard - The standard of the token\n * @returns The mock token balance change object\n */\nexport const createMockTokenBalanceChange = (\n address: `0x${string}`,\n options: {\n difference?: `0x${string}`;\n previousBalance?: `0x${string}`;\n newBalance?: `0x${string}`;\n isDecrease?: boolean;\n standard?: SimulationTokenStandard;\n } = {},\n) => ({\n address,\n standard: options.standard ?? SimulationTokenStandard.erc20,\n difference: options.difference ?? ('0xde0b6b3a7640000' as `0x${string}`),\n previousBalance: options.previousBalance ?? ('0x0' as `0x${string}`),\n newBalance: options.newBalance ?? ('0xde0b6b3a7640000' as `0x${string}`),\n isDecrease: options.isDecrease ?? false,\n});\n\n/**\n * Creates a mock transaction with token balance changes\n *\n * @param id - The transaction ID\n * @param tokenAddresses - Array of token addresses to include in balance changes\n * @param overrides - Partial transaction metadata to override defaults\n * @returns The mock transaction metadata object\n */\nexport const createMockTransaction = (\n id: string,\n tokenAddresses: `0x${string}`[] = [],\n overrides: Partial<TransactionMeta> = {},\n): TransactionMeta => {\n const simulationData =\n tokenAddresses.length > 0\n ? {\n tokenBalanceChanges: tokenAddresses.map((address) =>\n createMockTokenBalanceChange(address),\n ),\n }\n : overrides.simulationData;\n\n return {\n txParams: {\n from: TEST_ADDRESSES.FROM_ADDRESS,\n to: TEST_ADDRESSES.TO_ADDRESS,\n value: '0x0' as `0x${string}`,\n },\n chainId: '0x1' as `0x${string}`,\n id,\n networkClientId: 'mainnet',\n status: TransactionStatus.unapproved,\n time: Date.now(),\n type: TransactionType.contractInteraction,\n origin: 'https://metamask.io',\n submittedTime: Date.now(),\n simulationData,\n ...overrides,\n };\n};\n\n/**\n * Creates a mock state change payload for TransactionController\n *\n * @param transactions - The transactions to include in the state change payload.\n * @returns A mock state change payload.\n */\nexport const createMockStateChangePayload = (\n transactions: TransactionMeta[],\n) => ({\n transactions,\n transactionBatches: [],\n methodData: {},\n lastFetchedBlockNumbers: {},\n submitHistory: [],\n});\n"]}
1
+ {"version":3,"file":"utils.cjs","sourceRoot":"","sources":["../../src/tests/utils.ts"],"names":[],"mappings":";;;AACA,6EAI0C;AAE1C;;;;;;;;GAQG;AACI,MAAM,mBAAmB,GAAG,CAAC,QAAgB,EAAU,EAAE;IAC9D,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI;QACF,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;KAC9B;IAAC,MAAM;QACN,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;KACrD;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AARW,QAAA,mBAAmB,uBAQ9B;AAEF;;GAEG;AACU,QAAA,cAAc,GAAG;IAC5B,YAAY,EAAE,4CAA6D;IAC3E,IAAI,EAAE,4CAA6D;IACnE,YAAY,EAAE,4CAA6D;IAC3E,UAAU,EAAE,4CAA6D;CAC1E,CAAC;AAEF;;;;;;;;;;;GAWG;AACI,MAAM,4BAA4B,GAAG,CAC1C,OAAsB,EACtB,UAMI,EAAE,EACN,EAAE,CAAC,CAAC;IACJ,OAAO;IACP,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,gDAAuB,CAAC,KAAK;IAC3D,UAAU,EAAE,OAAO,CAAC,UAAU,IAAK,mBAAqC;IACxE,eAAe,EAAE,OAAO,CAAC,eAAe,IAAK,KAAuB;IACpE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAK,mBAAqC;IACxE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,KAAK;CACxC,CAAC,CAAC;AAhBU,QAAA,4BAA4B,gCAgBtC;AAEH;;;;;;;GAOG;AACI,MAAM,qBAAqB,GAAG,CACnC,EAAU,EACV,iBAAkC,EAAE,EACpC,YAAsC,EAAE,EACvB,EAAE;IACnB,MAAM,cAAc,GAClB,cAAc,CAAC,MAAM,GAAG,CAAC;QACvB,CAAC,CAAC;YACE,mBAAmB,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAClD,IAAA,oCAA4B,EAAC,OAAO,CAAC,CACtC;SACF;QACH,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC;IAE/B,OAAO;QACL,QAAQ,EAAE;YACR,IAAI,EAAE,sBAAc,CAAC,YAAY;YACjC,EAAE,EAAE,sBAAc,CAAC,UAAU;YAC7B,KAAK,EAAE,KAAsB;SAC9B;QACD,OAAO,EAAE,KAAsB;QAC/B,EAAE;QACF,eAAe,EAAE,SAAS;QAC1B,MAAM,EAAE,0CAAiB,CAAC,UAAU;QACpC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;QAChB,IAAI,EAAE,wCAAe,CAAC,mBAAmB;QACzC,MAAM,EAAE,qBAAqB;QAC7B,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;QACzB,cAAc;QACd,GAAG,SAAS;KACb,CAAC;AACJ,CAAC,CAAC;AA/BW,QAAA,qBAAqB,yBA+BhC;AAEF;;;;;GAKG;AACI,MAAM,4BAA4B,GAAG,CAC1C,YAA+B,EAC/B,EAAE,CAAC,CAAC;IACJ,YAAY;IACZ,kBAAkB,EAAE,EAAE;IACtB,UAAU,EAAE,EAAE;IACd,uBAAuB,EAAE,EAAE;IAC3B,aAAa,EAAE,EAAE;CAClB,CAAC,CAAC;AARU,QAAA,4BAA4B,gCAQtC","sourcesContent":["import type { TransactionMeta } from '@metamask/transaction-controller';\nimport {\n TransactionStatus,\n TransactionType,\n SimulationTokenStandard,\n} from '@metamask/transaction-controller';\n\n/**\n * Formats a hostname into a URL so we can parse it correctly\n * and pass full URLs into the PhishingDetector class. Previously\n * only hostnames were supported, but now only full URLs are\n * supported since we want to block IPFS CIDs.\n *\n * @param hostname - the hostname of the URL.\n * @returns the href property of a URL object.\n */\nexport const formatHostnameToUrl = (hostname: string): string => {\n let url = '';\n try {\n url = new URL(hostname).href;\n } catch {\n url = new URL(['https://', hostname].join('')).href;\n }\n return url;\n};\n\n/**\n * Test addresses for consistent use in tests\n */\nexport const TEST_ADDRESSES = {\n MOCK_TOKEN_1: '0x1234567890123456789012345678901234567890' as `0x${string}`,\n USDC: '0xA0B86991c6218B36C1D19D4A2E9EB0CE3606EB48' as `0x${string}`,\n FROM_ADDRESS: '0x0987654321098765432109876543210987654321' as `0x${string}`,\n TO_ADDRESS: '0x1234567890123456789012345678901234567890' as `0x${string}`,\n};\n\n/**\n * Creates a mock token balance change object\n *\n * @param address - The address of the token\n * @param options - The options for the token balance change\n * @param options.difference - The difference in the token balance\n * @param options.previousBalance - The previous balance of the token\n * @param options.newBalance - The new balance of the token\n * @param options.isDecrease - Whether the token balance is decreasing\n * @param options.standard - The standard of the token\n * @returns The mock token balance change object\n */\nexport const createMockTokenBalanceChange = (\n address: `0x${string}`,\n options: {\n difference?: `0x${string}`;\n previousBalance?: `0x${string}`;\n newBalance?: `0x${string}`;\n isDecrease?: boolean;\n standard?: SimulationTokenStandard;\n } = {},\n) => ({\n address,\n standard: options.standard ?? SimulationTokenStandard.erc20,\n difference: options.difference ?? ('0xde0b6b3a7640000' as `0x${string}`),\n previousBalance: options.previousBalance ?? ('0x0' as `0x${string}`),\n newBalance: options.newBalance ?? ('0xde0b6b3a7640000' as `0x${string}`),\n isDecrease: options.isDecrease ?? false,\n});\n\n/**\n * Creates a mock transaction with token balance changes\n *\n * @param id - The transaction ID\n * @param tokenAddresses - Array of token addresses to include in balance changes\n * @param overrides - Partial transaction metadata to override defaults\n * @returns The mock transaction metadata object\n */\nexport const createMockTransaction = (\n id: string,\n tokenAddresses: `0x${string}`[] = [],\n overrides: Partial<TransactionMeta> = {},\n): TransactionMeta => {\n const simulationData =\n tokenAddresses.length > 0\n ? {\n tokenBalanceChanges: tokenAddresses.map((address) =>\n createMockTokenBalanceChange(address),\n ),\n }\n : overrides.simulationData;\n\n return {\n txParams: {\n from: TEST_ADDRESSES.FROM_ADDRESS,\n to: TEST_ADDRESSES.TO_ADDRESS,\n value: '0x0' as `0x${string}`,\n },\n chainId: '0x1' as `0x${string}`,\n id,\n networkClientId: 'mainnet',\n status: TransactionStatus.unapproved,\n time: Date.now(),\n type: TransactionType.contractInteraction,\n origin: 'https://metamask.io',\n submittedTime: Date.now(),\n simulationData,\n ...overrides,\n };\n};\n\n/**\n * Creates a mock state change payload for TransactionController\n *\n * @param transactions - The transactions to include in the state change payload.\n * @returns A mock state change payload.\n */\nexport const createMockStateChangePayload = (\n transactions: TransactionMeta[],\n) => ({\n transactions,\n transactionBatches: [],\n methodData: {},\n lastFetchedBlockNumbers: {},\n submitHistory: [],\n});\n"]}
@@ -13,7 +13,7 @@ export const formatHostnameToUrl = (hostname) => {
13
13
  try {
14
14
  url = new URL(hostname).href;
15
15
  }
16
- catch (e) {
16
+ catch {
17
17
  url = new URL(['https://', hostname].join('')).href;
18
18
  }
19
19
  return url;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","sourceRoot":"","sources":["../../src/tests/utils.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACxB,yCAAyC;AAE1C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,QAAgB,EAAU,EAAE;IAC9D,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI;QACF,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;KAC9B;IAAC,OAAO,CAAC,EAAE;QACV,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;KACrD;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,YAAY,EAAE,4CAA6D;IAC3E,IAAI,EAAE,4CAA6D;IACnE,YAAY,EAAE,4CAA6D;IAC3E,UAAU,EAAE,4CAA6D;CAC1E,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAC1C,OAAsB,EACtB,UAMI,EAAE,EACN,EAAE,CAAC,CAAC;IACJ,OAAO;IACP,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,uBAAuB,CAAC,KAAK;IAC3D,UAAU,EAAE,OAAO,CAAC,UAAU,IAAK,mBAAqC;IACxE,eAAe,EAAE,OAAO,CAAC,eAAe,IAAK,KAAuB;IACpE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAK,mBAAqC;IACxE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,KAAK;CACxC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,EAAU,EACV,iBAAkC,EAAE,EACpC,YAAsC,EAAE,EACvB,EAAE;IACnB,MAAM,cAAc,GAClB,cAAc,CAAC,MAAM,GAAG,CAAC;QACvB,CAAC,CAAC;YACE,mBAAmB,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAClD,4BAA4B,CAAC,OAAO,CAAC,CACtC;SACF;QACH,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC;IAE/B,OAAO;QACL,QAAQ,EAAE;YACR,IAAI,EAAE,cAAc,CAAC,YAAY;YACjC,EAAE,EAAE,cAAc,CAAC,UAAU;YAC7B,KAAK,EAAE,KAAsB;SAC9B;QACD,OAAO,EAAE,KAAsB;QAC/B,EAAE;QACF,eAAe,EAAE,SAAS;QAC1B,MAAM,EAAE,iBAAiB,CAAC,UAAU;QACpC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;QAChB,IAAI,EAAE,eAAe,CAAC,mBAAmB;QACzC,MAAM,EAAE,qBAAqB;QAC7B,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;QACzB,cAAc;QACd,GAAG,SAAS;KACb,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAC1C,YAA+B,EAC/B,EAAE,CAAC,CAAC;IACJ,YAAY;IACZ,kBAAkB,EAAE,EAAE;IACtB,UAAU,EAAE,EAAE;IACd,uBAAuB,EAAE,EAAE;IAC3B,aAAa,EAAE,EAAE;CAClB,CAAC,CAAC","sourcesContent":["import type { TransactionMeta } from '@metamask/transaction-controller';\nimport {\n TransactionStatus,\n TransactionType,\n SimulationTokenStandard,\n} from '@metamask/transaction-controller';\n\n/**\n * Formats a hostname into a URL so we can parse it correctly\n * and pass full URLs into the PhishingDetector class. Previously\n * only hostnames were supported, but now only full URLs are\n * supported since we want to block IPFS CIDs.\n *\n * @param hostname - the hostname of the URL.\n * @returns the href property of a URL object.\n */\nexport const formatHostnameToUrl = (hostname: string): string => {\n let url = '';\n try {\n url = new URL(hostname).href;\n } catch (e) {\n url = new URL(['https://', hostname].join('')).href;\n }\n return url;\n};\n\n/**\n * Test addresses for consistent use in tests\n */\nexport const TEST_ADDRESSES = {\n MOCK_TOKEN_1: '0x1234567890123456789012345678901234567890' as `0x${string}`,\n USDC: '0xA0B86991c6218B36C1D19D4A2E9EB0CE3606EB48' as `0x${string}`,\n FROM_ADDRESS: '0x0987654321098765432109876543210987654321' as `0x${string}`,\n TO_ADDRESS: '0x1234567890123456789012345678901234567890' as `0x${string}`,\n};\n\n/**\n * Creates a mock token balance change object\n *\n * @param address - The address of the token\n * @param options - The options for the token balance change\n * @param options.difference - The difference in the token balance\n * @param options.previousBalance - The previous balance of the token\n * @param options.newBalance - The new balance of the token\n * @param options.isDecrease - Whether the token balance is decreasing\n * @param options.standard - The standard of the token\n * @returns The mock token balance change object\n */\nexport const createMockTokenBalanceChange = (\n address: `0x${string}`,\n options: {\n difference?: `0x${string}`;\n previousBalance?: `0x${string}`;\n newBalance?: `0x${string}`;\n isDecrease?: boolean;\n standard?: SimulationTokenStandard;\n } = {},\n) => ({\n address,\n standard: options.standard ?? SimulationTokenStandard.erc20,\n difference: options.difference ?? ('0xde0b6b3a7640000' as `0x${string}`),\n previousBalance: options.previousBalance ?? ('0x0' as `0x${string}`),\n newBalance: options.newBalance ?? ('0xde0b6b3a7640000' as `0x${string}`),\n isDecrease: options.isDecrease ?? false,\n});\n\n/**\n * Creates a mock transaction with token balance changes\n *\n * @param id - The transaction ID\n * @param tokenAddresses - Array of token addresses to include in balance changes\n * @param overrides - Partial transaction metadata to override defaults\n * @returns The mock transaction metadata object\n */\nexport const createMockTransaction = (\n id: string,\n tokenAddresses: `0x${string}`[] = [],\n overrides: Partial<TransactionMeta> = {},\n): TransactionMeta => {\n const simulationData =\n tokenAddresses.length > 0\n ? {\n tokenBalanceChanges: tokenAddresses.map((address) =>\n createMockTokenBalanceChange(address),\n ),\n }\n : overrides.simulationData;\n\n return {\n txParams: {\n from: TEST_ADDRESSES.FROM_ADDRESS,\n to: TEST_ADDRESSES.TO_ADDRESS,\n value: '0x0' as `0x${string}`,\n },\n chainId: '0x1' as `0x${string}`,\n id,\n networkClientId: 'mainnet',\n status: TransactionStatus.unapproved,\n time: Date.now(),\n type: TransactionType.contractInteraction,\n origin: 'https://metamask.io',\n submittedTime: Date.now(),\n simulationData,\n ...overrides,\n };\n};\n\n/**\n * Creates a mock state change payload for TransactionController\n *\n * @param transactions - The transactions to include in the state change payload.\n * @returns A mock state change payload.\n */\nexport const createMockStateChangePayload = (\n transactions: TransactionMeta[],\n) => ({\n transactions,\n transactionBatches: [],\n methodData: {},\n lastFetchedBlockNumbers: {},\n submitHistory: [],\n});\n"]}
1
+ {"version":3,"file":"utils.mjs","sourceRoot":"","sources":["../../src/tests/utils.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACxB,yCAAyC;AAE1C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,QAAgB,EAAU,EAAE;IAC9D,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI;QACF,GAAG,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;KAC9B;IAAC,MAAM;QACN,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;KACrD;IACD,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,YAAY,EAAE,4CAA6D;IAC3E,IAAI,EAAE,4CAA6D;IACnE,YAAY,EAAE,4CAA6D;IAC3E,UAAU,EAAE,4CAA6D;CAC1E,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAC1C,OAAsB,EACtB,UAMI,EAAE,EACN,EAAE,CAAC,CAAC;IACJ,OAAO;IACP,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,uBAAuB,CAAC,KAAK;IAC3D,UAAU,EAAE,OAAO,CAAC,UAAU,IAAK,mBAAqC;IACxE,eAAe,EAAE,OAAO,CAAC,eAAe,IAAK,KAAuB;IACpE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAK,mBAAqC;IACxE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,KAAK;CACxC,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,EAAU,EACV,iBAAkC,EAAE,EACpC,YAAsC,EAAE,EACvB,EAAE;IACnB,MAAM,cAAc,GAClB,cAAc,CAAC,MAAM,GAAG,CAAC;QACvB,CAAC,CAAC;YACE,mBAAmB,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAClD,4BAA4B,CAAC,OAAO,CAAC,CACtC;SACF;QACH,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC;IAE/B,OAAO;QACL,QAAQ,EAAE;YACR,IAAI,EAAE,cAAc,CAAC,YAAY;YACjC,EAAE,EAAE,cAAc,CAAC,UAAU;YAC7B,KAAK,EAAE,KAAsB;SAC9B;QACD,OAAO,EAAE,KAAsB;QAC/B,EAAE;QACF,eAAe,EAAE,SAAS;QAC1B,MAAM,EAAE,iBAAiB,CAAC,UAAU;QACpC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;QAChB,IAAI,EAAE,eAAe,CAAC,mBAAmB;QACzC,MAAM,EAAE,qBAAqB;QAC7B,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;QACzB,cAAc;QACd,GAAG,SAAS;KACb,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAC1C,YAA+B,EAC/B,EAAE,CAAC,CAAC;IACJ,YAAY;IACZ,kBAAkB,EAAE,EAAE;IACtB,UAAU,EAAE,EAAE;IACd,uBAAuB,EAAE,EAAE;IAC3B,aAAa,EAAE,EAAE;CAClB,CAAC,CAAC","sourcesContent":["import type { TransactionMeta } from '@metamask/transaction-controller';\nimport {\n TransactionStatus,\n TransactionType,\n SimulationTokenStandard,\n} from '@metamask/transaction-controller';\n\n/**\n * Formats a hostname into a URL so we can parse it correctly\n * and pass full URLs into the PhishingDetector class. Previously\n * only hostnames were supported, but now only full URLs are\n * supported since we want to block IPFS CIDs.\n *\n * @param hostname - the hostname of the URL.\n * @returns the href property of a URL object.\n */\nexport const formatHostnameToUrl = (hostname: string): string => {\n let url = '';\n try {\n url = new URL(hostname).href;\n } catch {\n url = new URL(['https://', hostname].join('')).href;\n }\n return url;\n};\n\n/**\n * Test addresses for consistent use in tests\n */\nexport const TEST_ADDRESSES = {\n MOCK_TOKEN_1: '0x1234567890123456789012345678901234567890' as `0x${string}`,\n USDC: '0xA0B86991c6218B36C1D19D4A2E9EB0CE3606EB48' as `0x${string}`,\n FROM_ADDRESS: '0x0987654321098765432109876543210987654321' as `0x${string}`,\n TO_ADDRESS: '0x1234567890123456789012345678901234567890' as `0x${string}`,\n};\n\n/**\n * Creates a mock token balance change object\n *\n * @param address - The address of the token\n * @param options - The options for the token balance change\n * @param options.difference - The difference in the token balance\n * @param options.previousBalance - The previous balance of the token\n * @param options.newBalance - The new balance of the token\n * @param options.isDecrease - Whether the token balance is decreasing\n * @param options.standard - The standard of the token\n * @returns The mock token balance change object\n */\nexport const createMockTokenBalanceChange = (\n address: `0x${string}`,\n options: {\n difference?: `0x${string}`;\n previousBalance?: `0x${string}`;\n newBalance?: `0x${string}`;\n isDecrease?: boolean;\n standard?: SimulationTokenStandard;\n } = {},\n) => ({\n address,\n standard: options.standard ?? SimulationTokenStandard.erc20,\n difference: options.difference ?? ('0xde0b6b3a7640000' as `0x${string}`),\n previousBalance: options.previousBalance ?? ('0x0' as `0x${string}`),\n newBalance: options.newBalance ?? ('0xde0b6b3a7640000' as `0x${string}`),\n isDecrease: options.isDecrease ?? false,\n});\n\n/**\n * Creates a mock transaction with token balance changes\n *\n * @param id - The transaction ID\n * @param tokenAddresses - Array of token addresses to include in balance changes\n * @param overrides - Partial transaction metadata to override defaults\n * @returns The mock transaction metadata object\n */\nexport const createMockTransaction = (\n id: string,\n tokenAddresses: `0x${string}`[] = [],\n overrides: Partial<TransactionMeta> = {},\n): TransactionMeta => {\n const simulationData =\n tokenAddresses.length > 0\n ? {\n tokenBalanceChanges: tokenAddresses.map((address) =>\n createMockTokenBalanceChange(address),\n ),\n }\n : overrides.simulationData;\n\n return {\n txParams: {\n from: TEST_ADDRESSES.FROM_ADDRESS,\n to: TEST_ADDRESSES.TO_ADDRESS,\n value: '0x0' as `0x${string}`,\n },\n chainId: '0x1' as `0x${string}`,\n id,\n networkClientId: 'mainnet',\n status: TransactionStatus.unapproved,\n time: Date.now(),\n type: TransactionType.contractInteraction,\n origin: 'https://metamask.io',\n submittedTime: Date.now(),\n simulationData,\n ...overrides,\n };\n};\n\n/**\n * Creates a mock state change payload for TransactionController\n *\n * @param transactions - The transactions to include in the state change payload.\n * @returns A mock state change payload.\n */\nexport const createMockStateChangePayload = (\n transactions: TransactionMeta[],\n) => ({\n transactions,\n transactionBatches: [],\n methodData: {},\n lastFetchedBlockNumbers: {},\n submitHistory: [],\n});\n"]}
package/dist/utils.cjs CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.splitCacheHits = exports.resolveChainName = exports.buildCacheKey = exports.generateParentDomains = exports.getHostnameFromWebUrl = exports.getHostnameFromUrl = exports.sha256Hash = exports.matchPartsAgainstList = exports.domainPartsToFuzzyForm = exports.domainPartsToDomain = exports.processConfigs = exports.getDefaultPhishingDetectorConfig = exports.processDomainList = exports.domainToParts = exports.validateConfig = exports.applyDiffs = exports.roundToNearestMinute = exports.fetchTimeNow = void 0;
3
+ exports.splitCacheHits = exports.resolveChainName = exports.buildCacheKey = exports.generateParentDomains = exports.getPathnameFromUrl = exports.getHostnameFromWebUrl = exports.getHostnameFromUrl = exports.sha256Hash = exports.matchPartsAgainstList = exports.domainPartsToFuzzyForm = exports.domainPartsToDomain = exports.processConfigs = exports.getDefaultPhishingDetectorConfig = exports.processDomainList = exports.domainToParts = exports.validateConfig = exports.applyDiffs = exports.getHostnameAndPathComponents = exports.roundToNearestMinute = exports.fetchTimeNow = void 0;
4
4
  const utils_1 = require("@noble/hashes/utils");
5
5
  const sha256_1 = require("ethereum-cryptography/sha256");
6
+ const PathTrie_1 = require("./PathTrie.cjs");
6
7
  const PhishingController_1 = require("./PhishingController.cjs");
7
8
  const types_1 = require("./types.cjs");
8
9
  const DEFAULT_TOLERANCE = 3;
@@ -36,6 +37,26 @@ const splitStringByPeriod = (stringToSplit) => {
36
37
  stringToSplit.slice(periodIndex + 1),
37
38
  ];
38
39
  };
40
+ const getHostnameAndPathComponents = (url) => {
41
+ const urlWithProtocol = url.startsWith('http') ? url : `https://${url}`;
42
+ try {
43
+ const { hostname, pathname } = new URL(urlWithProtocol);
44
+ return {
45
+ hostname: hostname.toLowerCase(),
46
+ pathComponents: pathname
47
+ .split('/')
48
+ .filter(Boolean)
49
+ .map((component) => decodeURIComponent(component)),
50
+ };
51
+ }
52
+ catch {
53
+ return {
54
+ hostname: '',
55
+ pathComponents: [],
56
+ };
57
+ }
58
+ };
59
+ exports.getHostnameAndPathComponents = getHostnameAndPathComponents;
39
60
  /**
40
61
  * Determines which diffs are applicable to the listState, then applies those diffs.
41
62
  *
@@ -62,13 +83,24 @@ const applyDiffs = (listState, hotlistDiffs, listKey, recentlyAddedC2Domains = [
62
83
  fuzzylist: new Set(listState.fuzzylist),
63
84
  c2DomainBlocklist: new Set(listState.c2DomainBlocklist),
64
85
  };
86
+ // deep copy of blocklistPaths to avoid mutating the original
87
+ const newBlocklistPaths = (0, PathTrie_1.deepCopyPathTrie)(listState.blocklistPaths);
65
88
  for (const { isRemoval, targetList, url, timestamp } of diffsToApply) {
66
89
  const targetListType = splitStringByPeriod(targetList)[1];
67
90
  if (timestamp > latestDiffTimestamp) {
68
91
  latestDiffTimestamp = timestamp;
69
92
  }
70
93
  if (isRemoval) {
71
- listSets[targetListType].delete(url);
94
+ if (targetListType === 'blocklistPaths') {
95
+ (0, PathTrie_1.deleteFromTrie)(url, newBlocklistPaths);
96
+ }
97
+ else {
98
+ listSets[targetListType].delete(url);
99
+ }
100
+ continue;
101
+ }
102
+ if (targetListType === 'blocklistPaths') {
103
+ (0, PathTrie_1.insertToTrie)(url, newBlocklistPaths);
72
104
  }
73
105
  else {
74
106
  listSets[targetListType].add(url);
@@ -87,6 +119,7 @@ const applyDiffs = (listState, hotlistDiffs, listKey, recentlyAddedC2Domains = [
87
119
  allowlist: Array.from(listSets.allowlist),
88
120
  blocklist: Array.from(listSets.blocklist),
89
121
  fuzzylist: Array.from(listSets.fuzzylist),
122
+ blocklistPaths: newBlocklistPaths,
90
123
  version: listState.version,
91
124
  name: PhishingController_1.phishingListKeyNameMap[listKey],
92
125
  tolerance: listState.tolerance,
@@ -125,12 +158,7 @@ exports.validateConfig = validateConfig;
125
158
  * @returns the list of domain parts.
126
159
  */
127
160
  const domainToParts = (domain) => {
128
- try {
129
- return domain.split('.').reverse();
130
- }
131
- catch (e) {
132
- throw new Error(JSON.stringify(domain));
133
- }
161
+ return domain.split('.').reverse();
134
162
  };
135
163
  exports.domainToParts = domainToParts;
136
164
  /**
@@ -140,7 +168,14 @@ exports.domainToParts = domainToParts;
140
168
  * @returns the list of domain parts.
141
169
  */
142
170
  const processDomainList = (list) => {
143
- return list.map(exports.domainToParts);
171
+ return list.reduce((acc, domain) => {
172
+ if (typeof domain !== 'string') {
173
+ console.warn(`Invalid domain value in list: ${JSON.stringify(domain)}`);
174
+ return acc;
175
+ }
176
+ acc.push((0, exports.domainToParts)(domain));
177
+ return acc;
178
+ }, []);
144
179
  };
145
180
  exports.processDomainList = processDomainList;
146
181
  /**
@@ -149,17 +184,20 @@ exports.processDomainList = processDomainList;
149
184
  * @param override - the optional override for the configuration.
150
185
  * @param override.allowlist - the optional allowlist to override.
151
186
  * @param override.blocklist - the optional blocklist to override.
152
- * @param override.c2DomainBlocklist - the optional c2DomainBlocklist to override.
153
187
  * @param override.fuzzylist - the optional fuzzylist to override.
154
188
  * @param override.tolerance - the optional tolerance to override.
155
189
  * @returns the default phishing detector configuration.
156
190
  */
157
- const getDefaultPhishingDetectorConfig = ({ allowlist = [], blocklist = [], fuzzylist = [], tolerance = DEFAULT_TOLERANCE, }) => ({
158
- allowlist: (0, exports.processDomainList)(allowlist),
159
- blocklist: (0, exports.processDomainList)(blocklist),
160
- fuzzylist: (0, exports.processDomainList)(fuzzylist),
161
- tolerance,
162
- });
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
+ };
163
201
  exports.getDefaultPhishingDetectorConfig = getDefaultPhishingDetectorConfig;
164
202
  /**
165
203
  * Processes the configurations for the phishing detector, filtering out any invalid configs.
@@ -276,6 +314,16 @@ const getHostnameFromWebUrl = (url) => {
276
314
  return [hostname || '', Boolean(hostname)];
277
315
  };
278
316
  exports.getHostnameFromWebUrl = getHostnameFromWebUrl;
317
+ const getPathnameFromUrl = (url) => {
318
+ try {
319
+ const { pathname } = new URL(url);
320
+ return pathname;
321
+ }
322
+ catch {
323
+ return '';
324
+ }
325
+ };
326
+ exports.getPathnameFromUrl = getPathnameFromUrl;
279
327
  /**
280
328
  * Generates all possible parent domains up to a specified limit.
281
329
  *
@@ -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 type { Hotlist, PhishingListState } from './PhishingController';\nimport { ListKeys, phishingListKeyNameMap } from './PhishingController';\nimport type {\n PhishingDetectorList,\n PhishingDetectorConfiguration,\n} from './PhishingDetector';\nimport {\n DEFAULT_CHAIN_ID_TO_NAME,\n type TokenScanCacheData,\n type TokenScanResult,\n} from './types';\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\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 for (const { isRemoval, targetList, url, timestamp } of diffsToApply) {\n const targetListType = splitStringByPeriod(targetList)[1];\n if (timestamp > latestDiffTimestamp) {\n latestDiffTimestamp = timestamp;\n }\n if (isRemoval) {\n listSets[targetListType].delete(url);\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 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 try {\n return domain.split('.').reverse();\n } catch (e) {\n throw new Error(JSON.stringify(domain));\n }\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[]) => {\n return list.map(domainToParts);\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.c2DomainBlocklist - the optional c2DomainBlocklist 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 c2DomainBlocklist?: string[];\n fuzzylist?: string[];\n tolerance?: number;\n}): PhishingDetectorConfiguration => ({\n allowlist: processDomainList(allowlist),\n blocklist: processDomainList(blocklist),\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 * 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 * @returns The cache key.\n */\nexport const buildCacheKey = (chainId: string, address: string) => {\n return `${chainId.toLowerCase()}:${address.toLowerCase()}`;\n};\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 * 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 * @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): {\n cachedResults: Record<string, TokenScanResult>;\n tokensToFetch: string[];\n} => {\n const cachedResults: Record<string, TokenScanResult> = {};\n const tokensToFetch: string[] = [];\n\n for (const addr of tokens) {\n const normalizedAddr = addr.toLowerCase();\n const key = buildCacheKey(chainId, normalizedAddr);\n const hit = cache.get(key);\n if (hit) {\n cachedResults[normalizedAddr] = {\n result_type: hit.result_type,\n chain: chainId,\n address: normalizedAddr,\n };\n } else {\n tokensToFetch.push(normalizedAddr);\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';\nimport type { Hotlist, PhishingListState } from './PhishingController';\nimport { ListKeys, phishingListKeyNameMap } from './PhishingController';\nimport type {\n PhishingDetectorList,\n PhishingDetectorConfiguration,\n} from './PhishingDetector';\nimport {\n DEFAULT_CHAIN_ID_TO_NAME,\n type TokenScanCacheData,\n type TokenScanResult,\n} from './types';\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\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 * @returns The cache key.\n */\nexport const buildCacheKey = (chainId: string, address: string) => {\n return `${chainId.toLowerCase()}:${address.toLowerCase()}`;\n};\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 * 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 * @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): {\n cachedResults: Record<string, TokenScanResult>;\n tokensToFetch: string[];\n} => {\n const cachedResults: Record<string, TokenScanResult> = {};\n const tokensToFetch: string[] = [];\n\n for (const addr of tokens) {\n const normalizedAddr = addr.toLowerCase();\n const key = buildCacheKey(chainId, normalizedAddr);\n const hit = cache.get(key);\n if (hit) {\n cachedResults[normalizedAddr] = {\n result_type: hit.result_type,\n chain: chainId,\n address: normalizedAddr,\n };\n } else {\n tokensToFetch.push(normalizedAddr);\n }\n }\n\n return { cachedResults, tokensToFetch };\n};\n"]}
package/dist/utils.d.cts CHANGED
@@ -15,6 +15,10 @@ export declare const fetchTimeNow: () => number;
15
15
  * @returns The rounded Unix timestamp.
16
16
  */
17
17
  export declare function roundToNearestMinute(unixTimestamp: number): number;
18
+ export declare const getHostnameAndPathComponents: (url: string) => {
19
+ hostname: string;
20
+ pathComponents: string[];
21
+ };
18
22
  /**
19
23
  * Determines which diffs are applicable to the listState, then applies those diffs.
20
24
  *
@@ -53,7 +57,6 @@ export declare const processDomainList: (list: string[]) => string[][];
53
57
  * @param override - the optional override for the configuration.
54
58
  * @param override.allowlist - the optional allowlist to override.
55
59
  * @param override.blocklist - the optional blocklist to override.
56
- * @param override.c2DomainBlocklist - the optional c2DomainBlocklist to override.
57
60
  * @param override.fuzzylist - the optional fuzzylist to override.
58
61
  * @param override.tolerance - the optional tolerance to override.
59
62
  * @returns the default phishing detector configuration.
@@ -61,7 +64,6 @@ export declare const processDomainList: (list: string[]) => string[][];
61
64
  export declare const getDefaultPhishingDetectorConfig: ({ allowlist, blocklist, fuzzylist, tolerance, }: {
62
65
  allowlist?: string[] | undefined;
63
66
  blocklist?: string[] | undefined;
64
- c2DomainBlocklist?: string[] | undefined;
65
67
  fuzzylist?: string[] | undefined;
66
68
  tolerance?: number | undefined;
67
69
  }) => PhishingDetectorConfiguration;
@@ -121,6 +123,7 @@ export declare const getHostnameFromUrl: (url: string) => string | null;
121
123
  * getHostnameFromWebUrl('') // Returns: ['', false]
122
124
  */
123
125
  export declare const getHostnameFromWebUrl: (url: string) => [string, boolean];
126
+ export declare const getPathnameFromUrl: (url: string) => string;
124
127
  /**
125
128
  * Generates all possible parent domains up to a specified limit.
126
129
  *
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.cts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,iCAA6B;AACvE,OAAO,EAAE,QAAQ,EAA0B,iCAA6B;AACxE,OAAO,KAAK,EACV,oBAAoB,EACpB,6BAA6B,EAC9B,+BAA2B;AAC5B,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACrB,oBAAgB;AAIjB;;;;GAIG;AACH,eAAO,MAAM,YAAY,QAAO,MAAuC,CAAC;AAExE;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAElE;AAkBD;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,cACV,iBAAiB,kCAEnB,QAAQ,2BACO,MAAM,EAAE,6BACN,MAAM,EAAE,KACjC,iBAoDF,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,MAAM,IAAI,iBAAiB,CAuBrC;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,WAAY,MAAM,aAM3C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,SAAU,MAAM,EAAE,eAE/C,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gCAAgC;;;;;;MAWzC,6BAKF,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,aAChB,oBAAoB,EAAE,KAC9B,6BAA6B,EAe/B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,gBAAiB,MAAM,EAAE,WAExD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,gBAAiB,MAAM,EAAE,WAE3D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,WAAY,MAAM,EAAE,QAAQ,MAAM,EAAE,EAAE,yBASvE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,aAAc,MAAM,KAAG,MAG7C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,QAAS,MAAM,KAAG,MAAM,GAAG,IAYzD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,QAAS,MAAM,KAAG,CAAC,MAAM,EAAE,OAAO,CAUnE,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB,gBACnB,MAAM,EAAE,qBAEpB,MAAM,EA2BR,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,YAAa,MAAM,WAAW,MAAM,WAE7D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,YAClB,MAAM;;;;;;;;;;;;;;;;;;;;;;;MAEd,MAAM,GAAG,IAEX,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;eACL,MAAM,KAAK,kBAAkB,GAAG,SAAS;YACpD,MAAM,UACP,MAAM,EAAE;mBAED,OAAO,MAAM,EAAE,eAAe,CAAC;mBAC/B,MAAM,EAAE;CAqBxB,CAAC"}
1
+ {"version":3,"file":"utils.d.cts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,iCAA6B;AACvE,OAAO,EAAE,QAAQ,EAA0B,iCAA6B;AACxE,OAAO,KAAK,EACV,oBAAoB,EACpB,6BAA6B,EAC9B,+BAA2B;AAC5B,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACrB,oBAAgB;AAIjB;;;;GAIG;AACH,eAAO,MAAM,YAAY,QAAO,MAAuC,CAAC;AAExE;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAElE;AAkBD,eAAO,MAAM,4BAA4B,QAClC,MAAM,KACV;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAiB9C,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,UAAU,cACV,iBAAiB,kCAEnB,QAAQ,2BACO,MAAM,EAAE,6BACN,MAAM,EAAE,KACjC,iBAmEF,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,MAAM,IAAI,iBAAiB,CAuBrC;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,WAAY,MAAM,aAE3C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,SAAU,MAAM,EAAE,KAAG,MAAM,EAAE,EAS1D,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,gCAAgC;;;;;MAUzC,6BASH,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,aAChB,oBAAoB,EAAE,KAC9B,6BAA6B,EAe/B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,gBAAiB,MAAM,EAAE,WAExD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,gBAAiB,MAAM,EAAE,WAE3D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,WAAY,MAAM,EAAE,QAAQ,MAAM,EAAE,EAAE,yBASvE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,aAAc,MAAM,KAAG,MAG7C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,QAAS,MAAM,KAAG,MAAM,GAAG,IAYzD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,QAAS,MAAM,KAAG,CAAC,MAAM,EAAE,OAAO,CAUnE,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAAS,MAAM,KAAG,MAOhD,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB,gBACnB,MAAM,EAAE,qBAEpB,MAAM,EA2BR,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,YAAa,MAAM,WAAW,MAAM,WAE7D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,YAClB,MAAM;;;;;;;;;;;;;;;;;;;;;;;MAEd,MAAM,GAAG,IAEX,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;eACL,MAAM,KAAK,kBAAkB,GAAG,SAAS;YACpD,MAAM,UACP,MAAM,EAAE;mBAED,OAAO,MAAM,EAAE,eAAe,CAAC;mBAC/B,MAAM,EAAE;CAqBxB,CAAC"}