@metamask-previews/phishing-controller 14.0.0-preview-bc6087f6 → 14.1.0-preview-622f3f09
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 +16 -1
- package/dist/PathTrie.cjs +169 -0
- package/dist/PathTrie.cjs.map +1 -0
- package/dist/PathTrie.d.cts +46 -0
- package/dist/PathTrie.d.cts.map +1 -0
- package/dist/PathTrie.d.mts +46 -0
- package/dist/PathTrie.d.mts.map +1 -0
- package/dist/PathTrie.mjs +160 -0
- package/dist/PathTrie.mjs.map +1 -0
- package/dist/PhishingController.cjs +33 -6
- package/dist/PhishingController.cjs.map +1 -1
- package/dist/PhishingController.d.cts +24 -10
- package/dist/PhishingController.d.cts.map +1 -1
- package/dist/PhishingController.d.mts +24 -10
- package/dist/PhishingController.d.mts.map +1 -1
- package/dist/PhishingController.mjs +34 -7
- package/dist/PhishingController.mjs.map +1 -1
- package/dist/PhishingDetector.cjs +36 -3
- package/dist/PhishingDetector.cjs.map +1 -1
- package/dist/PhishingDetector.d.cts +10 -1
- package/dist/PhishingDetector.d.cts.map +1 -1
- package/dist/PhishingDetector.d.mts +10 -1
- package/dist/PhishingDetector.d.mts.map +1 -1
- package/dist/PhishingDetector.mjs +36 -3
- package/dist/PhishingDetector.mjs.map +1 -1
- package/dist/tests/utils.cjs +1 -1
- package/dist/tests/utils.cjs.map +1 -1
- package/dist/tests/utils.mjs +1 -1
- package/dist/tests/utils.mjs.map +1 -1
- package/dist/utils.cjs +64 -16
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +5 -2
- package/dist/utils.d.cts.map +1 -1
- package/dist/utils.d.mts +5 -2
- package/dist/utils.d.mts.map +1 -1
- package/dist/utils.mjs +61 -15
- package/dist/utils.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
+
## [14.1.0]
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Add path-based blocking [#6416](https://github.com/MetaMask/core/pull/6416)
|
15
|
+
- Add `blocklistPaths` to `PhishingDetectorList`
|
16
|
+
- Add `blocklistPaths` to `PhishingDetectorConfiguration`
|
17
|
+
- Add `whitelistPaths` to `PhishingControllerState`
|
18
|
+
- Adds a type called PathTrie
|
19
|
+
|
20
|
+
### Fixed
|
21
|
+
|
22
|
+
- Fixed phishing detector initialization failure when domain lists contain invalid values (numbers, null, undefined) by filtering them out ([#6767](https://github.com/MetaMask/core/pull/6767))
|
23
|
+
|
10
24
|
## [14.0.0]
|
11
25
|
|
12
26
|
### Added
|
@@ -416,7 +430,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
416
430
|
|
417
431
|
All changes listed after this point were applied to this package following the monorepo conversion.
|
418
432
|
|
419
|
-
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@14.
|
433
|
+
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@14.1.0...HEAD
|
434
|
+
[14.1.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@14.0.0...@metamask/phishing-controller@14.1.0
|
420
435
|
[14.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@13.1.0...@metamask/phishing-controller@14.0.0
|
421
436
|
[13.1.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@13.0.0...@metamask/phishing-controller@13.1.0
|
422
437
|
[13.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@12.6.0...@metamask/phishing-controller@13.0.0
|
@@ -0,0 +1,169 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.deepCopyPathTrie = exports.convertListToTrie = exports.matchedPathPrefix = exports.deleteFromTrie = exports.insertToTrie = exports.isTerminal = void 0;
|
4
|
+
const utils_1 = require("./utils.cjs");
|
5
|
+
const isTerminal = (node) => {
|
6
|
+
if (!node || typeof node !== 'object') {
|
7
|
+
return false;
|
8
|
+
}
|
9
|
+
return Object.keys(node).length === 0;
|
10
|
+
};
|
11
|
+
exports.isTerminal = isTerminal;
|
12
|
+
/**
|
13
|
+
* Insert a URL into the trie.
|
14
|
+
*
|
15
|
+
* @param url - The URL to insert into the trie.
|
16
|
+
* @param pathTrie - The trie to insert the URL into.
|
17
|
+
*/
|
18
|
+
const insertToTrie = (url, pathTrie) => {
|
19
|
+
const { hostname, pathComponents } = (0, utils_1.getHostnameAndPathComponents)(url);
|
20
|
+
if (pathComponents.length === 0 || !hostname) {
|
21
|
+
return;
|
22
|
+
}
|
23
|
+
const lowerHostname = hostname.toLowerCase();
|
24
|
+
if (!pathTrie[lowerHostname]) {
|
25
|
+
pathTrie[lowerHostname] = {};
|
26
|
+
}
|
27
|
+
let curr = pathTrie[lowerHostname];
|
28
|
+
for (let i = 0; i < pathComponents.length; i++) {
|
29
|
+
const pathComponent = pathComponents[i];
|
30
|
+
const isLast = i === pathComponents.length - 1;
|
31
|
+
const exists = curr[pathComponent] !== undefined;
|
32
|
+
if (exists) {
|
33
|
+
if (!isLast && (0, exports.isTerminal)(curr[pathComponent])) {
|
34
|
+
return;
|
35
|
+
}
|
36
|
+
if (isLast) {
|
37
|
+
// Prune descendants if the current path component is not terminal
|
38
|
+
if (!(0, exports.isTerminal)(curr[pathComponent])) {
|
39
|
+
curr[pathComponent] = {};
|
40
|
+
}
|
41
|
+
return;
|
42
|
+
}
|
43
|
+
curr = curr[pathComponent];
|
44
|
+
continue;
|
45
|
+
}
|
46
|
+
if (isLast) {
|
47
|
+
curr[pathComponent] = {};
|
48
|
+
return;
|
49
|
+
}
|
50
|
+
const next = {};
|
51
|
+
curr[pathComponent] = next;
|
52
|
+
curr = next;
|
53
|
+
}
|
54
|
+
};
|
55
|
+
exports.insertToTrie = insertToTrie;
|
56
|
+
/**
|
57
|
+
* Delete a URL from the trie.
|
58
|
+
*
|
59
|
+
* @param url - The URL to delete from the trie.
|
60
|
+
* @param pathTrie - The trie to delete the URL from.
|
61
|
+
*/
|
62
|
+
const deleteFromTrie = (url, pathTrie) => {
|
63
|
+
const { hostname, pathComponents } = (0, utils_1.getHostnameAndPathComponents)(url);
|
64
|
+
const lowerHostname = hostname.toLowerCase();
|
65
|
+
if (pathComponents.length === 0 || !pathTrie[lowerHostname]) {
|
66
|
+
return;
|
67
|
+
}
|
68
|
+
const pathToNode = [
|
69
|
+
{ node: pathTrie, key: lowerHostname },
|
70
|
+
];
|
71
|
+
let curr = pathTrie[lowerHostname];
|
72
|
+
for (const pathComponent of pathComponents) {
|
73
|
+
if (!curr[pathComponent]) {
|
74
|
+
return;
|
75
|
+
}
|
76
|
+
pathToNode.push({ node: curr, key: pathComponent });
|
77
|
+
curr = curr[pathComponent];
|
78
|
+
}
|
79
|
+
const lastEntry = pathToNode[pathToNode.length - 1];
|
80
|
+
delete lastEntry.node[lastEntry.key];
|
81
|
+
for (let i = pathToNode.length - 2; i >= 0; i--) {
|
82
|
+
const { node, key } = pathToNode[i];
|
83
|
+
if ((0, exports.isTerminal)(node[key])) {
|
84
|
+
delete node[key];
|
85
|
+
}
|
86
|
+
else {
|
87
|
+
break;
|
88
|
+
}
|
89
|
+
}
|
90
|
+
};
|
91
|
+
exports.deleteFromTrie = deleteFromTrie;
|
92
|
+
/**
|
93
|
+
* Get the concatenated hostname and path components all the way down to the
|
94
|
+
* terminal node in the trie that is prefixed in the passed URL. It will only
|
95
|
+
* return a string if the terminal node in the trie is contained in the passed
|
96
|
+
* URL.
|
97
|
+
*
|
98
|
+
* @param url - The URL to check.
|
99
|
+
* @param pathTrie - The trie to check the URL in.
|
100
|
+
* @returns The matched path prefix, or null if no match is found.
|
101
|
+
*/
|
102
|
+
const matchedPathPrefix = (url, pathTrie) => {
|
103
|
+
const { hostname, pathComponents } = (0, utils_1.getHostnameAndPathComponents)(url);
|
104
|
+
const lowerHostname = hostname.toLowerCase();
|
105
|
+
if (pathComponents.length === 0 || !hostname || !pathTrie[lowerHostname]) {
|
106
|
+
return null;
|
107
|
+
}
|
108
|
+
let matchedPath = `${hostname}/`;
|
109
|
+
let curr = pathTrie[lowerHostname];
|
110
|
+
for (const pathComponent of pathComponents) {
|
111
|
+
if (!curr[pathComponent]) {
|
112
|
+
return null;
|
113
|
+
}
|
114
|
+
curr = curr[pathComponent];
|
115
|
+
// If we've reached a terminal node, then we can return the matched path.
|
116
|
+
if ((0, exports.isTerminal)(curr)) {
|
117
|
+
matchedPath += pathComponent;
|
118
|
+
return matchedPath;
|
119
|
+
}
|
120
|
+
matchedPath += `${pathComponent}/`;
|
121
|
+
}
|
122
|
+
return null;
|
123
|
+
};
|
124
|
+
exports.matchedPathPrefix = matchedPathPrefix;
|
125
|
+
/**
|
126
|
+
* Converts a list ofpaths into a PathTrie structure. This assumes that the
|
127
|
+
* entries are only hostname+pathname format.
|
128
|
+
*
|
129
|
+
* @param paths - Array of hostname+pathname
|
130
|
+
* @returns PathTrie structure for efficient path checking
|
131
|
+
*/
|
132
|
+
const convertListToTrie = (paths = []) => {
|
133
|
+
const pathTrie = {};
|
134
|
+
if (!paths || !Array.isArray(paths)) {
|
135
|
+
return pathTrie;
|
136
|
+
}
|
137
|
+
for (const path of paths) {
|
138
|
+
(0, exports.insertToTrie)(path, pathTrie);
|
139
|
+
}
|
140
|
+
return pathTrie;
|
141
|
+
};
|
142
|
+
exports.convertListToTrie = convertListToTrie;
|
143
|
+
/**
|
144
|
+
* Creates a deep copy of a PathNode structure.
|
145
|
+
*
|
146
|
+
* @param original - The original PathNode to copy.
|
147
|
+
* @returns A deep copy of the PathNode.
|
148
|
+
*/
|
149
|
+
const deepCopyPathNode = (original) => {
|
150
|
+
const copy = {};
|
151
|
+
for (const [key, childNode] of Object.entries(original)) {
|
152
|
+
copy[key] = deepCopyPathNode(childNode);
|
153
|
+
}
|
154
|
+
return copy;
|
155
|
+
};
|
156
|
+
/**
|
157
|
+
* Creates a deep copy of a PathTrie structure.
|
158
|
+
*
|
159
|
+
* @param original - The original PathTrie to copy.
|
160
|
+
* @returns A deep copy of the PathTrie.
|
161
|
+
*/
|
162
|
+
const deepCopyPathTrie = (original) => {
|
163
|
+
if (!original) {
|
164
|
+
return {};
|
165
|
+
}
|
166
|
+
return deepCopyPathNode(original);
|
167
|
+
};
|
168
|
+
exports.deepCopyPathTrie = deepCopyPathTrie;
|
169
|
+
//# sourceMappingURL=PathTrie.cjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"PathTrie.cjs","sourceRoot":"","sources":["../src/PathTrie.ts"],"names":[],"mappings":"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","sourcesContent":["import { getHostnameAndPathComponents } from './utils';\n\nexport type PathNode = {\n [key: string]: PathNode;\n};\n\nexport type PathTrie = Record<string, PathNode>;\n\nexport const isTerminal = (node: PathNode | undefined): boolean => {\n if (!node || typeof node !== 'object') {\n return false;\n }\n return Object.keys(node).length === 0;\n};\n\n/**\n * Insert a URL into the trie.\n *\n * @param url - The URL to insert into the trie.\n * @param pathTrie - The trie to insert the URL into.\n */\nexport const insertToTrie = (url: string, pathTrie: PathTrie) => {\n const { hostname, pathComponents } = getHostnameAndPathComponents(url);\n\n if (pathComponents.length === 0 || !hostname) {\n return;\n }\n\n const lowerHostname = hostname.toLowerCase();\n if (!pathTrie[lowerHostname]) {\n pathTrie[lowerHostname] = {} as PathNode;\n }\n\n let curr: PathNode = pathTrie[lowerHostname];\n for (let i = 0; i < pathComponents.length; i++) {\n const pathComponent = pathComponents[i];\n const isLast = i === pathComponents.length - 1;\n const exists = curr[pathComponent] !== undefined;\n\n if (exists) {\n if (!isLast && isTerminal(curr[pathComponent])) {\n return;\n }\n\n if (isLast) {\n // Prune descendants if the current path component is not terminal\n if (!isTerminal(curr[pathComponent])) {\n curr[pathComponent] = {};\n }\n return;\n }\n curr = curr[pathComponent];\n continue;\n }\n\n if (isLast) {\n curr[pathComponent] = {};\n return;\n }\n const next: PathNode = {};\n curr[pathComponent] = next;\n curr = next;\n }\n};\n\n/**\n * Delete a URL from the trie.\n *\n * @param url - The URL to delete from the trie.\n * @param pathTrie - The trie to delete the URL from.\n */\nexport const deleteFromTrie = (url: string, pathTrie: PathTrie) => {\n const { hostname, pathComponents } = getHostnameAndPathComponents(url);\n\n const lowerHostname = hostname.toLowerCase();\n if (pathComponents.length === 0 || !pathTrie[lowerHostname]) {\n return;\n }\n\n const pathToNode: { node: PathNode; key: string }[] = [\n { node: pathTrie, key: lowerHostname },\n ];\n let curr: PathNode = pathTrie[lowerHostname];\n for (const pathComponent of pathComponents) {\n if (!curr[pathComponent]) {\n return;\n }\n\n pathToNode.push({ node: curr, key: pathComponent });\n curr = curr[pathComponent];\n }\n\n const lastEntry = pathToNode[pathToNode.length - 1];\n delete lastEntry.node[lastEntry.key];\n for (let i = pathToNode.length - 2; i >= 0; i--) {\n const { node, key } = pathToNode[i];\n if (isTerminal(node[key])) {\n delete node[key];\n } else {\n break;\n }\n }\n};\n\n/**\n * Get the concatenated hostname and path components all the way down to the\n * terminal node in the trie that is prefixed in the passed URL. It will only\n * return a string if the terminal node in the trie is contained in the passed\n * URL.\n *\n * @param url - The URL to check.\n * @param pathTrie - The trie to check the URL in.\n * @returns The matched path prefix, or null if no match is found.\n */\nexport const matchedPathPrefix = (\n url: string,\n pathTrie: PathTrie,\n): string | null => {\n const { hostname, pathComponents } = getHostnameAndPathComponents(url);\n\n const lowerHostname = hostname.toLowerCase();\n if (pathComponents.length === 0 || !hostname || !pathTrie[lowerHostname]) {\n return null;\n }\n\n let matchedPath = `${hostname}/`;\n let curr: PathNode = pathTrie[lowerHostname];\n for (const pathComponent of pathComponents) {\n if (!curr[pathComponent]) {\n return null;\n }\n curr = curr[pathComponent];\n // If we've reached a terminal node, then we can return the matched path.\n if (isTerminal(curr)) {\n matchedPath += pathComponent;\n return matchedPath;\n }\n matchedPath += `${pathComponent}/`;\n }\n return null;\n};\n\n/**\n * Converts a list ofpaths into a PathTrie structure. This assumes that the\n * entries are only hostname+pathname format.\n *\n * @param paths - Array of hostname+pathname\n * @returns PathTrie structure for efficient path checking\n */\nexport const convertListToTrie = (paths: string[] = []): PathTrie => {\n const pathTrie: PathTrie = {};\n if (!paths || !Array.isArray(paths)) {\n return pathTrie;\n }\n for (const path of paths) {\n insertToTrie(path, pathTrie);\n }\n return pathTrie;\n};\n\n/**\n * Creates a deep copy of a PathNode structure.\n *\n * @param original - The original PathNode to copy.\n * @returns A deep copy of the PathNode.\n */\nconst deepCopyPathNode = (original: PathNode): PathNode => {\n const copy: PathNode = {};\n\n for (const [key, childNode] of Object.entries(original)) {\n copy[key] = deepCopyPathNode(childNode);\n }\n\n return copy;\n};\n\n/**\n * Creates a deep copy of a PathTrie structure.\n *\n * @param original - The original PathTrie to copy.\n * @returns A deep copy of the PathTrie.\n */\nexport const deepCopyPathTrie = (\n original: PathTrie | undefined | null,\n): PathTrie => {\n if (!original) {\n return {};\n }\n return deepCopyPathNode(original) as PathTrie;\n};\n"]}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
export type PathNode = {
|
2
|
+
[key: string]: PathNode;
|
3
|
+
};
|
4
|
+
export type PathTrie = Record<string, PathNode>;
|
5
|
+
export declare const isTerminal: (node: PathNode | undefined) => boolean;
|
6
|
+
/**
|
7
|
+
* Insert a URL into the trie.
|
8
|
+
*
|
9
|
+
* @param url - The URL to insert into the trie.
|
10
|
+
* @param pathTrie - The trie to insert the URL into.
|
11
|
+
*/
|
12
|
+
export declare const insertToTrie: (url: string, pathTrie: PathTrie) => void;
|
13
|
+
/**
|
14
|
+
* Delete a URL from the trie.
|
15
|
+
*
|
16
|
+
* @param url - The URL to delete from the trie.
|
17
|
+
* @param pathTrie - The trie to delete the URL from.
|
18
|
+
*/
|
19
|
+
export declare const deleteFromTrie: (url: string, pathTrie: PathTrie) => void;
|
20
|
+
/**
|
21
|
+
* Get the concatenated hostname and path components all the way down to the
|
22
|
+
* terminal node in the trie that is prefixed in the passed URL. It will only
|
23
|
+
* return a string if the terminal node in the trie is contained in the passed
|
24
|
+
* URL.
|
25
|
+
*
|
26
|
+
* @param url - The URL to check.
|
27
|
+
* @param pathTrie - The trie to check the URL in.
|
28
|
+
* @returns The matched path prefix, or null if no match is found.
|
29
|
+
*/
|
30
|
+
export declare const matchedPathPrefix: (url: string, pathTrie: PathTrie) => string | null;
|
31
|
+
/**
|
32
|
+
* Converts a list ofpaths into a PathTrie structure. This assumes that the
|
33
|
+
* entries are only hostname+pathname format.
|
34
|
+
*
|
35
|
+
* @param paths - Array of hostname+pathname
|
36
|
+
* @returns PathTrie structure for efficient path checking
|
37
|
+
*/
|
38
|
+
export declare const convertListToTrie: (paths?: string[]) => PathTrie;
|
39
|
+
/**
|
40
|
+
* Creates a deep copy of a PathTrie structure.
|
41
|
+
*
|
42
|
+
* @param original - The original PathTrie to copy.
|
43
|
+
* @returns A deep copy of the PathTrie.
|
44
|
+
*/
|
45
|
+
export declare const deepCopyPathTrie: (original: PathTrie | undefined | null) => PathTrie;
|
46
|
+
//# sourceMappingURL=PathTrie.d.cts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"PathTrie.d.cts","sourceRoot":"","sources":["../src/PathTrie.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAEhD,eAAO,MAAM,UAAU,SAAU,QAAQ,GAAG,SAAS,KAAG,OAKvD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,QAAS,MAAM,YAAY,QAAQ,SA0C3D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAAS,MAAM,YAAY,QAAQ,SA+B7D,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,QACvB,MAAM,YACD,QAAQ,KACjB,MAAM,GAAG,IAuBX,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,WAAW,MAAM,EAAE,KAAQ,QASxD,CAAC;AAkBF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,aACjB,QAAQ,GAAG,SAAS,GAAG,IAAI,KACpC,QAKF,CAAC"}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
export type PathNode = {
|
2
|
+
[key: string]: PathNode;
|
3
|
+
};
|
4
|
+
export type PathTrie = Record<string, PathNode>;
|
5
|
+
export declare const isTerminal: (node: PathNode | undefined) => boolean;
|
6
|
+
/**
|
7
|
+
* Insert a URL into the trie.
|
8
|
+
*
|
9
|
+
* @param url - The URL to insert into the trie.
|
10
|
+
* @param pathTrie - The trie to insert the URL into.
|
11
|
+
*/
|
12
|
+
export declare const insertToTrie: (url: string, pathTrie: PathTrie) => void;
|
13
|
+
/**
|
14
|
+
* Delete a URL from the trie.
|
15
|
+
*
|
16
|
+
* @param url - The URL to delete from the trie.
|
17
|
+
* @param pathTrie - The trie to delete the URL from.
|
18
|
+
*/
|
19
|
+
export declare const deleteFromTrie: (url: string, pathTrie: PathTrie) => void;
|
20
|
+
/**
|
21
|
+
* Get the concatenated hostname and path components all the way down to the
|
22
|
+
* terminal node in the trie that is prefixed in the passed URL. It will only
|
23
|
+
* return a string if the terminal node in the trie is contained in the passed
|
24
|
+
* URL.
|
25
|
+
*
|
26
|
+
* @param url - The URL to check.
|
27
|
+
* @param pathTrie - The trie to check the URL in.
|
28
|
+
* @returns The matched path prefix, or null if no match is found.
|
29
|
+
*/
|
30
|
+
export declare const matchedPathPrefix: (url: string, pathTrie: PathTrie) => string | null;
|
31
|
+
/**
|
32
|
+
* Converts a list ofpaths into a PathTrie structure. This assumes that the
|
33
|
+
* entries are only hostname+pathname format.
|
34
|
+
*
|
35
|
+
* @param paths - Array of hostname+pathname
|
36
|
+
* @returns PathTrie structure for efficient path checking
|
37
|
+
*/
|
38
|
+
export declare const convertListToTrie: (paths?: string[]) => PathTrie;
|
39
|
+
/**
|
40
|
+
* Creates a deep copy of a PathTrie structure.
|
41
|
+
*
|
42
|
+
* @param original - The original PathTrie to copy.
|
43
|
+
* @returns A deep copy of the PathTrie.
|
44
|
+
*/
|
45
|
+
export declare const deepCopyPathTrie: (original: PathTrie | undefined | null) => PathTrie;
|
46
|
+
//# sourceMappingURL=PathTrie.d.mts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"PathTrie.d.mts","sourceRoot":"","sources":["../src/PathTrie.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAEhD,eAAO,MAAM,UAAU,SAAU,QAAQ,GAAG,SAAS,KAAG,OAKvD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,QAAS,MAAM,YAAY,QAAQ,SA0C3D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,QAAS,MAAM,YAAY,QAAQ,SA+B7D,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,QACvB,MAAM,YACD,QAAQ,KACjB,MAAM,GAAG,IAuBX,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,WAAW,MAAM,EAAE,KAAQ,QASxD,CAAC;AAkBF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,aACjB,QAAQ,GAAG,SAAS,GAAG,IAAI,KACpC,QAKF,CAAC"}
|
@@ -0,0 +1,160 @@
|
|
1
|
+
import { getHostnameAndPathComponents } from "./utils.mjs";
|
2
|
+
export const isTerminal = (node) => {
|
3
|
+
if (!node || typeof node !== 'object') {
|
4
|
+
return false;
|
5
|
+
}
|
6
|
+
return Object.keys(node).length === 0;
|
7
|
+
};
|
8
|
+
/**
|
9
|
+
* Insert a URL into the trie.
|
10
|
+
*
|
11
|
+
* @param url - The URL to insert into the trie.
|
12
|
+
* @param pathTrie - The trie to insert the URL into.
|
13
|
+
*/
|
14
|
+
export const insertToTrie = (url, pathTrie) => {
|
15
|
+
const { hostname, pathComponents } = getHostnameAndPathComponents(url);
|
16
|
+
if (pathComponents.length === 0 || !hostname) {
|
17
|
+
return;
|
18
|
+
}
|
19
|
+
const lowerHostname = hostname.toLowerCase();
|
20
|
+
if (!pathTrie[lowerHostname]) {
|
21
|
+
pathTrie[lowerHostname] = {};
|
22
|
+
}
|
23
|
+
let curr = pathTrie[lowerHostname];
|
24
|
+
for (let i = 0; i < pathComponents.length; i++) {
|
25
|
+
const pathComponent = pathComponents[i];
|
26
|
+
const isLast = i === pathComponents.length - 1;
|
27
|
+
const exists = curr[pathComponent] !== undefined;
|
28
|
+
if (exists) {
|
29
|
+
if (!isLast && isTerminal(curr[pathComponent])) {
|
30
|
+
return;
|
31
|
+
}
|
32
|
+
if (isLast) {
|
33
|
+
// Prune descendants if the current path component is not terminal
|
34
|
+
if (!isTerminal(curr[pathComponent])) {
|
35
|
+
curr[pathComponent] = {};
|
36
|
+
}
|
37
|
+
return;
|
38
|
+
}
|
39
|
+
curr = curr[pathComponent];
|
40
|
+
continue;
|
41
|
+
}
|
42
|
+
if (isLast) {
|
43
|
+
curr[pathComponent] = {};
|
44
|
+
return;
|
45
|
+
}
|
46
|
+
const next = {};
|
47
|
+
curr[pathComponent] = next;
|
48
|
+
curr = next;
|
49
|
+
}
|
50
|
+
};
|
51
|
+
/**
|
52
|
+
* Delete a URL from the trie.
|
53
|
+
*
|
54
|
+
* @param url - The URL to delete from the trie.
|
55
|
+
* @param pathTrie - The trie to delete the URL from.
|
56
|
+
*/
|
57
|
+
export const deleteFromTrie = (url, pathTrie) => {
|
58
|
+
const { hostname, pathComponents } = getHostnameAndPathComponents(url);
|
59
|
+
const lowerHostname = hostname.toLowerCase();
|
60
|
+
if (pathComponents.length === 0 || !pathTrie[lowerHostname]) {
|
61
|
+
return;
|
62
|
+
}
|
63
|
+
const pathToNode = [
|
64
|
+
{ node: pathTrie, key: lowerHostname },
|
65
|
+
];
|
66
|
+
let curr = pathTrie[lowerHostname];
|
67
|
+
for (const pathComponent of pathComponents) {
|
68
|
+
if (!curr[pathComponent]) {
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
pathToNode.push({ node: curr, key: pathComponent });
|
72
|
+
curr = curr[pathComponent];
|
73
|
+
}
|
74
|
+
const lastEntry = pathToNode[pathToNode.length - 1];
|
75
|
+
delete lastEntry.node[lastEntry.key];
|
76
|
+
for (let i = pathToNode.length - 2; i >= 0; i--) {
|
77
|
+
const { node, key } = pathToNode[i];
|
78
|
+
if (isTerminal(node[key])) {
|
79
|
+
delete node[key];
|
80
|
+
}
|
81
|
+
else {
|
82
|
+
break;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
};
|
86
|
+
/**
|
87
|
+
* Get the concatenated hostname and path components all the way down to the
|
88
|
+
* terminal node in the trie that is prefixed in the passed URL. It will only
|
89
|
+
* return a string if the terminal node in the trie is contained in the passed
|
90
|
+
* URL.
|
91
|
+
*
|
92
|
+
* @param url - The URL to check.
|
93
|
+
* @param pathTrie - The trie to check the URL in.
|
94
|
+
* @returns The matched path prefix, or null if no match is found.
|
95
|
+
*/
|
96
|
+
export const matchedPathPrefix = (url, pathTrie) => {
|
97
|
+
const { hostname, pathComponents } = getHostnameAndPathComponents(url);
|
98
|
+
const lowerHostname = hostname.toLowerCase();
|
99
|
+
if (pathComponents.length === 0 || !hostname || !pathTrie[lowerHostname]) {
|
100
|
+
return null;
|
101
|
+
}
|
102
|
+
let matchedPath = `${hostname}/`;
|
103
|
+
let curr = pathTrie[lowerHostname];
|
104
|
+
for (const pathComponent of pathComponents) {
|
105
|
+
if (!curr[pathComponent]) {
|
106
|
+
return null;
|
107
|
+
}
|
108
|
+
curr = curr[pathComponent];
|
109
|
+
// If we've reached a terminal node, then we can return the matched path.
|
110
|
+
if (isTerminal(curr)) {
|
111
|
+
matchedPath += pathComponent;
|
112
|
+
return matchedPath;
|
113
|
+
}
|
114
|
+
matchedPath += `${pathComponent}/`;
|
115
|
+
}
|
116
|
+
return null;
|
117
|
+
};
|
118
|
+
/**
|
119
|
+
* Converts a list ofpaths into a PathTrie structure. This assumes that the
|
120
|
+
* entries are only hostname+pathname format.
|
121
|
+
*
|
122
|
+
* @param paths - Array of hostname+pathname
|
123
|
+
* @returns PathTrie structure for efficient path checking
|
124
|
+
*/
|
125
|
+
export const convertListToTrie = (paths = []) => {
|
126
|
+
const pathTrie = {};
|
127
|
+
if (!paths || !Array.isArray(paths)) {
|
128
|
+
return pathTrie;
|
129
|
+
}
|
130
|
+
for (const path of paths) {
|
131
|
+
insertToTrie(path, pathTrie);
|
132
|
+
}
|
133
|
+
return pathTrie;
|
134
|
+
};
|
135
|
+
/**
|
136
|
+
* Creates a deep copy of a PathNode structure.
|
137
|
+
*
|
138
|
+
* @param original - The original PathNode to copy.
|
139
|
+
* @returns A deep copy of the PathNode.
|
140
|
+
*/
|
141
|
+
const deepCopyPathNode = (original) => {
|
142
|
+
const copy = {};
|
143
|
+
for (const [key, childNode] of Object.entries(original)) {
|
144
|
+
copy[key] = deepCopyPathNode(childNode);
|
145
|
+
}
|
146
|
+
return copy;
|
147
|
+
};
|
148
|
+
/**
|
149
|
+
* Creates a deep copy of a PathTrie structure.
|
150
|
+
*
|
151
|
+
* @param original - The original PathTrie to copy.
|
152
|
+
* @returns A deep copy of the PathTrie.
|
153
|
+
*/
|
154
|
+
export const deepCopyPathTrie = (original) => {
|
155
|
+
if (!original) {
|
156
|
+
return {};
|
157
|
+
}
|
158
|
+
return deepCopyPathNode(original);
|
159
|
+
};
|
160
|
+
//# sourceMappingURL=PathTrie.mjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"PathTrie.mjs","sourceRoot":"","sources":["../src/PathTrie.ts"],"names":[],"mappings":"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","sourcesContent":["import { getHostnameAndPathComponents } from './utils';\n\nexport type PathNode = {\n [key: string]: PathNode;\n};\n\nexport type PathTrie = Record<string, PathNode>;\n\nexport const isTerminal = (node: PathNode | undefined): boolean => {\n if (!node || typeof node !== 'object') {\n return false;\n }\n return Object.keys(node).length === 0;\n};\n\n/**\n * Insert a URL into the trie.\n *\n * @param url - The URL to insert into the trie.\n * @param pathTrie - The trie to insert the URL into.\n */\nexport const insertToTrie = (url: string, pathTrie: PathTrie) => {\n const { hostname, pathComponents } = getHostnameAndPathComponents(url);\n\n if (pathComponents.length === 0 || !hostname) {\n return;\n }\n\n const lowerHostname = hostname.toLowerCase();\n if (!pathTrie[lowerHostname]) {\n pathTrie[lowerHostname] = {} as PathNode;\n }\n\n let curr: PathNode = pathTrie[lowerHostname];\n for (let i = 0; i < pathComponents.length; i++) {\n const pathComponent = pathComponents[i];\n const isLast = i === pathComponents.length - 1;\n const exists = curr[pathComponent] !== undefined;\n\n if (exists) {\n if (!isLast && isTerminal(curr[pathComponent])) {\n return;\n }\n\n if (isLast) {\n // Prune descendants if the current path component is not terminal\n if (!isTerminal(curr[pathComponent])) {\n curr[pathComponent] = {};\n }\n return;\n }\n curr = curr[pathComponent];\n continue;\n }\n\n if (isLast) {\n curr[pathComponent] = {};\n return;\n }\n const next: PathNode = {};\n curr[pathComponent] = next;\n curr = next;\n }\n};\n\n/**\n * Delete a URL from the trie.\n *\n * @param url - The URL to delete from the trie.\n * @param pathTrie - The trie to delete the URL from.\n */\nexport const deleteFromTrie = (url: string, pathTrie: PathTrie) => {\n const { hostname, pathComponents } = getHostnameAndPathComponents(url);\n\n const lowerHostname = hostname.toLowerCase();\n if (pathComponents.length === 0 || !pathTrie[lowerHostname]) {\n return;\n }\n\n const pathToNode: { node: PathNode; key: string }[] = [\n { node: pathTrie, key: lowerHostname },\n ];\n let curr: PathNode = pathTrie[lowerHostname];\n for (const pathComponent of pathComponents) {\n if (!curr[pathComponent]) {\n return;\n }\n\n pathToNode.push({ node: curr, key: pathComponent });\n curr = curr[pathComponent];\n }\n\n const lastEntry = pathToNode[pathToNode.length - 1];\n delete lastEntry.node[lastEntry.key];\n for (let i = pathToNode.length - 2; i >= 0; i--) {\n const { node, key } = pathToNode[i];\n if (isTerminal(node[key])) {\n delete node[key];\n } else {\n break;\n }\n }\n};\n\n/**\n * Get the concatenated hostname and path components all the way down to the\n * terminal node in the trie that is prefixed in the passed URL. It will only\n * return a string if the terminal node in the trie is contained in the passed\n * URL.\n *\n * @param url - The URL to check.\n * @param pathTrie - The trie to check the URL in.\n * @returns The matched path prefix, or null if no match is found.\n */\nexport const matchedPathPrefix = (\n url: string,\n pathTrie: PathTrie,\n): string | null => {\n const { hostname, pathComponents } = getHostnameAndPathComponents(url);\n\n const lowerHostname = hostname.toLowerCase();\n if (pathComponents.length === 0 || !hostname || !pathTrie[lowerHostname]) {\n return null;\n }\n\n let matchedPath = `${hostname}/`;\n let curr: PathNode = pathTrie[lowerHostname];\n for (const pathComponent of pathComponents) {\n if (!curr[pathComponent]) {\n return null;\n }\n curr = curr[pathComponent];\n // If we've reached a terminal node, then we can return the matched path.\n if (isTerminal(curr)) {\n matchedPath += pathComponent;\n return matchedPath;\n }\n matchedPath += `${pathComponent}/`;\n }\n return null;\n};\n\n/**\n * Converts a list ofpaths into a PathTrie structure. This assumes that the\n * entries are only hostname+pathname format.\n *\n * @param paths - Array of hostname+pathname\n * @returns PathTrie structure for efficient path checking\n */\nexport const convertListToTrie = (paths: string[] = []): PathTrie => {\n const pathTrie: PathTrie = {};\n if (!paths || !Array.isArray(paths)) {\n return pathTrie;\n }\n for (const path of paths) {\n insertToTrie(path, pathTrie);\n }\n return pathTrie;\n};\n\n/**\n * Creates a deep copy of a PathNode structure.\n *\n * @param original - The original PathNode to copy.\n * @returns A deep copy of the PathNode.\n */\nconst deepCopyPathNode = (original: PathNode): PathNode => {\n const copy: PathNode = {};\n\n for (const [key, childNode] of Object.entries(original)) {\n copy[key] = deepCopyPathNode(childNode);\n }\n\n return copy;\n};\n\n/**\n * Creates a deep copy of a PathTrie structure.\n *\n * @param original - The original PathTrie to copy.\n * @returns A deep copy of the PathTrie.\n */\nexport const deepCopyPathTrie = (\n original: PathTrie | undefined | null,\n): PathTrie => {\n if (!original) {\n return {};\n }\n return deepCopyPathNode(original) as PathTrie;\n};\n"]}
|
@@ -17,6 +17,7 @@ const base_controller_1 = require("@metamask/base-controller");
|
|
17
17
|
const controller_utils_1 = require("@metamask/controller-utils");
|
18
18
|
const punycode_js_1 = require("punycode/punycode.js");
|
19
19
|
const CacheManager_1 = require("./CacheManager.cjs");
|
20
|
+
const PathTrie_1 = require("./PathTrie.cjs");
|
20
21
|
const PhishingDetector_1 = require("./PhishingDetector.cjs");
|
21
22
|
const types_1 = require("./types.cjs");
|
22
23
|
const utils_1 = require("./utils.cjs");
|
@@ -84,6 +85,12 @@ const metadata = {
|
|
84
85
|
anonymous: false,
|
85
86
|
usedInUi: false,
|
86
87
|
},
|
88
|
+
whitelistPaths: {
|
89
|
+
includeInStateLogs: false,
|
90
|
+
persist: true,
|
91
|
+
anonymous: false,
|
92
|
+
usedInUi: false,
|
93
|
+
},
|
87
94
|
hotlistLastFetched: {
|
88
95
|
includeInStateLogs: true,
|
89
96
|
persist: true,
|
@@ -117,12 +124,14 @@ const metadata = {
|
|
117
124
|
};
|
118
125
|
/**
|
119
126
|
* Get a default empty state for the controller.
|
127
|
+
*
|
120
128
|
* @returns The default empty state.
|
121
129
|
*/
|
122
130
|
const getDefaultState = () => {
|
123
131
|
return {
|
124
132
|
phishingLists: [],
|
125
133
|
whitelist: [],
|
134
|
+
whitelistPaths: {},
|
126
135
|
hotlistLastFetched: 0,
|
127
136
|
stalelistLastFetched: 0,
|
128
137
|
c2DomainBlocklistLastFetched: 0,
|
@@ -610,6 +619,10 @@ class PhishingController extends base_controller_1.BaseController {
|
|
610
619
|
test(origin) {
|
611
620
|
const punycodeOrigin = (0, punycode_js_1.toASCII)(origin);
|
612
621
|
const hostname = (0, utils_1.getHostnameFromUrl)(punycodeOrigin);
|
622
|
+
const hostnameWithPaths = hostname + (0, utils_1.getPathnameFromUrl)(origin);
|
623
|
+
if ((0, PathTrie_1.matchedPathPrefix)(hostnameWithPaths, this.state.whitelistPaths)) {
|
624
|
+
return { result: false, type: types_1.PhishingDetectorResultType.All };
|
625
|
+
}
|
613
626
|
if (this.state.whitelist.includes(hostname || punycodeOrigin)) {
|
614
627
|
return { result: false, type: types_1.PhishingDetectorResultType.All }; // Same as whitelisted match returned by detector.check(...).
|
615
628
|
}
|
@@ -641,8 +654,19 @@ class PhishingController extends base_controller_1.BaseController {
|
|
641
654
|
bypass(origin) {
|
642
655
|
const punycodeOrigin = (0, punycode_js_1.toASCII)(origin);
|
643
656
|
const hostname = (0, utils_1.getHostnameFromUrl)(punycodeOrigin);
|
644
|
-
const
|
645
|
-
|
657
|
+
const hostnameWithPaths = hostname + (0, utils_1.getPathnameFromUrl)(origin);
|
658
|
+
const { whitelist, whitelistPaths } = this.state;
|
659
|
+
const whitelistPath = (0, PathTrie_1.matchedPathPrefix)(hostnameWithPaths, whitelistPaths);
|
660
|
+
if (whitelist.includes(hostname || punycodeOrigin) || whitelistPath) {
|
661
|
+
return;
|
662
|
+
}
|
663
|
+
// If the origin was blocked by a path, then we only want to add it to the whitelistPaths since
|
664
|
+
// other paths with the same hostname may not be blocked.
|
665
|
+
const blockingPath = __classPrivateFieldGet(this, _PhishingController_detector, "f").blockingPath(origin);
|
666
|
+
if (blockingPath) {
|
667
|
+
this.update((draftState) => {
|
668
|
+
(0, PathTrie_1.insertToTrie)(blockingPath, draftState.whitelistPaths);
|
669
|
+
});
|
646
670
|
return;
|
647
671
|
}
|
648
672
|
this.update((draftState) => {
|
@@ -801,11 +825,14 @@ async function _PhishingController_updateStalelist() {
|
|
801
825
|
if (!stalelistResponse || !hotlistDiffsResponse) {
|
802
826
|
return;
|
803
827
|
}
|
804
|
-
// TODO: Either fix this lint violation or explain why it's necessary to ignore.
|
805
|
-
const { eth_phishing_detect_config, ...partialState } = stalelistResponse.data;
|
806
828
|
const metamaskListState = {
|
807
|
-
|
808
|
-
|
829
|
+
allowlist: stalelistResponse.data.allowlist,
|
830
|
+
fuzzylist: stalelistResponse.data.fuzzylist,
|
831
|
+
tolerance: stalelistResponse.data.tolerance,
|
832
|
+
version: stalelistResponse.data.version,
|
833
|
+
lastUpdated: stalelistResponse.data.lastUpdated,
|
834
|
+
blocklist: stalelistResponse.data.blocklist,
|
835
|
+
blocklistPaths: (0, PathTrie_1.convertListToTrie)(stalelistResponse.data.blocklistPaths),
|
809
836
|
c2DomainBlocklist: c2DomainBlocklistResponse
|
810
837
|
? c2DomainBlocklistResponse.recentlyAdded
|
811
838
|
: [],
|