@metamask-previews/phishing-controller 6.0.0-preview.3dbf14f

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 ADDED
@@ -0,0 +1,87 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [6.0.0]
10
+ ### Changed
11
+ - **BREAKING:** Remove fallback phishing configuration ([#1527](https://github.com/MetaMask/core/pull/1527))
12
+ - The default configuration is now blank. A custom initial configuration can still be specified via the constructor to preserve the old behavior.
13
+
14
+ ## [5.0.0]
15
+ ### Changed
16
+ - **BREAKING:** Bump to Node 16 ([#1262](https://github.com/MetaMask/core/pull/1262))
17
+
18
+ ## [4.0.0]
19
+ ### Changed
20
+ - **BREAKING:** Switch to new phishing configuration API that returns a diff since the last update ([#1123](https://github.com/MetaMask/core/pull/1123))
21
+ - The "hotlist" has been replaced by a service that returns any configuration changes since the last update. This should reduce network traffic even further.
22
+ - The endpoints used are now `https://phishing-detection.metafi.codefi.network/v1/stalelist` and `https://phishing-detection.metafi.codefi.network/v1/diffsSince/:lastUpdated`
23
+ - **BREAKING:**: The phishing controller state now keeps the MetaMask and PhishFort configuration separate, allowing for proper attribution of each block ([#1123](https://github.com/MetaMask/core/pull/1123))
24
+ - The `listState` state property has been replaced with an array of phishing list state objects (one entry for MetaMask, one for PhishFort).
25
+ - The PhishFort config is deduplicated server-side, so it should have zero overlap with the MetaMask configuration (which helps reduce memory/disk usage)
26
+
27
+ ## [3.0.0]
28
+ ### Removed
29
+ - **BREAKING:** Remove `isomorphic-fetch` ([#1106](https://github.com/MetaMask/controllers/pull/1106))
30
+ - Consumers must now import `isomorphic-fetch` or another polyfill themselves if they are running in an environment without `fetch`
31
+
32
+ ## [2.0.0]
33
+ ### Changed
34
+ - **BREAKING:** Refactor to Cost-Optimized Phishing List Data Architecture. ([#1080](https://github.com/MetaMask/core/pull/1080))
35
+ - Rather than periodically downloading two separate configurations (MetaMask and Phishfort), we now download a combined "stalelist" and "hotlist". The stalelist is downloaded every 4 days, and the hotlist is downloaded every 30 minutes. The hotlist only includes data from the last 8 days, which should dramatically reduce the required network traffic for phishing config updates.
36
+ - When a site is blocked, we no longer know which list is responsible due to the combined format. We will need to come up with another way to attribute blocks to a specific list; this controller will no longer be responsible for that.
37
+ - This change includes the removal of the exports:
38
+ - `METAMASK_CONFIG_FILE` and `PHISHFORT_HOTLIST_FILE` (replaced by `METAMASK_STALELIST_FILE` and `METAMASK_HOTLIST_DIFF_FILE`)
39
+ - `METAMASK_CONFIG_URL` and `PHISHFORT_HOTLIST_URL` (replaced by `METAMASK_STALELIST_URL` and `METAMASK_HOTLIST_DIFF_URL`)
40
+ - `EthPhishingResponse` (replaced by `PhishingStalelist` for the API response and `PhishingListState` for the list in controller state, as they're now different)
41
+ - The configuration has changed:
42
+ - Instead of accepting a `refreshInterval`, we now accept a separate interval for the stalelist and hotlist (`stalelistRefreshInterval` and `hotlistRefreshInterval`)
43
+ - The controller state has been updated:
44
+ - The phishing list itself has been renamed from `phishing` to `listState`, and the shape has changed. Removing the old `phishing` state would be advised, as it will get replaced by an updated configuration immediately anyway.
45
+ - `lastFetched` has been replaced by `hotlistLastFetched` and `stalelistLastFetched`. The old `lastFetched` state can be removed as well (it never needed to be persisted anyway).
46
+ - The `setRefreshInterval` method has been replaced by `setStalelistRefreshInterval` and `setHotlistRefreshInterval`
47
+ - The `isOutOfDate` method has been replaced by `isStalelistOutOfDate` and `isHotlistOutOfDate`
48
+ - The `maybeUpdatePhishingLists` method has been replaced by `maybeUpdateState`
49
+ - The `updatePhishingLists` method has been replaced by `updateStalelist` and `updateHotlist`
50
+
51
+ ## [1.1.2]
52
+ ### Fixed
53
+ - Improve performance of phishing list update ([#1086](https://github.com/MetaMask/core/pull/1086))
54
+ - We now use a `Set` + `has` method instead of the array `includes` method for detecting overlap between phishing lists after an update.
55
+
56
+ ## [1.1.1]
57
+ ### Changed
58
+ - Rename this repository to `core` ([#1031](https://github.com/MetaMask/controllers/pull/1031))
59
+ - Update `@metamask/controller-utils` package ([#1041](https://github.com/MetaMask/controllers/pull/1041))
60
+
61
+ ## [1.1.0]
62
+ ### Added
63
+ - Add method to conditionally update the phishing lists ([#986](https://github.com/MetaMask/core/pull/986))
64
+
65
+ ### Changed
66
+ - Relax dependencies on `@metamask/base-controller` and `@metamask/controller-utils` (use `^` instead of `~`) ([#998](https://github.com/MetaMask/core/pull/998))
67
+ - Expose `lastFetched` in PhishingController state ([#986](https://github.com/MetaMask/core/pull/986))
68
+
69
+ ## [1.0.0]
70
+ ### Added
71
+ - Initial release
72
+ - As a result of converting our shared controllers repo into a monorepo ([#831](https://github.com/MetaMask/core/pull/831)), we've created this package from select parts of [`@metamask/controllers` v33.0.0](https://github.com/MetaMask/core/tree/v33.0.0), namely:
73
+ - `src/third-party/PhishingController.ts`
74
+ - `src/third-party/PhishingController.test.ts`
75
+
76
+ All changes listed after this point were applied to this package following the monorepo conversion.
77
+
78
+ [Unreleased]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@6.0.0...HEAD
79
+ [6.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@5.0.0...@metamask/phishing-controller@6.0.0
80
+ [5.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@4.0.0...@metamask/phishing-controller@5.0.0
81
+ [4.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@3.0.0...@metamask/phishing-controller@4.0.0
82
+ [3.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@2.0.0...@metamask/phishing-controller@3.0.0
83
+ [2.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@1.1.2...@metamask/phishing-controller@2.0.0
84
+ [1.1.2]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@1.1.1...@metamask/phishing-controller@1.1.2
85
+ [1.1.1]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@1.1.0...@metamask/phishing-controller@1.1.1
86
+ [1.1.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@1.0.0...@metamask/phishing-controller@1.1.0
87
+ [1.0.0]: https://github.com/MetaMask/core/releases/tag/@metamask/phishing-controller@1.0.0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 MetaMask
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # `@metamask/phishing-controller`
2
+
3
+ Maintains a periodically updated list of approved and unapproved website origins.
4
+
5
+ ## Installation
6
+
7
+ `yarn add @metamask/phishing-controller`
8
+
9
+ or
10
+
11
+ `npm install @metamask/phishing-controller`
12
+
13
+ ## Contributing
14
+
15
+ This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme).
@@ -0,0 +1,256 @@
1
+ import type { BaseConfig, BaseState } from '@metamask/base-controller';
2
+ import { BaseController } from '@metamask/base-controller';
3
+ /**
4
+ * @type ListTypes
5
+ *
6
+ * Type outlining the types of lists provided by aggregating different source lists
7
+ */
8
+ export declare type ListTypes = 'fuzzylist' | 'blocklist' | 'allowlist';
9
+ /**
10
+ * @type EthPhishingResponse
11
+ *
12
+ * Configuration response from the eth-phishing-detect package
13
+ * consisting of approved and unapproved website origins
14
+ * @property blacklist - List of unapproved origins
15
+ * @property fuzzylist - List of fuzzy-matched unapproved origins
16
+ * @property tolerance - Fuzzy match tolerance level
17
+ * @property version - Version number of this configuration
18
+ * @property whitelist - List of approved origins
19
+ */
20
+ export interface EthPhishingResponse {
21
+ blacklist: string[];
22
+ fuzzylist: string[];
23
+ tolerance: number;
24
+ version: number;
25
+ whitelist: string[];
26
+ }
27
+ /**
28
+ * @type PhishingStalelist
29
+ *
30
+ * Interface defining expected type of the stalelist.json file.
31
+ * @property eth_phishing_detect_config - Stale list sourced from eth-phishing-detect's config.json.
32
+ * @property phishfort_hotlist - Stale list sourced from phishfort's hotlist.json. Only includes blocklist. Deduplicated entries from eth_phishing_detect_config.
33
+ * @property tolerance - Fuzzy match tolerance level
34
+ * @property lastUpdated - Timestamp of last update.
35
+ * @property version - Stalelist data structure iteration.
36
+ */
37
+ export interface PhishingStalelist {
38
+ eth_phishing_detect_config: Record<ListTypes, string[]>;
39
+ phishfort_hotlist: Record<ListTypes, string[]>;
40
+ tolerance: number;
41
+ version: number;
42
+ lastUpdated: number;
43
+ }
44
+ /**
45
+ * @type PhishingListState
46
+ *
47
+ * Interface defining the persisted list state. This is the persisted state that is updated frequently with `this.maybeUpdateState()`.
48
+ * @property allowlist - List of approved origins (legacy naming "whitelist")
49
+ * @property blocklist - List of unapproved origins (legacy naming "blacklist")
50
+ * @property fuzzylist - List of fuzzy-matched unapproved origins
51
+ * @property tolerance - Fuzzy match tolerance level
52
+ * @property lastUpdated - Timestamp of last update.
53
+ * @property version - Version of the phishing list state.
54
+ * @property name - Name of the list. Used for attribution.
55
+ */
56
+ export interface PhishingListState {
57
+ allowlist: string[];
58
+ blocklist: string[];
59
+ fuzzylist: string[];
60
+ tolerance: number;
61
+ version: number;
62
+ lastUpdated: number;
63
+ name: ListNames;
64
+ }
65
+ /**
66
+ * @type EthPhishingDetectResult
67
+ *
68
+ * Interface that describes the result of the `test` method.
69
+ * @property name - Name of the config on which a match was found.
70
+ * @property version - Version of the config on which a match was found.
71
+ * @property result - Whether a domain was detected as a phishing domain. True means an unsafe domain.
72
+ * @property match - The matching fuzzylist origin when a fuzzylist match is found. Returned as undefined for non-fuzzy true results.
73
+ * @property type - The field of the config on which a match was found.
74
+ */
75
+ export interface EthPhishingDetectResult {
76
+ name?: string;
77
+ version?: string;
78
+ result: boolean;
79
+ match?: string;
80
+ type: 'all' | 'fuzzy' | 'blocklist' | 'allowlist';
81
+ }
82
+ /**
83
+ * @type HotlistDiff
84
+ *
85
+ * Interface defining the expected type of the diffs in hotlist.json file.
86
+ * @property url - Url of the diff entry.
87
+ * @property timestamp - Timestamp at which the diff was identified.
88
+ * @property targetList - The list name where the diff was identified.
89
+ * @property isRemoval - Was the diff identified a removal type.
90
+ */
91
+ export interface HotlistDiff {
92
+ url: string;
93
+ timestamp: number;
94
+ targetList: `${ListKeys}.${ListTypes}`;
95
+ isRemoval?: boolean;
96
+ }
97
+ export interface DataResultWrapper<T> {
98
+ data: T;
99
+ }
100
+ /**
101
+ * @type Hotlist
102
+ *
103
+ * Type defining expected hotlist.json file.
104
+ * @property url - Url of the diff entry.
105
+ * @property timestamp - Timestamp at which the diff was identified.
106
+ * @property targetList - The list name where the diff was identified.
107
+ * @property isRemoval - Was the diff identified a removal type.
108
+ */
109
+ export declare type Hotlist = HotlistDiff[];
110
+ /**
111
+ * @type PhishingConfig
112
+ *
113
+ * Phishing controller configuration
114
+ * @property stalelistRefreshInterval - Polling interval used to fetch stale list.
115
+ * @property hotlistRefreshInterval - Polling interval used to fetch hotlist diff list.
116
+ */
117
+ export interface PhishingConfig extends BaseConfig {
118
+ stalelistRefreshInterval: number;
119
+ hotlistRefreshInterval: number;
120
+ }
121
+ /**
122
+ * @type PhishingState
123
+ *
124
+ * Phishing controller state
125
+ * @property phishing - eth-phishing-detect configuration
126
+ * @property whitelist - array of temporarily-approved origins
127
+ */
128
+ export interface PhishingState extends BaseState {
129
+ phishingLists: PhishingListState[];
130
+ whitelist: string[];
131
+ hotlistLastFetched: number;
132
+ stalelistLastFetched: number;
133
+ }
134
+ export declare const PHISHING_CONFIG_BASE_URL = "https://phishing-detection.metafi.codefi.network";
135
+ export declare const METAMASK_STALELIST_FILE = "/v1/stalelist";
136
+ export declare const METAMASK_HOTLIST_DIFF_FILE = "/v1/diffsSince";
137
+ export declare const HOTLIST_REFRESH_INTERVAL: number;
138
+ export declare const STALELIST_REFRESH_INTERVAL: number;
139
+ export declare const METAMASK_STALELIST_URL: string;
140
+ export declare const METAMASK_HOTLIST_DIFF_URL: string;
141
+ /**
142
+ * Enum containing upstream data provider source list keys.
143
+ * These are the keys denoting lists consumed by the upstream data provider.
144
+ */
145
+ export declare enum ListKeys {
146
+ PhishfortHotlist = "phishfort_hotlist",
147
+ EthPhishingDetectConfig = "eth_phishing_detect_config"
148
+ }
149
+ /**
150
+ * Enum containing downstream client attribution names.
151
+ */
152
+ export declare enum ListNames {
153
+ MetaMask = "MetaMask",
154
+ Phishfort = "Phishfort"
155
+ }
156
+ /**
157
+ * Maps from list key sourced from upstream data
158
+ * provider to downstream client attribution name.
159
+ */
160
+ export declare const phishingListKeyNameMap: {
161
+ eth_phishing_detect_config: ListNames;
162
+ phishfort_hotlist: ListNames;
163
+ };
164
+ /**
165
+ * Controller that manages community-maintained lists of approved and unapproved website origins.
166
+ */
167
+ export declare class PhishingController extends BaseController<PhishingConfig, PhishingState> {
168
+ #private;
169
+ private detector;
170
+ /**
171
+ * Name of this controller used during composition
172
+ */
173
+ name: string;
174
+ /**
175
+ * Creates a PhishingController instance.
176
+ *
177
+ * @param config - Initial options used to configure this controller.
178
+ * @param state - Initial state to set on this controller.
179
+ */
180
+ constructor(config?: Partial<PhishingConfig>, state?: Partial<PhishingState>);
181
+ /**
182
+ * Updates this.detector with an instance of PhishingDetector using the current state.
183
+ */
184
+ updatePhishingDetector(): void;
185
+ /**
186
+ * Set the interval at which the stale phishing list will be refetched.
187
+ * Fetching will only occur on the next call to test/bypass.
188
+ * For immediate update to the phishing list, call {@link updateStalelist} directly.
189
+ *
190
+ * @param interval - the new interval, in ms.
191
+ */
192
+ setStalelistRefreshInterval(interval: number): void;
193
+ /**
194
+ * Set the interval at which the hot list will be refetched.
195
+ * Fetching will only occur on the next call to test/bypass.
196
+ * For immediate update to the phishing list, call {@link updateHotlist} directly.
197
+ *
198
+ * @param interval - the new interval, in ms.
199
+ */
200
+ setHotlistRefreshInterval(interval: number): void;
201
+ /**
202
+ * Determine if an update to the stalelist configuration is needed.
203
+ *
204
+ * @returns Whether an update is needed
205
+ */
206
+ isStalelistOutOfDate(): boolean;
207
+ /**
208
+ * Determine if an update to the hotlist configuration is needed.
209
+ *
210
+ * @returns Whether an update is needed
211
+ */
212
+ isHotlistOutOfDate(): boolean;
213
+ /**
214
+ * Conditionally update the phishing configuration.
215
+ *
216
+ * If the stalelist configuration is out of date, this function will call `updateStalelist`
217
+ * to update the configuration. This will automatically grab the hotlist,
218
+ * so it isn't necessary to continue on to download the hotlist.
219
+ *
220
+ */
221
+ maybeUpdateState(): Promise<void>;
222
+ /**
223
+ * Determines if a given origin is unapproved.
224
+ *
225
+ * It is strongly recommended that you call {@link maybeUpdateState} before calling this,
226
+ * to check whether the phishing configuration is up-to-date. It will be updated if necessary
227
+ * by calling {@link updateStalelist} or {@link updateHotlist}.
228
+ *
229
+ * @param origin - Domain origin of a website.
230
+ * @returns Whether the origin is an unapproved origin.
231
+ */
232
+ test(origin: string): EthPhishingDetectResult;
233
+ /**
234
+ * Temporarily marks a given origin as approved.
235
+ *
236
+ * @param origin - The origin to mark as approved.
237
+ */
238
+ bypass(origin: string): void;
239
+ /**
240
+ * Update the hotlist.
241
+ *
242
+ * If an update is in progress, no additional update will be made. Instead this will wait until
243
+ * the in-progress update has finished.
244
+ */
245
+ updateHotlist(): Promise<void>;
246
+ /**
247
+ * Update the stalelist.
248
+ *
249
+ * If an update is in progress, no additional update will be made. Instead this will wait until
250
+ * the in-progress update has finished.
251
+ */
252
+ updateStalelist(): Promise<void>;
253
+ private queryConfig;
254
+ }
255
+ export default PhishingController;
256
+ //# sourceMappingURL=PhishingController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PhishingController.d.ts","sourceRoot":"","sources":["../src/PhishingController.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
12
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
13
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
14
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
15
+ };
16
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
17
+ if (kind === "m") throw new TypeError("Private method is not writable");
18
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
19
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
20
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
21
+ };
22
+ var __rest = (this && this.__rest) || function (s, e) {
23
+ var t = {};
24
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
25
+ t[p] = s[p];
26
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
27
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
28
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
29
+ t[p[i]] = s[p[i]];
30
+ }
31
+ return t;
32
+ };
33
+ var __importDefault = (this && this.__importDefault) || function (mod) {
34
+ return (mod && mod.__esModule) ? mod : { "default": mod };
35
+ };
36
+ var _PhishingController_instances, _PhishingController_inProgressHotlistUpdate, _PhishingController_inProgressStalelistUpdate, _PhishingController_updateStalelist, _PhishingController_updateHotlist;
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.PhishingController = exports.phishingListKeyNameMap = exports.ListNames = exports.ListKeys = exports.METAMASK_HOTLIST_DIFF_URL = exports.METAMASK_STALELIST_URL = exports.STALELIST_REFRESH_INTERVAL = exports.HOTLIST_REFRESH_INTERVAL = exports.METAMASK_HOTLIST_DIFF_FILE = exports.METAMASK_STALELIST_FILE = exports.PHISHING_CONFIG_BASE_URL = void 0;
39
+ const base_controller_1 = require("@metamask/base-controller");
40
+ const controller_utils_1 = require("@metamask/controller-utils");
41
+ const detector_1 = __importDefault(require("eth-phishing-detect/src/detector"));
42
+ const punycode_1 = require("punycode/");
43
+ const utils_1 = require("./utils");
44
+ exports.PHISHING_CONFIG_BASE_URL = 'https://phishing-detection.metafi.codefi.network';
45
+ exports.METAMASK_STALELIST_FILE = '/v1/stalelist';
46
+ exports.METAMASK_HOTLIST_DIFF_FILE = '/v1/diffsSince';
47
+ exports.HOTLIST_REFRESH_INTERVAL = 30 * 60; // 30 mins in seconds
48
+ exports.STALELIST_REFRESH_INTERVAL = 4 * 24 * 60 * 60; // 4 days in seconds
49
+ exports.METAMASK_STALELIST_URL = `${exports.PHISHING_CONFIG_BASE_URL}${exports.METAMASK_STALELIST_FILE}`;
50
+ exports.METAMASK_HOTLIST_DIFF_URL = `${exports.PHISHING_CONFIG_BASE_URL}${exports.METAMASK_HOTLIST_DIFF_FILE}`;
51
+ /**
52
+ * Enum containing upstream data provider source list keys.
53
+ * These are the keys denoting lists consumed by the upstream data provider.
54
+ */
55
+ var ListKeys;
56
+ (function (ListKeys) {
57
+ ListKeys["PhishfortHotlist"] = "phishfort_hotlist";
58
+ ListKeys["EthPhishingDetectConfig"] = "eth_phishing_detect_config";
59
+ })(ListKeys = exports.ListKeys || (exports.ListKeys = {}));
60
+ /**
61
+ * Enum containing downstream client attribution names.
62
+ */
63
+ var ListNames;
64
+ (function (ListNames) {
65
+ ListNames["MetaMask"] = "MetaMask";
66
+ ListNames["Phishfort"] = "Phishfort";
67
+ })(ListNames = exports.ListNames || (exports.ListNames = {}));
68
+ /**
69
+ * Maps from downstream client attribution name
70
+ * to list key sourced from upstream data provider.
71
+ */
72
+ const phishingListNameKeyMap = {
73
+ [ListNames.Phishfort]: ListKeys.PhishfortHotlist,
74
+ [ListNames.MetaMask]: ListKeys.EthPhishingDetectConfig,
75
+ };
76
+ /**
77
+ * Maps from list key sourced from upstream data
78
+ * provider to downstream client attribution name.
79
+ */
80
+ exports.phishingListKeyNameMap = {
81
+ [ListKeys.EthPhishingDetectConfig]: ListNames.MetaMask,
82
+ [ListKeys.PhishfortHotlist]: ListNames.Phishfort,
83
+ };
84
+ /**
85
+ * Controller that manages community-maintained lists of approved and unapproved website origins.
86
+ */
87
+ class PhishingController extends base_controller_1.BaseController {
88
+ /**
89
+ * Creates a PhishingController instance.
90
+ *
91
+ * @param config - Initial options used to configure this controller.
92
+ * @param state - Initial state to set on this controller.
93
+ */
94
+ constructor(config, state) {
95
+ super(config, state);
96
+ _PhishingController_instances.add(this);
97
+ _PhishingController_inProgressHotlistUpdate.set(this, void 0);
98
+ _PhishingController_inProgressStalelistUpdate.set(this, void 0);
99
+ /**
100
+ * Name of this controller used during composition
101
+ */
102
+ this.name = 'PhishingController';
103
+ this.defaultConfig = {
104
+ stalelistRefreshInterval: exports.STALELIST_REFRESH_INTERVAL,
105
+ hotlistRefreshInterval: exports.HOTLIST_REFRESH_INTERVAL,
106
+ };
107
+ this.defaultState = {
108
+ phishingLists: [],
109
+ whitelist: [],
110
+ hotlistLastFetched: 0,
111
+ stalelistLastFetched: 0,
112
+ };
113
+ this.initialize();
114
+ this.updatePhishingDetector();
115
+ }
116
+ /**
117
+ * Updates this.detector with an instance of PhishingDetector using the current state.
118
+ */
119
+ updatePhishingDetector() {
120
+ this.detector = new detector_1.default(this.state.phishingLists);
121
+ }
122
+ /**
123
+ * Set the interval at which the stale phishing list will be refetched.
124
+ * Fetching will only occur on the next call to test/bypass.
125
+ * For immediate update to the phishing list, call {@link updateStalelist} directly.
126
+ *
127
+ * @param interval - the new interval, in ms.
128
+ */
129
+ setStalelistRefreshInterval(interval) {
130
+ this.configure({ stalelistRefreshInterval: interval }, false, false);
131
+ }
132
+ /**
133
+ * Set the interval at which the hot list will be refetched.
134
+ * Fetching will only occur on the next call to test/bypass.
135
+ * For immediate update to the phishing list, call {@link updateHotlist} directly.
136
+ *
137
+ * @param interval - the new interval, in ms.
138
+ */
139
+ setHotlistRefreshInterval(interval) {
140
+ this.configure({ hotlistRefreshInterval: interval }, false, false);
141
+ }
142
+ /**
143
+ * Determine if an update to the stalelist configuration is needed.
144
+ *
145
+ * @returns Whether an update is needed
146
+ */
147
+ isStalelistOutOfDate() {
148
+ return ((0, utils_1.fetchTimeNow)() - this.state.stalelistLastFetched >=
149
+ this.config.stalelistRefreshInterval);
150
+ }
151
+ /**
152
+ * Determine if an update to the hotlist configuration is needed.
153
+ *
154
+ * @returns Whether an update is needed
155
+ */
156
+ isHotlistOutOfDate() {
157
+ return ((0, utils_1.fetchTimeNow)() - this.state.hotlistLastFetched >=
158
+ this.config.hotlistRefreshInterval);
159
+ }
160
+ /**
161
+ * Conditionally update the phishing configuration.
162
+ *
163
+ * If the stalelist configuration is out of date, this function will call `updateStalelist`
164
+ * to update the configuration. This will automatically grab the hotlist,
165
+ * so it isn't necessary to continue on to download the hotlist.
166
+ *
167
+ */
168
+ maybeUpdateState() {
169
+ return __awaiter(this, void 0, void 0, function* () {
170
+ const staleListOutOfDate = this.isStalelistOutOfDate();
171
+ if (staleListOutOfDate) {
172
+ yield this.updateStalelist();
173
+ return;
174
+ }
175
+ const hotlistOutOfDate = this.isHotlistOutOfDate();
176
+ if (hotlistOutOfDate) {
177
+ yield this.updateHotlist();
178
+ }
179
+ });
180
+ }
181
+ /**
182
+ * Determines if a given origin is unapproved.
183
+ *
184
+ * It is strongly recommended that you call {@link maybeUpdateState} before calling this,
185
+ * to check whether the phishing configuration is up-to-date. It will be updated if necessary
186
+ * by calling {@link updateStalelist} or {@link updateHotlist}.
187
+ *
188
+ * @param origin - Domain origin of a website.
189
+ * @returns Whether the origin is an unapproved origin.
190
+ */
191
+ test(origin) {
192
+ const punycodeOrigin = (0, punycode_1.toASCII)(origin);
193
+ if (this.state.whitelist.includes(punycodeOrigin)) {
194
+ return { result: false, type: 'all' }; // Same as whitelisted match returned by detector.check(...).
195
+ }
196
+ return this.detector.check(punycodeOrigin);
197
+ }
198
+ /**
199
+ * Temporarily marks a given origin as approved.
200
+ *
201
+ * @param origin - The origin to mark as approved.
202
+ */
203
+ bypass(origin) {
204
+ const punycodeOrigin = (0, punycode_1.toASCII)(origin);
205
+ const { whitelist } = this.state;
206
+ if (whitelist.includes(punycodeOrigin)) {
207
+ return;
208
+ }
209
+ this.update({ whitelist: [...whitelist, punycodeOrigin] });
210
+ }
211
+ /**
212
+ * Update the hotlist.
213
+ *
214
+ * If an update is in progress, no additional update will be made. Instead this will wait until
215
+ * the in-progress update has finished.
216
+ */
217
+ updateHotlist() {
218
+ return __awaiter(this, void 0, void 0, function* () {
219
+ if (__classPrivateFieldGet(this, _PhishingController_inProgressHotlistUpdate, "f")) {
220
+ yield __classPrivateFieldGet(this, _PhishingController_inProgressHotlistUpdate, "f");
221
+ return;
222
+ }
223
+ try {
224
+ __classPrivateFieldSet(this, _PhishingController_inProgressHotlistUpdate, __classPrivateFieldGet(this, _PhishingController_instances, "m", _PhishingController_updateHotlist).call(this), "f");
225
+ yield __classPrivateFieldGet(this, _PhishingController_inProgressHotlistUpdate, "f");
226
+ }
227
+ finally {
228
+ __classPrivateFieldSet(this, _PhishingController_inProgressHotlistUpdate, undefined, "f");
229
+ }
230
+ });
231
+ }
232
+ /**
233
+ * Update the stalelist.
234
+ *
235
+ * If an update is in progress, no additional update will be made. Instead this will wait until
236
+ * the in-progress update has finished.
237
+ */
238
+ updateStalelist() {
239
+ return __awaiter(this, void 0, void 0, function* () {
240
+ if (__classPrivateFieldGet(this, _PhishingController_inProgressStalelistUpdate, "f")) {
241
+ yield __classPrivateFieldGet(this, _PhishingController_inProgressStalelistUpdate, "f");
242
+ return;
243
+ }
244
+ try {
245
+ __classPrivateFieldSet(this, _PhishingController_inProgressStalelistUpdate, __classPrivateFieldGet(this, _PhishingController_instances, "m", _PhishingController_updateStalelist).call(this), "f");
246
+ yield __classPrivateFieldGet(this, _PhishingController_inProgressStalelistUpdate, "f");
247
+ }
248
+ finally {
249
+ __classPrivateFieldSet(this, _PhishingController_inProgressStalelistUpdate, undefined, "f");
250
+ }
251
+ });
252
+ }
253
+ queryConfig(input) {
254
+ return __awaiter(this, void 0, void 0, function* () {
255
+ const response = yield (0, controller_utils_1.safelyExecute)(() => fetch(input, { cache: 'no-cache' }), true);
256
+ switch (response === null || response === void 0 ? void 0 : response.status) {
257
+ case 200: {
258
+ return yield response.json();
259
+ }
260
+ default: {
261
+ return null;
262
+ }
263
+ }
264
+ });
265
+ }
266
+ }
267
+ exports.PhishingController = PhishingController;
268
+ _PhishingController_inProgressHotlistUpdate = new WeakMap(), _PhishingController_inProgressStalelistUpdate = new WeakMap(), _PhishingController_instances = new WeakSet(), _PhishingController_updateStalelist = function _PhishingController_updateStalelist() {
269
+ return __awaiter(this, void 0, void 0, function* () {
270
+ if (this.disabled) {
271
+ return;
272
+ }
273
+ let stalelistResponse;
274
+ let hotlistDiffsResponse;
275
+ try {
276
+ stalelistResponse = yield this.queryConfig(exports.METAMASK_STALELIST_URL).then((d) => d);
277
+ // Fetching hotlist diffs relies on having a lastUpdated timestamp to do `GET /v1/diffsSince/:timestamp`,
278
+ // so it doesn't make sense to call if there is not a timestamp to begin with.
279
+ if ((stalelistResponse === null || stalelistResponse === void 0 ? void 0 : stalelistResponse.data) && stalelistResponse.data.lastUpdated > 0) {
280
+ hotlistDiffsResponse = yield this.queryConfig(`${exports.METAMASK_HOTLIST_DIFF_URL}/${stalelistResponse.data.lastUpdated}`);
281
+ }
282
+ }
283
+ finally {
284
+ // Set `stalelistLastFetched` and `hotlistLastFetched` even for failed requests to prevent server
285
+ // from being overwhelmed with traffic after a network disruption.
286
+ const timeNow = (0, utils_1.fetchTimeNow)();
287
+ this.update({
288
+ stalelistLastFetched: timeNow,
289
+ hotlistLastFetched: timeNow,
290
+ });
291
+ }
292
+ if (!stalelistResponse || !hotlistDiffsResponse) {
293
+ return;
294
+ }
295
+ const _a = stalelistResponse.data, { phishfort_hotlist, eth_phishing_detect_config } = _a, partialState = __rest(_a, ["phishfort_hotlist", "eth_phishing_detect_config"]);
296
+ const phishfortListState = Object.assign(Object.assign(Object.assign({}, phishfort_hotlist), partialState), { fuzzylist: [], allowlist: [], name: exports.phishingListKeyNameMap.phishfort_hotlist });
297
+ const metamaskListState = Object.assign(Object.assign(Object.assign({}, eth_phishing_detect_config), partialState), { name: exports.phishingListKeyNameMap.eth_phishing_detect_config });
298
+ // Correctly shaping eth-phishing-detect state by applying hotlist diffs to the stalelist.
299
+ const newPhishfortListState = (0, utils_1.applyDiffs)(phishfortListState, hotlistDiffsResponse.data, ListKeys.PhishfortHotlist);
300
+ const newMetaMaskListState = (0, utils_1.applyDiffs)(metamaskListState, hotlistDiffsResponse.data, ListKeys.EthPhishingDetectConfig);
301
+ this.update({
302
+ phishingLists: [newMetaMaskListState, newPhishfortListState],
303
+ });
304
+ this.updatePhishingDetector();
305
+ });
306
+ }, _PhishingController_updateHotlist = function _PhishingController_updateHotlist() {
307
+ return __awaiter(this, void 0, void 0, function* () {
308
+ if (this.disabled) {
309
+ return;
310
+ }
311
+ const lastDiffTimestamp = Math.max(...this.state.phishingLists.map(({ lastUpdated }) => lastUpdated));
312
+ let hotlistResponse;
313
+ try {
314
+ hotlistResponse = yield this.queryConfig(`${exports.METAMASK_HOTLIST_DIFF_URL}/${lastDiffTimestamp}`);
315
+ }
316
+ finally {
317
+ // Set `hotlistLastFetched` even for failed requests to prevent server from being overwhelmed with
318
+ // traffic after a network disruption.
319
+ this.update({
320
+ hotlistLastFetched: (0, utils_1.fetchTimeNow)(),
321
+ });
322
+ }
323
+ if (!(hotlistResponse === null || hotlistResponse === void 0 ? void 0 : hotlistResponse.data)) {
324
+ return;
325
+ }
326
+ const hotlist = hotlistResponse.data;
327
+ const newPhishingLists = this.state.phishingLists.map((phishingList) => (0, utils_1.applyDiffs)(phishingList, hotlist, phishingListNameKeyMap[phishingList.name]));
328
+ this.update({
329
+ phishingLists: newPhishingLists,
330
+ });
331
+ this.updatePhishingDetector();
332
+ });
333
+ };
334
+ exports.default = PhishingController;
335
+ //# sourceMappingURL=PhishingController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PhishingController.js","sourceRoot":"","sources":["../src/PhishingController.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BaseConfig, BaseState } from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport { safelyExecute } from '@metamask/controller-utils';\nimport PhishingDetector from 'eth-phishing-detect/src/detector';\nimport { toASCII } from 'punycode/';\n\nimport { applyDiffs, fetchTimeNow } from './utils';\n\n/**\n * @type ListTypes\n *\n * Type outlining the types of lists provided by aggregating different source lists\n */\nexport type ListTypes = 'fuzzylist' | 'blocklist' | 'allowlist';\n\n/**\n * @type EthPhishingResponse\n *\n * Configuration response from the eth-phishing-detect package\n * consisting of approved and unapproved website origins\n * @property blacklist - List of unapproved origins\n * @property fuzzylist - List of fuzzy-matched unapproved origins\n * @property tolerance - Fuzzy match tolerance level\n * @property version - Version number of this configuration\n * @property whitelist - List of approved origins\n */\nexport interface EthPhishingResponse {\n blacklist: string[];\n fuzzylist: string[];\n tolerance: number;\n version: number;\n whitelist: string[];\n}\n\n/**\n * @type PhishingStalelist\n *\n * Interface defining expected type of the stalelist.json file.\n * @property eth_phishing_detect_config - Stale list sourced from eth-phishing-detect's config.json.\n * @property phishfort_hotlist - Stale list sourced from phishfort's hotlist.json. Only includes blocklist. Deduplicated entries from eth_phishing_detect_config.\n * @property tolerance - Fuzzy match tolerance level\n * @property lastUpdated - Timestamp of last update.\n * @property version - Stalelist data structure iteration.\n */\nexport interface PhishingStalelist {\n eth_phishing_detect_config: Record<ListTypes, string[]>;\n phishfort_hotlist: Record<ListTypes, string[]>;\n tolerance: number;\n version: number;\n lastUpdated: number;\n}\n\n/**\n * @type PhishingListState\n *\n * Interface defining the persisted list state. This is the persisted state that is updated frequently with `this.maybeUpdateState()`.\n * @property allowlist - List of approved origins (legacy naming \"whitelist\")\n * @property blocklist - List of unapproved origins (legacy naming \"blacklist\")\n * @property fuzzylist - List of fuzzy-matched unapproved origins\n * @property tolerance - Fuzzy match tolerance level\n * @property lastUpdated - Timestamp of last update.\n * @property version - Version of the phishing list state.\n * @property name - Name of the list. Used for attribution.\n */\nexport interface PhishingListState {\n allowlist: string[];\n blocklist: string[];\n fuzzylist: string[];\n tolerance: number;\n version: number;\n lastUpdated: number;\n name: ListNames;\n}\n\n/**\n * @type EthPhishingDetectResult\n *\n * Interface that describes the result of the `test` method.\n * @property name - Name of the config on which a match was found.\n * @property version - Version of the config on which a match was found.\n * @property result - Whether a domain was detected as a phishing domain. True means an unsafe domain.\n * @property match - The matching fuzzylist origin when a fuzzylist match is found. Returned as undefined for non-fuzzy true results.\n * @property type - The field of the config on which a match was found.\n */\nexport interface EthPhishingDetectResult {\n name?: string;\n version?: string;\n result: boolean;\n match?: string; // Returned as undefined for non-fuzzy true results.\n type: 'all' | 'fuzzy' | 'blocklist' | 'allowlist';\n}\n\n/**\n * @type HotlistDiff\n *\n * Interface defining the expected type of the diffs in hotlist.json file.\n * @property url - Url of the diff entry.\n * @property timestamp - Timestamp at which the diff was identified.\n * @property targetList - The list name where the diff was identified.\n * @property isRemoval - Was the diff identified a removal type.\n */\nexport interface HotlistDiff {\n url: string;\n timestamp: number;\n targetList: `${ListKeys}.${ListTypes}`;\n isRemoval?: boolean;\n}\n\nexport interface DataResultWrapper<T> {\n data: T;\n}\n\n/**\n * @type Hotlist\n *\n * Type defining expected hotlist.json file.\n * @property url - Url of the diff entry.\n * @property timestamp - Timestamp at which the diff was identified.\n * @property targetList - The list name where the diff was identified.\n * @property isRemoval - Was the diff identified a removal type.\n */\nexport type Hotlist = HotlistDiff[];\n\n/**\n * @type PhishingConfig\n *\n * Phishing controller configuration\n * @property stalelistRefreshInterval - Polling interval used to fetch stale list.\n * @property hotlistRefreshInterval - Polling interval used to fetch hotlist diff list.\n */\nexport interface PhishingConfig extends BaseConfig {\n stalelistRefreshInterval: number;\n hotlistRefreshInterval: number;\n}\n\n/**\n * @type PhishingState\n *\n * Phishing controller state\n * @property phishing - eth-phishing-detect configuration\n * @property whitelist - array of temporarily-approved origins\n */\nexport interface PhishingState extends BaseState {\n phishingLists: PhishingListState[];\n whitelist: string[];\n hotlistLastFetched: number;\n stalelistLastFetched: number;\n}\n\nexport const PHISHING_CONFIG_BASE_URL =\n 'https://phishing-detection.metafi.codefi.network';\n\nexport const METAMASK_STALELIST_FILE = '/v1/stalelist';\n\nexport const METAMASK_HOTLIST_DIFF_FILE = '/v1/diffsSince';\n\nexport const HOTLIST_REFRESH_INTERVAL = 30 * 60; // 30 mins in seconds\nexport const STALELIST_REFRESH_INTERVAL = 4 * 24 * 60 * 60; // 4 days in seconds\n\nexport const METAMASK_STALELIST_URL = `${PHISHING_CONFIG_BASE_URL}${METAMASK_STALELIST_FILE}`;\nexport const METAMASK_HOTLIST_DIFF_URL = `${PHISHING_CONFIG_BASE_URL}${METAMASK_HOTLIST_DIFF_FILE}`;\n\n/**\n * Enum containing upstream data provider source list keys.\n * These are the keys denoting lists consumed by the upstream data provider.\n */\nexport enum ListKeys {\n PhishfortHotlist = 'phishfort_hotlist',\n EthPhishingDetectConfig = 'eth_phishing_detect_config',\n}\n\n/**\n * Enum containing downstream client attribution names.\n */\nexport enum ListNames {\n MetaMask = 'MetaMask',\n Phishfort = 'Phishfort',\n}\n\n/**\n * Maps from downstream client attribution name\n * to list key sourced from upstream data provider.\n */\nconst phishingListNameKeyMap = {\n [ListNames.Phishfort]: ListKeys.PhishfortHotlist,\n [ListNames.MetaMask]: ListKeys.EthPhishingDetectConfig,\n};\n\n/**\n * Maps from list key sourced from upstream data\n * provider to downstream client attribution name.\n */\nexport const phishingListKeyNameMap = {\n [ListKeys.EthPhishingDetectConfig]: ListNames.MetaMask,\n [ListKeys.PhishfortHotlist]: ListNames.Phishfort,\n};\n\n/**\n * Controller that manages community-maintained lists of approved and unapproved website origins.\n */\nexport class PhishingController extends BaseController<\n PhishingConfig,\n PhishingState\n> {\n private detector: any;\n\n #inProgressHotlistUpdate: Promise<void> | undefined;\n\n #inProgressStalelistUpdate: Promise<void> | undefined;\n\n /**\n * Name of this controller used during composition\n */\n override name = 'PhishingController';\n\n /**\n * Creates a PhishingController instance.\n *\n * @param config - Initial options used to configure this controller.\n * @param state - Initial state to set on this controller.\n */\n constructor(\n config?: Partial<PhishingConfig>,\n state?: Partial<PhishingState>,\n ) {\n super(config, state);\n this.defaultConfig = {\n stalelistRefreshInterval: STALELIST_REFRESH_INTERVAL,\n hotlistRefreshInterval: HOTLIST_REFRESH_INTERVAL,\n };\n\n this.defaultState = {\n phishingLists: [],\n whitelist: [],\n hotlistLastFetched: 0,\n stalelistLastFetched: 0,\n };\n\n this.initialize();\n this.updatePhishingDetector();\n }\n\n /**\n * Updates this.detector with an instance of PhishingDetector using the current state.\n */\n updatePhishingDetector() {\n this.detector = new PhishingDetector(this.state.phishingLists);\n }\n\n /**\n * Set the interval at which the stale phishing list will be refetched.\n * Fetching will only occur on the next call to test/bypass.\n * For immediate update to the phishing list, call {@link updateStalelist} directly.\n *\n * @param interval - the new interval, in ms.\n */\n setStalelistRefreshInterval(interval: number) {\n this.configure({ stalelistRefreshInterval: interval }, false, false);\n }\n\n /**\n * Set the interval at which the hot list will be refetched.\n * Fetching will only occur on the next call to test/bypass.\n * For immediate update to the phishing list, call {@link updateHotlist} directly.\n *\n * @param interval - the new interval, in ms.\n */\n setHotlistRefreshInterval(interval: number) {\n this.configure({ hotlistRefreshInterval: interval }, false, false);\n }\n\n /**\n * Determine if an update to the stalelist configuration is needed.\n *\n * @returns Whether an update is needed\n */\n isStalelistOutOfDate() {\n return (\n fetchTimeNow() - this.state.stalelistLastFetched >=\n this.config.stalelistRefreshInterval\n );\n }\n\n /**\n * Determine if an update to the hotlist configuration is needed.\n *\n * @returns Whether an update is needed\n */\n isHotlistOutOfDate() {\n return (\n fetchTimeNow() - this.state.hotlistLastFetched >=\n this.config.hotlistRefreshInterval\n );\n }\n\n /**\n * Conditionally update the phishing configuration.\n *\n * If the stalelist configuration is out of date, this function will call `updateStalelist`\n * to update the configuration. This will automatically grab the hotlist,\n * so it isn't necessary to continue on to download the hotlist.\n *\n */\n async maybeUpdateState() {\n const staleListOutOfDate = this.isStalelistOutOfDate();\n if (staleListOutOfDate) {\n await this.updateStalelist();\n return;\n }\n const hotlistOutOfDate = this.isHotlistOutOfDate();\n if (hotlistOutOfDate) {\n await this.updateHotlist();\n }\n }\n\n /**\n * Determines if a given origin is unapproved.\n *\n * It is strongly recommended that you call {@link maybeUpdateState} before calling this,\n * to check whether the phishing configuration is up-to-date. It will be updated if necessary\n * by calling {@link updateStalelist} or {@link updateHotlist}.\n *\n * @param origin - Domain origin of a website.\n * @returns Whether the origin is an unapproved origin.\n */\n test(origin: string): EthPhishingDetectResult {\n const punycodeOrigin = toASCII(origin);\n if (this.state.whitelist.includes(punycodeOrigin)) {\n return { result: false, type: 'all' }; // Same as whitelisted match returned by detector.check(...).\n }\n return this.detector.check(punycodeOrigin);\n }\n\n /**\n * Temporarily marks a given origin as approved.\n *\n * @param origin - The origin to mark as approved.\n */\n bypass(origin: string) {\n const punycodeOrigin = toASCII(origin);\n const { whitelist } = this.state;\n if (whitelist.includes(punycodeOrigin)) {\n return;\n }\n this.update({ whitelist: [...whitelist, punycodeOrigin] });\n }\n\n /**\n * Update the hotlist.\n *\n * If an update is in progress, no additional update will be made. Instead this will wait until\n * the in-progress update has finished.\n */\n async updateHotlist() {\n if (this.#inProgressHotlistUpdate) {\n await this.#inProgressHotlistUpdate;\n return;\n }\n\n try {\n this.#inProgressHotlistUpdate = this.#updateHotlist();\n await this.#inProgressHotlistUpdate;\n } finally {\n this.#inProgressHotlistUpdate = undefined;\n }\n }\n\n /**\n * Update the stalelist.\n *\n * If an update is in progress, no additional update will be made. Instead this will wait until\n * the in-progress update has finished.\n */\n async updateStalelist() {\n if (this.#inProgressStalelistUpdate) {\n await this.#inProgressStalelistUpdate;\n return;\n }\n\n try {\n this.#inProgressStalelistUpdate = this.#updateStalelist();\n await this.#inProgressStalelistUpdate;\n } finally {\n this.#inProgressStalelistUpdate = undefined;\n }\n }\n\n /**\n * Update the stalelist configuration.\n *\n * This should only be called from the `updateStalelist` function, which is a wrapper around\n * this function that prevents redundant configuration updates.\n */\n async #updateStalelist() {\n if (this.disabled) {\n return;\n }\n\n let stalelistResponse;\n let hotlistDiffsResponse;\n try {\n stalelistResponse = await this.queryConfig<\n DataResultWrapper<PhishingStalelist>\n >(METAMASK_STALELIST_URL).then((d) => d);\n\n // Fetching hotlist diffs relies on having a lastUpdated timestamp to do `GET /v1/diffsSince/:timestamp`,\n // so it doesn't make sense to call if there is not a timestamp to begin with.\n if (stalelistResponse?.data && stalelistResponse.data.lastUpdated > 0) {\n hotlistDiffsResponse = await this.queryConfig<\n DataResultWrapper<Hotlist>\n >(`${METAMASK_HOTLIST_DIFF_URL}/${stalelistResponse.data.lastUpdated}`);\n }\n } finally {\n // Set `stalelistLastFetched` and `hotlistLastFetched` even for failed requests to prevent server\n // from being overwhelmed with traffic after a network disruption.\n const timeNow = fetchTimeNow();\n this.update({\n stalelistLastFetched: timeNow,\n hotlistLastFetched: timeNow,\n });\n }\n\n if (!stalelistResponse || !hotlistDiffsResponse) {\n return;\n }\n\n const { phishfort_hotlist, eth_phishing_detect_config, ...partialState } =\n stalelistResponse.data;\n\n const phishfortListState: PhishingListState = {\n ...phishfort_hotlist,\n ...partialState,\n fuzzylist: [], // Phishfort hotlist doesn't contain a fuzzylist\n allowlist: [], // Phishfort hotlist doesn't contain an allowlist\n name: phishingListKeyNameMap.phishfort_hotlist,\n };\n const metamaskListState: PhishingListState = {\n ...eth_phishing_detect_config,\n ...partialState,\n name: phishingListKeyNameMap.eth_phishing_detect_config,\n };\n // Correctly shaping eth-phishing-detect state by applying hotlist diffs to the stalelist.\n const newPhishfortListState: PhishingListState = applyDiffs(\n phishfortListState,\n hotlistDiffsResponse.data,\n ListKeys.PhishfortHotlist,\n );\n const newMetaMaskListState: PhishingListState = applyDiffs(\n metamaskListState,\n hotlistDiffsResponse.data,\n ListKeys.EthPhishingDetectConfig,\n );\n\n this.update({\n phishingLists: [newMetaMaskListState, newPhishfortListState],\n });\n this.updatePhishingDetector();\n }\n\n /**\n * Update the stalelist configuration.\n *\n * This should only be called from the `updateStalelist` function, which is a wrapper around\n * this function that prevents redundant configuration updates.\n */\n async #updateHotlist() {\n if (this.disabled) {\n return;\n }\n const lastDiffTimestamp = Math.max(\n ...this.state.phishingLists.map(({ lastUpdated }) => lastUpdated),\n );\n let hotlistResponse: DataResultWrapper<Hotlist> | null;\n\n try {\n hotlistResponse = await this.queryConfig<DataResultWrapper<Hotlist>>(\n `${METAMASK_HOTLIST_DIFF_URL}/${lastDiffTimestamp}`,\n );\n } finally {\n // Set `hotlistLastFetched` even for failed requests to prevent server from being overwhelmed with\n // traffic after a network disruption.\n this.update({\n hotlistLastFetched: fetchTimeNow(),\n });\n }\n\n if (!hotlistResponse?.data) {\n return;\n }\n const hotlist = hotlistResponse.data;\n const newPhishingLists = this.state.phishingLists.map((phishingList) =>\n applyDiffs(\n phishingList,\n hotlist,\n phishingListNameKeyMap[phishingList.name],\n ),\n );\n\n this.update({\n phishingLists: newPhishingLists,\n });\n this.updatePhishingDetector();\n }\n\n private async queryConfig<ResponseType>(\n input: RequestInfo,\n ): Promise<ResponseType | null> {\n const response = await safelyExecute(\n () => fetch(input, { cache: 'no-cache' }),\n true,\n );\n\n switch (response?.status) {\n case 200: {\n return await response.json();\n }\n\n default: {\n return null;\n }\n }\n }\n}\n\nexport default PhishingController;\n"]}
@@ -0,0 +1,2 @@
1
+ export * from './PhishingController';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./PhishingController"), exports);
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,uDAAqC","sourcesContent":["export * from './PhishingController';\n"]}
@@ -0,0 +1,17 @@
1
+ import type { Hotlist, ListKeys, PhishingListState } from './PhishingController';
2
+ /**
3
+ * Fetches current epoch time in seconds.
4
+ *
5
+ * @returns the Date.now() time in seconds instead of miliseconds. backend files rely on timestamps in seconds since epoch.
6
+ */
7
+ export declare const fetchTimeNow: () => number;
8
+ /**
9
+ * Determines which diffs are applicable to the listState, then applies those diffs.
10
+ *
11
+ * @param listState - the stalelist or the existing liststate that diffs will be applied to.
12
+ * @param hotlistDiffs - the diffs to apply to the listState if valid.
13
+ * @param listKey - the key associated with the input/output phishing list state.
14
+ * @returns the new list state
15
+ */
16
+ export declare const applyDiffs: (listState: PhishingListState, hotlistDiffs: Hotlist, listKey: ListKeys) => PhishingListState;
17
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,QAAQ,EACR,iBAAiB,EAClB,MAAM,sBAAsB,CAAC;AAE9B;;;;GAIG;AACH,eAAO,MAAM,YAAY,QAAO,MAAuC,CAAC;AAkBxE;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,cACV,iBAAiB,kCAEnB,QAAQ,KAChB,iBAyCF,CAAC"}
package/dist/utils.js ADDED
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applyDiffs = exports.fetchTimeNow = void 0;
4
+ const PhishingController_1 = require("./PhishingController");
5
+ /**
6
+ * Fetches current epoch time in seconds.
7
+ *
8
+ * @returns the Date.now() time in seconds instead of miliseconds. backend files rely on timestamps in seconds since epoch.
9
+ */
10
+ const fetchTimeNow = () => Math.round(Date.now() / 1000);
11
+ exports.fetchTimeNow = fetchTimeNow;
12
+ /**
13
+ * Split a string into two pieces, using the first period as the delimiter.
14
+ *
15
+ * @param stringToSplit - The string to split.
16
+ * @returns An array of length two containing the beginning and end of the string.
17
+ */
18
+ const splitStringByPeriod = (stringToSplit) => {
19
+ const periodIndex = stringToSplit.indexOf('.');
20
+ return [
21
+ stringToSplit.slice(0, periodIndex),
22
+ stringToSplit.slice(periodIndex + 1),
23
+ ];
24
+ };
25
+ /**
26
+ * Determines which diffs are applicable to the listState, then applies those diffs.
27
+ *
28
+ * @param listState - the stalelist or the existing liststate that diffs will be applied to.
29
+ * @param hotlistDiffs - the diffs to apply to the listState if valid.
30
+ * @param listKey - the key associated with the input/output phishing list state.
31
+ * @returns the new list state
32
+ */
33
+ const applyDiffs = (listState, hotlistDiffs, listKey) => {
34
+ // filter to remove diffs that were added before the lastUpdate time.
35
+ // filter to remove diffs that aren't applicable to the specified list (by listKey).
36
+ const diffsToApply = hotlistDiffs.filter(({ timestamp, targetList }) => timestamp > listState.lastUpdated &&
37
+ splitStringByPeriod(targetList)[0] === listKey);
38
+ // the reason behind using latestDiffTimestamp as the lastUpdated time
39
+ // is so that we can benefit server-side from memoization due to end client's
40
+ // `GET /v1/diffSince/:timestamp` requests lining up with
41
+ // our periodic updates (which create diffs at specific timestamps).
42
+ let latestDiffTimestamp = listState.lastUpdated;
43
+ const listSets = {
44
+ allowlist: new Set(listState.allowlist),
45
+ blocklist: new Set(listState.blocklist),
46
+ fuzzylist: new Set(listState.fuzzylist),
47
+ };
48
+ for (const { isRemoval, targetList, url, timestamp } of diffsToApply) {
49
+ const targetListType = splitStringByPeriod(targetList)[1];
50
+ if (timestamp > latestDiffTimestamp) {
51
+ latestDiffTimestamp = timestamp;
52
+ }
53
+ if (isRemoval) {
54
+ listSets[targetListType].delete(url);
55
+ }
56
+ else {
57
+ listSets[targetListType].add(url);
58
+ }
59
+ }
60
+ return {
61
+ allowlist: Array.from(listSets.allowlist),
62
+ blocklist: Array.from(listSets.blocklist),
63
+ fuzzylist: Array.from(listSets.fuzzylist),
64
+ version: listState.version,
65
+ name: PhishingController_1.phishingListKeyNameMap[listKey],
66
+ tolerance: listState.tolerance,
67
+ lastUpdated: latestDiffTimestamp,
68
+ };
69
+ };
70
+ exports.applyDiffs = applyDiffs;
71
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAKA,6DAA8D;AAC9D;;;;GAIG;AACI,MAAM,YAAY,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;AAA3D,QAAA,YAAY,gBAA+C;AAExE;;;;;GAKG;AACH,MAAM,mBAAmB,GAAG,CAC1B,aAAgC,EAClB,EAAE;IAChB,MAAM,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/C,OAAO;QACL,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAU;QAC5C,aAAa,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,CAAQ;KAC5C,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;;GAOG;AACI,MAAM,UAAU,GAAG,CACxB,SAA4B,EAC5B,YAAqB,EACrB,OAAiB,EACE,EAAE;IACrB,qEAAqE;IACrE,oFAAoF;IACpF,MAAM,YAAY,GAAG,YAAY,CAAC,MAAM,CACtC,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,EAAE,CAC5B,SAAS,GAAG,SAAS,CAAC,WAAW;QACjC,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,CACjD,CAAC;IAEF,sEAAsE;IACtE,6EAA6E;IAC7E,yDAAyD;IACzD,oEAAoE;IACpE,IAAI,mBAAmB,GAAG,SAAS,CAAC,WAAW,CAAC;IAEhD,MAAM,QAAQ,GAAG;QACf,SAAS,EAAE,IAAI,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC;QACvC,SAAS,EAAE,IAAI,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC;QACvC,SAAS,EAAE,IAAI,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC;KACxC,CAAC;IACF,KAAK,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,YAAY,EAAE;QACpE,MAAM,cAAc,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1D,IAAI,SAAS,GAAG,mBAAmB,EAAE;YACnC,mBAAmB,GAAG,SAAS,CAAC;SACjC;QACD,IAAI,SAAS,EAAE;YACb,QAAQ,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;SACtC;aAAM;YACL,QAAQ,CAAC,cAAc,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;SACnC;KACF;IAED,OAAO;QACL,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QACzC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QACzC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QACzC,OAAO,EAAE,SAAS,CAAC,OAAO;QAC1B,IAAI,EAAE,2CAAsB,CAAC,OAAO,CAAC;QACrC,SAAS,EAAE,SAAS,CAAC,SAAS;QAC9B,WAAW,EAAE,mBAAmB;KACjC,CAAC;AACJ,CAAC,CAAC;AA7CW,QAAA,UAAU,cA6CrB","sourcesContent":["import type {\n Hotlist,\n ListKeys,\n PhishingListState,\n} from './PhishingController';\nimport { phishingListKeyNameMap } from './PhishingController';\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 * 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 * @returns the new list state\n */\nexport const applyDiffs = (\n listState: PhishingListState,\n hotlistDiffs: Hotlist,\n listKey: ListKeys,\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 };\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 return {\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"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@metamask-previews/phishing-controller",
3
+ "version": "6.0.0-preview.3dbf14f",
4
+ "description": "Maintains a periodically updated list of approved and unapproved website origins",
5
+ "keywords": [
6
+ "MetaMask",
7
+ "Ethereum"
8
+ ],
9
+ "homepage": "https://github.com/MetaMask/core/tree/main/packages/phishing-controller#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/MetaMask/core/issues"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/MetaMask/core.git"
16
+ },
17
+ "license": "MIT",
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "files": [
21
+ "dist/"
22
+ ],
23
+ "scripts": {
24
+ "build:docs": "typedoc",
25
+ "changelog:validate": "../../scripts/validate-changelog.sh @metamask/phishing-controller",
26
+ "publish:preview": "yarn npm publish --tag preview",
27
+ "test": "jest",
28
+ "test:watch": "jest --watch"
29
+ },
30
+ "dependencies": {
31
+ "@metamask-previews/base-controller": "3.2.0-preview.3dbf14f",
32
+ "@metamask-previews/controller-utils": "4.3.1-preview.3dbf14f",
33
+ "@types/punycode": "^2.1.0",
34
+ "eth-phishing-detect": "^1.2.0",
35
+ "punycode": "^2.1.1"
36
+ },
37
+ "devDependencies": {
38
+ "@metamask/auto-changelog": "^3.1.0",
39
+ "@types/jest": "^27.4.1",
40
+ "deepmerge": "^4.2.2",
41
+ "jest": "^27.5.1",
42
+ "nock": "^13.3.1",
43
+ "sinon": "^9.2.4",
44
+ "ts-jest": "^27.1.4",
45
+ "typedoc": "^0.22.15",
46
+ "typedoc-plugin-missing-exports": "^0.22.6",
47
+ "typescript": "~4.6.3"
48
+ },
49
+ "engines": {
50
+ "node": ">=16.0.0"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public",
54
+ "registry": "https://registry.npmjs.org/"
55
+ }
56
+ }