@metamask-previews/phishing-controller 12.2.0-preview-9e22b9bb → 12.3.0-preview-6bb6f553
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -1
- package/dist/PhishingDetector.cjs +21 -10
- package/dist/PhishingDetector.cjs.map +1 -1
- package/dist/PhishingDetector.d.cts.map +1 -1
- package/dist/PhishingDetector.d.mts.map +1 -1
- package/dist/PhishingDetector.mjs +22 -11
- package/dist/PhishingDetector.mjs.map +1 -1
- package/dist/utils.cjs +39 -1
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +17 -0
- package/dist/utils.d.cts.map +1 -1
- package/dist/utils.d.mts +17 -0
- package/dist/utils.d.mts.map +1 -1
- package/dist/utils.mjs +37 -0
- package/dist/utils.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [12.3.0]
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed extension performance issues ([#4853](https://github.com/MetaMask/core/pull/4853))
|
|
15
|
+
|
|
10
16
|
## [12.2.0]
|
|
11
17
|
|
|
12
18
|
### Changed
|
|
@@ -299,7 +305,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
299
305
|
|
|
300
306
|
All changes listed after this point were applied to this package following the monorepo conversion.
|
|
301
307
|
|
|
302
|
-
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@12.
|
|
308
|
+
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@12.3.0...HEAD
|
|
309
|
+
[12.3.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@12.2.0...@metamask/phishing-controller@12.3.0
|
|
303
310
|
[12.2.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@12.1.0...@metamask/phishing-controller@12.2.0
|
|
304
311
|
[12.1.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@12.0.3...@metamask/phishing-controller@12.1.0
|
|
305
312
|
[12.0.3]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@12.0.2...@metamask/phishing-controller@12.0.3
|
|
@@ -85,21 +85,18 @@ class PhishingDetector {
|
|
|
85
85
|
* @returns An object indicating if the URL is blocked and relevant metadata.
|
|
86
86
|
*/
|
|
87
87
|
isMaliciousC2Domain(urlString) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
hostname = new URL(urlString).hostname;
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
88
|
+
const hostname = (0, utils_1.getHostnameFromUrl)(urlString);
|
|
89
|
+
if (!hostname) {
|
|
93
90
|
return {
|
|
94
91
|
result: false,
|
|
95
92
|
type: types_1.PhishingDetectorResultType.C2DomainBlocklist,
|
|
96
93
|
};
|
|
97
94
|
}
|
|
98
95
|
const fqdn = hostname.endsWith('.') ? hostname.slice(0, -1) : hostname;
|
|
99
|
-
const
|
|
96
|
+
const sourceParts = (0, utils_1.domainToParts)(fqdn);
|
|
100
97
|
for (const { allowlist, name, version } of __classPrivateFieldGet(this, _PhishingDetector_configs, "f")) {
|
|
101
98
|
// if source matches allowlist hostname (or subdomain thereof), PASS
|
|
102
|
-
const allowlistMatch = (0, utils_1.matchPartsAgainstList)(
|
|
99
|
+
const allowlistMatch = (0, utils_1.matchPartsAgainstList)(sourceParts, allowlist);
|
|
103
100
|
if (allowlistMatch) {
|
|
104
101
|
const match = (0, utils_1.domainPartsToDomain)(allowlistMatch);
|
|
105
102
|
return {
|
|
@@ -111,10 +108,13 @@ class PhishingDetector {
|
|
|
111
108
|
};
|
|
112
109
|
}
|
|
113
110
|
}
|
|
111
|
+
const hostnameHash = (0, utils_1.sha256Hash)(hostname.toLowerCase());
|
|
112
|
+
const domainsToCheck = (0, utils_1.generateParentDomains)(sourceParts.reverse(), 5);
|
|
114
113
|
for (const { c2DomainBlocklist, name, version } of __classPrivateFieldGet(this, _PhishingDetector_configs, "f")) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
if (!c2DomainBlocklist || c2DomainBlocklist.length === 0) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (c2DomainBlocklist.includes(hostnameHash)) {
|
|
118
118
|
return {
|
|
119
119
|
name,
|
|
120
120
|
result: true,
|
|
@@ -122,6 +122,17 @@ class PhishingDetector {
|
|
|
122
122
|
version: version === undefined ? version : String(version),
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
|
+
for (const domain of domainsToCheck) {
|
|
126
|
+
const domainHash = (0, utils_1.sha256Hash)(domain);
|
|
127
|
+
if (c2DomainBlocklist.includes(domainHash)) {
|
|
128
|
+
return {
|
|
129
|
+
name,
|
|
130
|
+
result: true,
|
|
131
|
+
type: types_1.PhishingDetectorResultType.C2DomainBlocklist,
|
|
132
|
+
version: version === undefined ? version : String(version),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
125
136
|
}
|
|
126
137
|
// did not match, PASS
|
|
127
138
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhishingDetector.cjs","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"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","sourcesContent":["import { distance } from 'fastest-levenshtein';\n\nimport {\n PhishingDetectorResultType,\n type PhishingDetectorResult,\n} from './types';\nimport {\n domainPartsToDomain,\n domainPartsToFuzzyForm,\n domainToParts,\n getDefaultPhishingDetectorConfig,\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 let hostname;\n try {\n hostname = new URL(urlString).hostname;\n } catch (error) {\n return {\n result: false,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n };\n }\n\n const fqdn = hostname.endsWith('.') ? hostname.slice(0, -1) : hostname;\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 { c2DomainBlocklist, name, version } of this.#configs) {\n const hash = sha256Hash(hostname.toLowerCase());\n const blocked = c2DomainBlocklist?.includes(hash) ?? false;\n\n if (blocked) {\n return {\n name,\n result: true,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n version: version === undefined ? version : String(version),\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.cjs","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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhishingDetector.d.cts","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,oBAAgB;
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhishingDetector.d.mts","sourceRoot":"","sources":["../src/PhishingDetector.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,oBAAgB;
|
|
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"}
|
|
@@ -12,7 +12,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
12
12
|
var _PhishingDetector_instances, _PhishingDetector_configs, _PhishingDetector_legacyConfig, _PhishingDetector_check;
|
|
13
13
|
import { distance } from "fastest-levenshtein";
|
|
14
14
|
import { PhishingDetectorResultType } from "./types.mjs";
|
|
15
|
-
import { domainPartsToDomain, domainPartsToFuzzyForm, domainToParts, getDefaultPhishingDetectorConfig, matchPartsAgainstList, processConfigs, sha256Hash } from "./utils.mjs";
|
|
15
|
+
import { domainPartsToDomain, domainPartsToFuzzyForm, domainToParts, generateParentDomains, getDefaultPhishingDetectorConfig, getHostnameFromUrl, matchPartsAgainstList, processConfigs, sha256Hash } from "./utils.mjs";
|
|
16
16
|
export class PhishingDetector {
|
|
17
17
|
/**
|
|
18
18
|
* Construct a phishing detector, which can check whether origins are known
|
|
@@ -82,21 +82,18 @@ export class PhishingDetector {
|
|
|
82
82
|
* @returns An object indicating if the URL is blocked and relevant metadata.
|
|
83
83
|
*/
|
|
84
84
|
isMaliciousC2Domain(urlString) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
hostname = new URL(urlString).hostname;
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
85
|
+
const hostname = getHostnameFromUrl(urlString);
|
|
86
|
+
if (!hostname) {
|
|
90
87
|
return {
|
|
91
88
|
result: false,
|
|
92
89
|
type: PhishingDetectorResultType.C2DomainBlocklist,
|
|
93
90
|
};
|
|
94
91
|
}
|
|
95
92
|
const fqdn = hostname.endsWith('.') ? hostname.slice(0, -1) : hostname;
|
|
96
|
-
const
|
|
93
|
+
const sourceParts = domainToParts(fqdn);
|
|
97
94
|
for (const { allowlist, name, version } of __classPrivateFieldGet(this, _PhishingDetector_configs, "f")) {
|
|
98
95
|
// if source matches allowlist hostname (or subdomain thereof), PASS
|
|
99
|
-
const allowlistMatch = matchPartsAgainstList(
|
|
96
|
+
const allowlistMatch = matchPartsAgainstList(sourceParts, allowlist);
|
|
100
97
|
if (allowlistMatch) {
|
|
101
98
|
const match = domainPartsToDomain(allowlistMatch);
|
|
102
99
|
return {
|
|
@@ -108,10 +105,13 @@ export class PhishingDetector {
|
|
|
108
105
|
};
|
|
109
106
|
}
|
|
110
107
|
}
|
|
108
|
+
const hostnameHash = sha256Hash(hostname.toLowerCase());
|
|
109
|
+
const domainsToCheck = generateParentDomains(sourceParts.reverse(), 5);
|
|
111
110
|
for (const { c2DomainBlocklist, name, version } of __classPrivateFieldGet(this, _PhishingDetector_configs, "f")) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
if (!c2DomainBlocklist || c2DomainBlocklist.length === 0) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (c2DomainBlocklist.includes(hostnameHash)) {
|
|
115
115
|
return {
|
|
116
116
|
name,
|
|
117
117
|
result: true,
|
|
@@ -119,6 +119,17 @@ export class PhishingDetector {
|
|
|
119
119
|
version: version === undefined ? version : String(version),
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
|
+
for (const domain of domainsToCheck) {
|
|
123
|
+
const domainHash = sha256Hash(domain);
|
|
124
|
+
if (c2DomainBlocklist.includes(domainHash)) {
|
|
125
|
+
return {
|
|
126
|
+
name,
|
|
127
|
+
result: true,
|
|
128
|
+
type: PhishingDetectorResultType.C2DomainBlocklist,
|
|
129
|
+
version: version === undefined ? version : String(version),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
122
133
|
}
|
|
123
134
|
// did not match, PASS
|
|
124
135
|
return {
|
|
@@ -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 getDefaultPhishingDetectorConfig,\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 let hostname;\n try {\n hostname = new URL(urlString).hostname;\n } catch (error) {\n return {\n result: false,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n };\n }\n\n const fqdn = hostname.endsWith('.') ? hostname.slice(0, -1) : hostname;\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 { c2DomainBlocklist, name, version } of this.#configs) {\n const hash = sha256Hash(hostname.toLowerCase());\n const blocked = c2DomainBlocklist?.includes(hash) ?? false;\n\n if (blocked) {\n return {\n name,\n result: true,\n type: PhishingDetectorResultType.C2DomainBlocklist,\n version: version === undefined ? version : String(version),\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 {\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"]}
|
package/dist/utils.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
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.generateParentDomains = 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;
|
|
4
4
|
const utils_1 = require("@noble/hashes/utils");
|
|
5
5
|
const sha256_1 = require("ethereum-cryptography/sha256");
|
|
6
6
|
const PhishingController_1 = require("./PhishingController.cjs");
|
|
@@ -250,4 +250,42 @@ const getHostnameFromUrl = (url) => {
|
|
|
250
250
|
return hostname;
|
|
251
251
|
};
|
|
252
252
|
exports.getHostnameFromUrl = getHostnameFromUrl;
|
|
253
|
+
/**
|
|
254
|
+
* Generates all possible parent domains up to a specified limit.
|
|
255
|
+
*
|
|
256
|
+
* @param sourceParts - The list of domain parts in normal order (e.g., ['evil', 'domain', 'co', 'uk']).
|
|
257
|
+
* @param limit - The maximum number of parent domains to generate (default is 5).
|
|
258
|
+
* @returns An array of parent domains starting from the base TLD to the most specific subdomain.
|
|
259
|
+
* @example
|
|
260
|
+
* generateParentDomains(['evil', 'domain', 'co', 'uk'], 5)
|
|
261
|
+
* // Returns: ['co.uk', 'domain.co.uk', 'evil.domain.co.uk']
|
|
262
|
+
*
|
|
263
|
+
* generateParentDomains(['uk'], 5)
|
|
264
|
+
* // Returns: ['uk']
|
|
265
|
+
*
|
|
266
|
+
* generateParentDomains(['sub', 'example', 'com'], 5)
|
|
267
|
+
* // Returns: ['example.com', 'sub.example.com']
|
|
268
|
+
*/
|
|
269
|
+
const generateParentDomains = (sourceParts, limit = 5) => {
|
|
270
|
+
const domains = [];
|
|
271
|
+
if (sourceParts.length === 0) {
|
|
272
|
+
return domains;
|
|
273
|
+
}
|
|
274
|
+
if (sourceParts.length === 1) {
|
|
275
|
+
// Single-segment hostname (e.g., 'uk')
|
|
276
|
+
domains.push(sourceParts[0].toLowerCase());
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
// Start with the base domain or TLD (last two labels, e.g., 'co.uk' or 'example.com')
|
|
280
|
+
const baseDomain = sourceParts.slice(-2).join('.');
|
|
281
|
+
domains.push(baseDomain.toLowerCase());
|
|
282
|
+
// Iteratively add one subdomain level at a time, up to the specified limit
|
|
283
|
+
for (let i = sourceParts.length - 3; i >= 0 && domains.length < limit; i--) {
|
|
284
|
+
const domain = sourceParts.slice(i).join('.');
|
|
285
|
+
domains.push(domain.toLowerCase());
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return domains;
|
|
289
|
+
};
|
|
290
|
+
exports.generateParentDomains = generateParentDomains;
|
|
253
291
|
//# sourceMappingURL=utils.cjs.map
|
package/dist/utils.cjs.map
CHANGED
|
@@ -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';\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 } catch (error) {\n return null;\n }\n return hostname;\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 type { Hotlist, PhishingListState } from './PhishingController';\nimport { ListKeys, phishingListKeyNameMap } from './PhishingController';\nimport type {\n PhishingDetectorList,\n PhishingDetectorConfiguration,\n} from './PhishingDetector';\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 } catch (error) {\n return null;\n }\n return 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"]}
|
package/dist/utils.d.cts
CHANGED
|
@@ -107,4 +107,21 @@ export declare const sha256Hash: (hostname: string) => string;
|
|
|
107
107
|
* @returns The hostname extracted from the URL, or null if the URL is invalid.
|
|
108
108
|
*/
|
|
109
109
|
export declare const getHostnameFromUrl: (url: string) => string | null;
|
|
110
|
+
/**
|
|
111
|
+
* Generates all possible parent domains up to a specified limit.
|
|
112
|
+
*
|
|
113
|
+
* @param sourceParts - The list of domain parts in normal order (e.g., ['evil', 'domain', 'co', 'uk']).
|
|
114
|
+
* @param limit - The maximum number of parent domains to generate (default is 5).
|
|
115
|
+
* @returns An array of parent domains starting from the base TLD to the most specific subdomain.
|
|
116
|
+
* @example
|
|
117
|
+
* generateParentDomains(['evil', 'domain', 'co', 'uk'], 5)
|
|
118
|
+
* // Returns: ['co.uk', 'domain.co.uk', 'evil.domain.co.uk']
|
|
119
|
+
*
|
|
120
|
+
* generateParentDomains(['uk'], 5)
|
|
121
|
+
* // Returns: ['uk']
|
|
122
|
+
*
|
|
123
|
+
* generateParentDomains(['sub', 'example', 'com'], 5)
|
|
124
|
+
* // Returns: ['example.com', 'sub.example.com']
|
|
125
|
+
*/
|
|
126
|
+
export declare const generateParentDomains: (sourceParts: string[], limit?: number) => string[];
|
|
110
127
|
//# sourceMappingURL=utils.d.cts.map
|
package/dist/utils.d.cts.map
CHANGED
|
@@ -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;AAI5B;;;;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,IAQzD,CAAC"}
|
|
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;AAI5B;;;;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,IAQzD,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB,gBACnB,MAAM,EAAE,qBAEpB,MAAM,EA2BR,CAAC"}
|
package/dist/utils.d.mts
CHANGED
|
@@ -107,4 +107,21 @@ export declare const sha256Hash: (hostname: string) => string;
|
|
|
107
107
|
* @returns The hostname extracted from the URL, or null if the URL is invalid.
|
|
108
108
|
*/
|
|
109
109
|
export declare const getHostnameFromUrl: (url: string) => string | null;
|
|
110
|
+
/**
|
|
111
|
+
* Generates all possible parent domains up to a specified limit.
|
|
112
|
+
*
|
|
113
|
+
* @param sourceParts - The list of domain parts in normal order (e.g., ['evil', 'domain', 'co', 'uk']).
|
|
114
|
+
* @param limit - The maximum number of parent domains to generate (default is 5).
|
|
115
|
+
* @returns An array of parent domains starting from the base TLD to the most specific subdomain.
|
|
116
|
+
* @example
|
|
117
|
+
* generateParentDomains(['evil', 'domain', 'co', 'uk'], 5)
|
|
118
|
+
* // Returns: ['co.uk', 'domain.co.uk', 'evil.domain.co.uk']
|
|
119
|
+
*
|
|
120
|
+
* generateParentDomains(['uk'], 5)
|
|
121
|
+
* // Returns: ['uk']
|
|
122
|
+
*
|
|
123
|
+
* generateParentDomains(['sub', 'example', 'com'], 5)
|
|
124
|
+
* // Returns: ['example.com', 'sub.example.com']
|
|
125
|
+
*/
|
|
126
|
+
export declare const generateParentDomains: (sourceParts: string[], limit?: number) => string[];
|
|
110
127
|
//# sourceMappingURL=utils.d.mts.map
|
package/dist/utils.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.mts","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;AAI5B;;;;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,IAQzD,CAAC"}
|
|
1
|
+
{"version":3,"file":"utils.d.mts","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;AAI5B;;;;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,IAQzD,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,qBAAqB,gBACnB,MAAM,EAAE,qBAEpB,MAAM,EA2BR,CAAC"}
|
package/dist/utils.mjs
CHANGED
|
@@ -234,4 +234,41 @@ export const getHostnameFromUrl = (url) => {
|
|
|
234
234
|
}
|
|
235
235
|
return hostname;
|
|
236
236
|
};
|
|
237
|
+
/**
|
|
238
|
+
* Generates all possible parent domains up to a specified limit.
|
|
239
|
+
*
|
|
240
|
+
* @param sourceParts - The list of domain parts in normal order (e.g., ['evil', 'domain', 'co', 'uk']).
|
|
241
|
+
* @param limit - The maximum number of parent domains to generate (default is 5).
|
|
242
|
+
* @returns An array of parent domains starting from the base TLD to the most specific subdomain.
|
|
243
|
+
* @example
|
|
244
|
+
* generateParentDomains(['evil', 'domain', 'co', 'uk'], 5)
|
|
245
|
+
* // Returns: ['co.uk', 'domain.co.uk', 'evil.domain.co.uk']
|
|
246
|
+
*
|
|
247
|
+
* generateParentDomains(['uk'], 5)
|
|
248
|
+
* // Returns: ['uk']
|
|
249
|
+
*
|
|
250
|
+
* generateParentDomains(['sub', 'example', 'com'], 5)
|
|
251
|
+
* // Returns: ['example.com', 'sub.example.com']
|
|
252
|
+
*/
|
|
253
|
+
export const generateParentDomains = (sourceParts, limit = 5) => {
|
|
254
|
+
const domains = [];
|
|
255
|
+
if (sourceParts.length === 0) {
|
|
256
|
+
return domains;
|
|
257
|
+
}
|
|
258
|
+
if (sourceParts.length === 1) {
|
|
259
|
+
// Single-segment hostname (e.g., 'uk')
|
|
260
|
+
domains.push(sourceParts[0].toLowerCase());
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
// Start with the base domain or TLD (last two labels, e.g., 'co.uk' or 'example.com')
|
|
264
|
+
const baseDomain = sourceParts.slice(-2).join('.');
|
|
265
|
+
domains.push(baseDomain.toLowerCase());
|
|
266
|
+
// Iteratively add one subdomain level at a time, up to the specified limit
|
|
267
|
+
for (let i = sourceParts.length - 3; i >= 0 && domains.length < limit; i--) {
|
|
268
|
+
const domain = sourceParts.slice(i).join('.');
|
|
269
|
+
domains.push(domain.toLowerCase());
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return domains;
|
|
273
|
+
};
|
|
237
274
|
//# sourceMappingURL=utils.mjs.map
|
package/dist/utils.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex } from '@noble/hashes/utils';\nimport { sha256 } from 'ethereum-cryptography/sha256';\n\nimport type { Hotlist, PhishingListState } from './PhishingController';\nimport { ListKeys, phishingListKeyNameMap } from './PhishingController';\nimport type {\n PhishingDetectorList,\n PhishingDetectorConfiguration,\n} from './PhishingDetector';\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 } catch (error) {\n return null;\n }\n return hostname;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"utils.mjs","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex } from '@noble/hashes/utils';\nimport { sha256 } from 'ethereum-cryptography/sha256';\n\nimport type { Hotlist, PhishingListState } from './PhishingController';\nimport { ListKeys, phishingListKeyNameMap } from './PhishingController';\nimport type {\n PhishingDetectorList,\n PhishingDetectorConfiguration,\n} from './PhishingDetector';\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 } catch (error) {\n return null;\n }\n return 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"]}
|
package/package.json
CHANGED