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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.0.0...HEAD
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 { whitelist } = this.state;
645
- if (whitelist.includes(hostname || punycodeOrigin)) {
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
- ...eth_phishing_detect_config,
808
- ...partialState,
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
  : [],