@metamask-previews/phishing-controller 11.0.0-preview-b59ee0c8 → 11.0.0-preview-3b4ca958

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.
@@ -11,7 +11,12 @@
11
11
 
12
12
 
13
13
 
14
- var _chunkDPHXQJ4Ejs = require('./chunk-DPHXQJ4E.js');
14
+
15
+
16
+
17
+
18
+ var _chunkQLJTY3E3js = require('./chunk-QLJTY3E3.js');
19
+ require('./chunk-5NDIXQG5.js');
15
20
  require('./chunk-Z4BLTVTB.js');
16
21
 
17
22
 
@@ -26,5 +31,9 @@ require('./chunk-Z4BLTVTB.js');
26
31
 
27
32
 
28
33
 
29
- exports.HOTLIST_REFRESH_INTERVAL = _chunkDPHXQJ4Ejs.HOTLIST_REFRESH_INTERVAL; exports.ListKeys = _chunkDPHXQJ4Ejs.ListKeys; exports.ListNames = _chunkDPHXQJ4Ejs.ListNames; exports.METAMASK_HOTLIST_DIFF_FILE = _chunkDPHXQJ4Ejs.METAMASK_HOTLIST_DIFF_FILE; exports.METAMASK_HOTLIST_DIFF_URL = _chunkDPHXQJ4Ejs.METAMASK_HOTLIST_DIFF_URL; exports.METAMASK_STALELIST_FILE = _chunkDPHXQJ4Ejs.METAMASK_STALELIST_FILE; exports.METAMASK_STALELIST_URL = _chunkDPHXQJ4Ejs.METAMASK_STALELIST_URL; exports.PHISHING_CONFIG_BASE_URL = _chunkDPHXQJ4Ejs.PHISHING_CONFIG_BASE_URL; exports.PhishingController = _chunkDPHXQJ4Ejs.PhishingController; exports.STALELIST_REFRESH_INTERVAL = _chunkDPHXQJ4Ejs.STALELIST_REFRESH_INTERVAL; exports.default = _chunkDPHXQJ4Ejs.PhishingController_default; exports.phishingListKeyNameMap = _chunkDPHXQJ4Ejs.phishingListKeyNameMap;
34
+
35
+
36
+
37
+
38
+ exports.C2_DOMAIN_BLOCKLIST_ENDPOINT = _chunkQLJTY3E3js.C2_DOMAIN_BLOCKLIST_ENDPOINT; exports.C2_DOMAIN_BLOCKLIST_REFRESH_INTERVAL = _chunkQLJTY3E3js.C2_DOMAIN_BLOCKLIST_REFRESH_INTERVAL; exports.C2_DOMAIN_BLOCKLIST_URL = _chunkQLJTY3E3js.C2_DOMAIN_BLOCKLIST_URL; exports.CLIENT_SIDE_DETECION_BASE_URL = _chunkQLJTY3E3js.CLIENT_SIDE_DETECION_BASE_URL; exports.HOTLIST_REFRESH_INTERVAL = _chunkQLJTY3E3js.HOTLIST_REFRESH_INTERVAL; exports.ListKeys = _chunkQLJTY3E3js.ListKeys; exports.ListNames = _chunkQLJTY3E3js.ListNames; exports.METAMASK_HOTLIST_DIFF_FILE = _chunkQLJTY3E3js.METAMASK_HOTLIST_DIFF_FILE; exports.METAMASK_HOTLIST_DIFF_URL = _chunkQLJTY3E3js.METAMASK_HOTLIST_DIFF_URL; exports.METAMASK_STALELIST_FILE = _chunkQLJTY3E3js.METAMASK_STALELIST_FILE; exports.METAMASK_STALELIST_URL = _chunkQLJTY3E3js.METAMASK_STALELIST_URL; exports.PHISHING_CONFIG_BASE_URL = _chunkQLJTY3E3js.PHISHING_CONFIG_BASE_URL; exports.PhishingController = _chunkQLJTY3E3js.PhishingController; exports.STALELIST_REFRESH_INTERVAL = _chunkQLJTY3E3js.STALELIST_REFRESH_INTERVAL; exports.default = _chunkQLJTY3E3js.PhishingController_default; exports.phishingListKeyNameMap = _chunkQLJTY3E3js.phishingListKeyNameMap;
30
39
  //# sourceMappingURL=PhishingController.js.map
@@ -1,4 +1,8 @@
1
1
  import {
2
+ C2_DOMAIN_BLOCKLIST_ENDPOINT,
3
+ C2_DOMAIN_BLOCKLIST_REFRESH_INTERVAL,
4
+ C2_DOMAIN_BLOCKLIST_URL,
5
+ CLIENT_SIDE_DETECION_BASE_URL,
2
6
  HOTLIST_REFRESH_INTERVAL,
3
7
  ListKeys,
4
8
  ListNames,
@@ -11,9 +15,14 @@ import {
11
15
  PhishingController_default,
12
16
  STALELIST_REFRESH_INTERVAL,
13
17
  phishingListKeyNameMap
14
- } from "./chunk-Q42CM6EP.mjs";
18
+ } from "./chunk-A3KBBZ6O.mjs";
19
+ import "./chunk-G2RL74NS.mjs";
15
20
  import "./chunk-XUI43LEZ.mjs";
16
21
  export {
22
+ C2_DOMAIN_BLOCKLIST_ENDPOINT,
23
+ C2_DOMAIN_BLOCKLIST_REFRESH_INTERVAL,
24
+ C2_DOMAIN_BLOCKLIST_URL,
25
+ CLIENT_SIDE_DETECION_BASE_URL,
17
26
  HOTLIST_REFRESH_INTERVAL,
18
27
  ListKeys,
19
28
  ListNames,
@@ -1,8 +1,9 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
- var _chunkDPHXQJ4Ejs = require('./chunk-DPHXQJ4E.js');
3
+ var _chunkQLJTY3E3js = require('./chunk-QLJTY3E3.js');
4
+ require('./chunk-5NDIXQG5.js');
4
5
  require('./chunk-Z4BLTVTB.js');
5
6
 
6
7
 
7
- exports.PhishingDetector = _chunkDPHXQJ4Ejs.PhishingDetector;
8
+ exports.PhishingDetector = _chunkQLJTY3E3js.PhishingDetector;
8
9
  //# sourceMappingURL=PhishingDetector.js.map
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  PhishingDetector
3
- } from "./chunk-Q42CM6EP.mjs";
3
+ } from "./chunk-A3KBBZ6O.mjs";
4
+ import "./chunk-G2RL74NS.mjs";
4
5
  import "./chunk-XUI43LEZ.mjs";
5
6
  export {
6
7
  PhishingDetector
@@ -0,0 +1,16 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});// src/types.ts
2
+ var PhishingDetectorResultType = /* @__PURE__ */ ((PhishingDetectorResultType2) => {
3
+ PhishingDetectorResultType2["All"] = "all";
4
+ PhishingDetectorResultType2["Fuzzy"] = "fuzzy";
5
+ PhishingDetectorResultType2["Blocklist"] = "blocklist";
6
+ PhishingDetectorResultType2["Allowlist"] = "allowlist";
7
+ PhishingDetectorResultType2["Blacklist"] = "blacklist";
8
+ PhishingDetectorResultType2["Whitelist"] = "whitelist";
9
+ PhishingDetectorResultType2["C2DomainBlocklist"] = "c2DomainBlocklist";
10
+ return PhishingDetectorResultType2;
11
+ })(PhishingDetectorResultType || {});
12
+
13
+
14
+
15
+ exports.PhishingDetectorResultType = PhishingDetectorResultType;
16
+ //# sourceMappingURL=chunk-5NDIXQG5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"names":["PhishingDetectorResultType"],"mappings":";AAyCO,IAAK,6BAAL,kBAAKA,gCAAL;AAIL,EAAAA,4BAAA,SAAM;AAIN,EAAAA,4BAAA,WAAQ;AAIR,EAAAA,4BAAA,eAAY;AAIZ,EAAAA,4BAAA,eAAY;AAKZ,EAAAA,4BAAA,eAAY;AAKZ,EAAAA,4BAAA,eAAY;AAIZ,EAAAA,4BAAA,uBAAoB;AA9BV,SAAAA;AAAA,GAAA","sourcesContent":["/**\n * Represents the result of checking a domain.\n */\nexport type PhishingDetectorResult = {\n /**\n * The name of the configuration object in which the domain was found within\n * an allowlist, blocklist, or fuzzylist.\n */\n name?: string;\n /**\n * The version associated with the configuration object in which the domain\n * was found within an allowlist, blocklist, or fuzzylist.\n */\n version?: string;\n /**\n * Whether the domain is regarded as allowed (true) or not (false).\n */\n result: boolean;\n /**\n * A normalized version of the domain, which is only constructed if the domain\n * is found within a list.\n */\n match?: string;\n /**\n * Which type of list in which the domain was found.\n *\n * - \"allowlist\" means that the domain was found in the allowlist.\n * - \"blocklist\" means that the domain was found in the blocklist.\n * - \"fuzzy\" means that the domain was found in the fuzzylist.\n * - \"blacklist\" means that the domain was found in a blacklist of a legacy\n * configuration object.\n * - \"whitelist\" means that the domain was found in a whitelist of a legacy\n * configuration object.\n * - \"all\" means that the domain was not found in any list.\n */\n type: PhishingDetectorResultType;\n};\n\n/**\n * The type of list in which the domain was found.\n */\nexport enum PhishingDetectorResultType {\n /*\n * \"all\" means that the domain was not found in any list.\n */\n All = 'all',\n /*\n * \"fuzzy\" means that the domain was found in the fuzzylist.\n */\n Fuzzy = 'fuzzy',\n /*\n * \"blocklist\" means that the domain was found in the blocklist.\n */\n Blocklist = 'blocklist',\n /*\n * \"allowlist\" means that the domain was found in the allowlist.\n */\n Allowlist = 'allowlist',\n /*\n * \"blacklist\" means that the domain was found in a blacklist of a legacy\n * configuration object.\n */\n Blacklist = 'blacklist',\n /*\n * \"whitelist\" means that the domain was found in a whitelist of a legacy\n * configuration object.\n */\n Whitelist = 'whitelist',\n /*\n * \"c2DomainBlocklist\" means that the domain was found in the C2 domain blocklist.\n */\n C2DomainBlocklist = 'c2DomainBlocklist',\n}\n"]}
@@ -5,6 +5,10 @@ import {
5
5
  __privateSet
6
6
  } from "./chunk-XUI43LEZ.mjs";
7
7
 
8
+ // src/utils.ts
9
+ import { bytesToHex } from "@noble/hashes/utils";
10
+ import { sha256 } from "ethereum-cryptography/sha256";
11
+
8
12
  // src/PhishingController.ts
9
13
  import { BaseController } from "@metamask/base-controller";
10
14
  import { safelyExecute } from "@metamask/controller-utils";
@@ -36,6 +40,7 @@ var PhishingDetector = class {
36
40
  getDefaultPhishingDetectorConfig({
37
41
  allowlist: opts.whitelist,
38
42
  blocklist: opts.blacklist,
43
+ c2DomainBlocklist: opts.c2DomainBlocklist,
39
44
  fuzzylist: opts.fuzzylist,
40
45
  tolerance: opts.tolerance
41
46
  })
@@ -55,10 +60,10 @@ var PhishingDetector = class {
55
60
  const result = __privateMethod(this, _check, check_fn).call(this, url);
56
61
  if (__privateGet(this, _legacyConfig)) {
57
62
  let legacyType = result.type;
58
- if (legacyType === "allowlist") {
59
- legacyType = "whitelist";
60
- } else if (legacyType === "blocklist") {
61
- legacyType = "blacklist";
63
+ if (legacyType === "allowlist" /* Allowlist */) {
64
+ legacyType = "whitelist" /* Whitelist */;
65
+ } else if (legacyType === "blocklist" /* Blocklist */) {
66
+ legacyType = "blacklist" /* Blacklist */;
62
67
  }
63
68
  return {
64
69
  match: result.match,
@@ -68,12 +73,72 @@ var PhishingDetector = class {
68
73
  }
69
74
  return result;
70
75
  }
76
+ /**
77
+ * Checks if a URL is blocked against the hashed request blocklist.
78
+ * This is done by hashing the URL's hostname and checking it against the hashed request blocklist.
79
+ *
80
+ *
81
+ * @param urlString - The URL to check.
82
+ * @returns An object indicating if the URL is blocked and relevant metadata.
83
+ */
84
+ isMaliciousRequestDomain(urlString) {
85
+ for (const { c2DomainBlocklist, name, version } of __privateGet(this, _configs)) {
86
+ try {
87
+ const url = new URL(urlString);
88
+ const hash = sha256Hash(url.hostname.toLowerCase());
89
+ const blocked = c2DomainBlocklist?.includes(hash) ?? false;
90
+ if (blocked) {
91
+ return {
92
+ name,
93
+ result: true,
94
+ type: "c2DomainBlocklist" /* C2DomainBlocklist */,
95
+ version: version === void 0 ? version : String(version)
96
+ };
97
+ }
98
+ } catch (error) {
99
+ return {
100
+ result: false,
101
+ type: "c2DomainBlocklist" /* C2DomainBlocklist */
102
+ };
103
+ }
104
+ }
105
+ return {
106
+ result: false,
107
+ type: "c2DomainBlocklist" /* C2DomainBlocklist */
108
+ };
109
+ }
71
110
  };
72
111
  _configs = new WeakMap();
73
112
  _legacyConfig = new WeakMap();
74
113
  _check = new WeakSet();
75
114
  check_fn = function(url) {
76
- const domain = new URL(url).hostname;
115
+ const ipfsCidMatch = url.match(ipfsCidRegex());
116
+ if (ipfsCidMatch !== null) {
117
+ const cID = ipfsCidMatch[0];
118
+ for (const { blocklist, name, version } of __privateGet(this, _configs)) {
119
+ const blocklistMatch = blocklist.filter((entries) => entries.length === 1).find((entries) => {
120
+ return entries[0] === cID;
121
+ });
122
+ if (blocklistMatch) {
123
+ return {
124
+ name,
125
+ match: cID,
126
+ result: true,
127
+ type: "blocklist" /* Blocklist */,
128
+ version: version === void 0 ? version : String(version)
129
+ };
130
+ }
131
+ }
132
+ }
133
+ let domain;
134
+ try {
135
+ domain = new URL(url).hostname;
136
+ } catch (error) {
137
+ return {
138
+ result: false,
139
+ type: "all" /* All */
140
+ };
141
+ }
77
142
  const fqdn = domain.endsWith(".") ? domain.slice(0, -1) : domain;
78
143
  const source = domainToParts(fqdn);
79
144
  for (const { allowlist, name, version } of __privateGet(this, _configs)) {
@@ -84,7 +149,7 @@ check_fn = function(url) {
84
149
  match,
85
150
  name,
86
151
  result: false,
87
- type: "allowlist",
152
+ type: "allowlist" /* Allowlist */,
88
153
  version: version === void 0 ? version : String(version)
89
154
  };
90
155
  }
@@ -97,7 +162,7 @@ check_fn = function(url) {
97
162
  match,
98
163
  name,
99
164
  result: true,
100
- type: "blocklist",
165
+ type: "blocklist" /* Blocklist */,
101
166
  version: version === void 0 ? version : String(version)
102
167
  };
103
168
  }
@@ -115,31 +180,13 @@ check_fn = function(url) {
115
180
  name,
116
181
  match,
117
182
  result: true,
118
- type: "fuzzy",
183
+ type: "fuzzy" /* Fuzzy */,
119
184
  version: version === void 0 ? version : String(version)
120
185
  };
121
186
  }
122
187
  }
123
188
  }
124
- const ipfsCidMatch = url.match(ipfsCidRegex());
125
- if (ipfsCidMatch !== null) {
126
- const cID = ipfsCidMatch[0];
127
- for (const { blocklist, name, version } of __privateGet(this, _configs)) {
128
- const blocklistMatch = blocklist.filter((entries) => entries.length === 1).find((entries) => {
129
- return entries[0] === cID;
130
- });
131
- if (blocklistMatch) {
132
- return {
133
- name,
134
- match: cID,
135
- result: true,
136
- type: "blocklist",
137
- version: version === void 0 ? version : String(version)
138
- };
139
- }
140
- }
141
- }
142
- return { result: false, type: "all" };
189
+ return { result: false, type: "all" /* All */ };
143
190
  };
144
191
  function ipfsCidRegex() {
145
192
  const reg = "Qm[1-9A-HJ-NP-Za-km-z]{44,}|b[A-Za-z2-7]{58,}|B[A-Z2-7]{58,}|z[1-9A-HJ-NP-Za-km-z]{48,}|F[0-9A-F]{50,}";
@@ -150,10 +197,14 @@ function ipfsCidRegex() {
150
197
  var PHISHING_CONFIG_BASE_URL = "https://phishing-detection.api.cx.metamask.io";
151
198
  var METAMASK_STALELIST_FILE = "/v1/stalelist";
152
199
  var METAMASK_HOTLIST_DIFF_FILE = "/v1/diffsSince";
200
+ var CLIENT_SIDE_DETECION_BASE_URL = "https://client-side-detection.api.cx.metamask.io";
201
+ var C2_DOMAIN_BLOCKLIST_ENDPOINT = "/v1/request-blocklist";
202
+ var C2_DOMAIN_BLOCKLIST_REFRESH_INTERVAL = 15 * 60;
153
203
  var HOTLIST_REFRESH_INTERVAL = 5 * 60;
154
204
  var STALELIST_REFRESH_INTERVAL = 30 * 24 * 60 * 60;
155
205
  var METAMASK_STALELIST_URL = `${PHISHING_CONFIG_BASE_URL}${METAMASK_STALELIST_FILE}`;
156
206
  var METAMASK_HOTLIST_DIFF_URL = `${PHISHING_CONFIG_BASE_URL}${METAMASK_HOTLIST_DIFF_FILE}`;
207
+ var C2_DOMAIN_BLOCKLIST_URL = `${CLIENT_SIDE_DETECION_BASE_URL}${C2_DOMAIN_BLOCKLIST_ENDPOINT}`;
157
208
  var ListKeys = /* @__PURE__ */ ((ListKeys2) => {
158
209
  ListKeys2["EthPhishingDetectConfig"] = "eth_phishing_detect_config";
159
210
  return ListKeys2;
@@ -173,17 +224,19 @@ var metadata = {
173
224
  phishingLists: { persist: true, anonymous: false },
174
225
  whitelist: { persist: true, anonymous: false },
175
226
  hotlistLastFetched: { persist: true, anonymous: false },
176
- stalelistLastFetched: { persist: true, anonymous: false }
227
+ stalelistLastFetched: { persist: true, anonymous: false },
228
+ c2DomainBlocklistLastFetched: { persist: true, anonymous: false }
177
229
  };
178
230
  var getDefaultState = () => {
179
231
  return {
180
232
  phishingLists: [],
181
233
  whitelist: [],
182
234
  hotlistLastFetched: 0,
183
- stalelistLastFetched: 0
235
+ stalelistLastFetched: 0,
236
+ c2DomainBlocklistLastFetched: 0
184
237
  };
185
238
  };
186
- var _detector, _stalelistRefreshInterval, _hotlistRefreshInterval, _inProgressHotlistUpdate, _inProgressStalelistUpdate, _registerMessageHandlers, registerMessageHandlers_fn, _updateStalelist, updateStalelist_fn, _updateHotlist, updateHotlist_fn, _queryConfig, queryConfig_fn;
239
+ var _detector, _stalelistRefreshInterval, _hotlistRefreshInterval, _c2DomainBlocklistRefreshInterval, _inProgressHotlistUpdate, _inProgressStalelistUpdate, _isProgressC2DomainBlocklistUpdate, _registerMessageHandlers, registerMessageHandlers_fn, _updateStalelist, updateStalelist_fn, _updateHotlist, updateHotlist_fn, _updateC2DomainBlocklist, updateC2DomainBlocklist_fn, _queryConfig, queryConfig_fn;
187
240
  var PhishingController = class extends BaseController {
188
241
  /**
189
242
  * Construct a Phishing Controller.
@@ -191,12 +244,14 @@ var PhishingController = class extends BaseController {
191
244
  * @param config - Initial options used to configure this controller.
192
245
  * @param config.stalelistRefreshInterval - Polling interval used to fetch stale list.
193
246
  * @param config.hotlistRefreshInterval - Polling interval used to fetch hotlist diff list.
247
+ * @param config.c2DomainBlocklistRefreshInterval - Polling interval used to fetch c2 domain blocklist.
194
248
  * @param config.messenger - The controller restricted messenger.
195
249
  * @param config.state - Initial state to set on this controller.
196
250
  */
197
251
  constructor({
198
252
  stalelistRefreshInterval = STALELIST_REFRESH_INTERVAL,
199
253
  hotlistRefreshInterval = HOTLIST_REFRESH_INTERVAL,
254
+ c2DomainBlocklistRefreshInterval = C2_DOMAIN_BLOCKLIST_REFRESH_INTERVAL,
200
255
  messenger,
201
256
  state = {}
202
257
  }) {
@@ -228,16 +283,26 @@ var PhishingController = class extends BaseController {
228
283
  * this function that prevents redundant configuration updates.
229
284
  */
230
285
  __privateAdd(this, _updateHotlist);
286
+ /**
287
+ * Update the C2 domain blocklist.
288
+ *
289
+ * This should only be called from the `updateC2DomainBlocklist` function, which is a wrapper around
290
+ * this function that prevents redundant configuration updates.
291
+ */
292
+ __privateAdd(this, _updateC2DomainBlocklist);
231
293
  __privateAdd(this, _queryConfig);
232
294
  // TODO: Replace `any` with type
233
295
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
234
296
  __privateAdd(this, _detector, void 0);
235
297
  __privateAdd(this, _stalelistRefreshInterval, void 0);
236
298
  __privateAdd(this, _hotlistRefreshInterval, void 0);
299
+ __privateAdd(this, _c2DomainBlocklistRefreshInterval, void 0);
237
300
  __privateAdd(this, _inProgressHotlistUpdate, void 0);
238
301
  __privateAdd(this, _inProgressStalelistUpdate, void 0);
302
+ __privateAdd(this, _isProgressC2DomainBlocklistUpdate, void 0);
239
303
  __privateSet(this, _stalelistRefreshInterval, stalelistRefreshInterval);
240
304
  __privateSet(this, _hotlistRefreshInterval, hotlistRefreshInterval);
305
+ __privateSet(this, _c2DomainBlocklistRefreshInterval, c2DomainBlocklistRefreshInterval);
241
306
  __privateMethod(this, _registerMessageHandlers, registerMessageHandlers_fn).call(this);
242
307
  this.updatePhishingDetector();
243
308
  }
@@ -267,6 +332,16 @@ var PhishingController = class extends BaseController {
267
332
  setHotlistRefreshInterval(interval) {
268
333
  __privateSet(this, _hotlistRefreshInterval, interval);
269
334
  }
335
+ /**
336
+ * Set the interval at which the C2 domain blocklist will be refetched.
337
+ * Fetching will only occur on the next call to test/bypass.
338
+ * For immediate update to the phishing list, call {@link updateHotlist} directly.
339
+ *
340
+ * @param interval - the new interval, in ms.
341
+ */
342
+ setC2DomainBlocklistRefreshInterval(interval) {
343
+ __privateSet(this, _c2DomainBlocklistRefreshInterval, interval);
344
+ }
270
345
  /**
271
346
  * Determine if an update to the stalelist configuration is needed.
272
347
  *
@@ -283,6 +358,14 @@ var PhishingController = class extends BaseController {
283
358
  isHotlistOutOfDate() {
284
359
  return fetchTimeNow() - this.state.hotlistLastFetched >= __privateGet(this, _hotlistRefreshInterval);
285
360
  }
361
+ /**
362
+ * Determine if an update to the C2 domain blocklist is needed.
363
+ *
364
+ * @returns Whether an update is needed
365
+ */
366
+ isC2DomainBlocklistOutOfDate() {
367
+ return fetchTimeNow() - this.state.c2DomainBlocklistLastFetched >= __privateGet(this, _c2DomainBlocklistRefreshInterval);
368
+ }
286
369
  /**
287
370
  * Conditionally update the phishing configuration.
288
371
  *
@@ -301,6 +384,10 @@ var PhishingController = class extends BaseController {
301
384
  if (hotlistOutOfDate) {
302
385
  await this.updateHotlist();
303
386
  }
387
+ const c2DomainBlocklistOutOfDate = this.isC2DomainBlocklistOutOfDate();
388
+ if (c2DomainBlocklistOutOfDate) {
389
+ await this.updateC2DomainBlocklist();
390
+ }
304
391
  }
305
392
  /**
306
393
  * Determines if a given origin is unapproved.
@@ -315,10 +402,24 @@ var PhishingController = class extends BaseController {
315
402
  test(origin) {
316
403
  const punycodeOrigin = toASCII(origin);
317
404
  if (this.state.whitelist.includes(punycodeOrigin)) {
318
- return { result: false, type: "all" };
405
+ return { result: false, type: "all" /* All */ };
319
406
  }
320
407
  return __privateGet(this, _detector).check(punycodeOrigin);
321
408
  }
409
+ /**
410
+ * Checks if a request URL's domain is blocked against the request blocklist.
411
+ *
412
+ * This method is used to determine if a specific request URL is associated with a malicious
413
+ * command and control (C2) domain. The URL's hostname is hashed and checked against a configured
414
+ * blocklist of known malicious domains.
415
+ *
416
+ * @param origin - The full request URL to be checked.
417
+ * @returns An object indicating whether the URL's domain is blocked and relevant metadata.
418
+ */
419
+ isBlockedRequest(origin) {
420
+ const punycodeOrigin = toASCII(origin);
421
+ return __privateGet(this, _detector).isMaliciousRequestDomain(punycodeOrigin);
422
+ }
322
423
  /**
323
424
  * Temporarily marks a given origin as approved.
324
425
  *
@@ -334,6 +435,24 @@ var PhishingController = class extends BaseController {
334
435
  draftState.whitelist.push(punycodeOrigin);
335
436
  });
336
437
  }
438
+ /**
439
+ * Update the C2 domain blocklist.
440
+ *
441
+ * If an update is in progress, no additional update will be made. Instead this will wait until
442
+ * the in-progress update has finished.
443
+ */
444
+ async updateC2DomainBlocklist() {
445
+ if (__privateGet(this, _isProgressC2DomainBlocklistUpdate)) {
446
+ await __privateGet(this, _isProgressC2DomainBlocklistUpdate);
447
+ return;
448
+ }
449
+ try {
450
+ __privateSet(this, _isProgressC2DomainBlocklistUpdate, __privateMethod(this, _updateC2DomainBlocklist, updateC2DomainBlocklist_fn).call(this));
451
+ await __privateGet(this, _isProgressC2DomainBlocklistUpdate);
452
+ } finally {
453
+ __privateSet(this, _isProgressC2DomainBlocklistUpdate, void 0);
454
+ }
455
+ }
337
456
  /**
338
457
  * Update the hotlist.
339
458
  *
@@ -374,8 +493,10 @@ var PhishingController = class extends BaseController {
374
493
  _detector = new WeakMap();
375
494
  _stalelistRefreshInterval = new WeakMap();
376
495
  _hotlistRefreshInterval = new WeakMap();
496
+ _c2DomainBlocklistRefreshInterval = new WeakMap();
377
497
  _inProgressHotlistUpdate = new WeakMap();
378
498
  _inProgressStalelistUpdate = new WeakMap();
499
+ _isProgressC2DomainBlocklistUpdate = new WeakMap();
379
500
  _registerMessageHandlers = new WeakSet();
380
501
  registerMessageHandlers_fn = function() {
381
502
  this.messagingSystem.registerActionHandler(
@@ -389,10 +510,16 @@ registerMessageHandlers_fn = function() {
389
510
  };
390
511
  _updateStalelist = new WeakSet();
391
512
  updateStalelist_fn = async function() {
392
- let stalelistResponse;
393
- let hotlistDiffsResponse;
513
+ let stalelistResponse = null;
514
+ let hotlistDiffsResponse = null;
515
+ let c2DomainBlocklistResponse = null;
394
516
  try {
395
- stalelistResponse = await __privateMethod(this, _queryConfig, queryConfig_fn).call(this, METAMASK_STALELIST_URL).then((d) => d);
517
+ const stalelistPromise = __privateMethod(this, _queryConfig, queryConfig_fn).call(this, METAMASK_STALELIST_URL);
518
+ const c2DomainBlocklistPromise = __privateMethod(this, _queryConfig, queryConfig_fn).call(this, C2_DOMAIN_BLOCKLIST_URL);
519
+ [stalelistResponse, c2DomainBlocklistResponse] = await Promise.all([
520
+ stalelistPromise,
521
+ c2DomainBlocklistPromise
522
+ ]);
396
523
  if (stalelistResponse?.data && stalelistResponse.data.lastUpdated > 0) {
397
524
  hotlistDiffsResponse = await __privateMethod(this, _queryConfig, queryConfig_fn).call(this, `${METAMASK_HOTLIST_DIFF_URL}/${stalelistResponse.data.lastUpdated}`);
398
525
  }
@@ -401,6 +528,7 @@ updateStalelist_fn = async function() {
401
528
  this.update((draftState) => {
402
529
  draftState.stalelistLastFetched = timeNow;
403
530
  draftState.hotlistLastFetched = timeNow;
531
+ draftState.c2DomainBlocklistLastFetched = timeNow;
404
532
  });
405
533
  }
406
534
  if (!stalelistResponse || !hotlistDiffsResponse) {
@@ -410,6 +538,7 @@ updateStalelist_fn = async function() {
410
538
  const metamaskListState = {
411
539
  ...eth_phishing_detect_config,
412
540
  ...partialState,
541
+ c2DomainBlocklist: c2DomainBlocklistResponse ? c2DomainBlocklistResponse.recentlyAdded : [],
413
542
  name: phishingListKeyNameMap.eth_phishing_detect_config
414
543
  };
415
544
  const newMetaMaskListState = applyDiffs(
@@ -439,13 +568,48 @@ updateHotlist_fn = async function() {
439
568
  return;
440
569
  }
441
570
  const hotlist = hotlistResponse.data;
442
- const newPhishingLists = this.state.phishingLists.map(
443
- (phishingList) => applyDiffs(
571
+ const newPhishingLists = this.state.phishingLists.map((phishingList) => {
572
+ const updatedList = applyDiffs(
444
573
  phishingList,
445
574
  hotlist,
446
- phishingListNameKeyMap[phishingList.name]
447
- )
448
- );
575
+ phishingListNameKeyMap[phishingList.name],
576
+ [],
577
+ []
578
+ );
579
+ return updatedList;
580
+ });
581
+ this.update((draftState) => {
582
+ draftState.phishingLists = newPhishingLists;
583
+ });
584
+ this.updatePhishingDetector();
585
+ };
586
+ _updateC2DomainBlocklist = new WeakSet();
587
+ updateC2DomainBlocklist_fn = async function() {
588
+ let c2DomainBlocklistResponse = null;
589
+ try {
590
+ c2DomainBlocklistResponse = await __privateMethod(this, _queryConfig, queryConfig_fn).call(this, `${C2_DOMAIN_BLOCKLIST_URL}?timestamp=${roundToNearestMinute(
591
+ this.state.c2DomainBlocklistLastFetched
592
+ )}`);
593
+ } finally {
594
+ this.update((draftState) => {
595
+ draftState.c2DomainBlocklistLastFetched = fetchTimeNow();
596
+ });
597
+ }
598
+ if (!c2DomainBlocklistResponse) {
599
+ return;
600
+ }
601
+ const recentlyAddedC2Domains = c2DomainBlocklistResponse.recentlyAdded;
602
+ const recentlyRemovedC2Domains = c2DomainBlocklistResponse.recentlyRemoved;
603
+ const newPhishingLists = this.state.phishingLists.map((phishingList) => {
604
+ const updatedList = applyDiffs(
605
+ phishingList,
606
+ [],
607
+ phishingListNameKeyMap[phishingList.name],
608
+ recentlyAddedC2Domains,
609
+ recentlyRemovedC2Domains
610
+ );
611
+ return updatedList;
612
+ });
449
613
  this.update((draftState) => {
450
614
  draftState.phishingLists = newPhishingLists;
451
615
  });
@@ -471,6 +635,9 @@ var PhishingController_default = PhishingController;
471
635
  // src/utils.ts
472
636
  var DEFAULT_TOLERANCE = 3;
473
637
  var fetchTimeNow = () => Math.round(Date.now() / 1e3);
638
+ function roundToNearestMinute(unixTimestamp) {
639
+ return Math.floor(unixTimestamp / 60) * 60;
640
+ }
474
641
  var splitStringByPeriod = (stringToSplit) => {
475
642
  const periodIndex = stringToSplit.indexOf(".");
476
643
  return [
@@ -478,7 +645,7 @@ var splitStringByPeriod = (stringToSplit) => {
478
645
  stringToSplit.slice(periodIndex + 1)
479
646
  ];
480
647
  };
481
- var applyDiffs = (listState, hotlistDiffs, listKey) => {
648
+ var applyDiffs = (listState, hotlistDiffs, listKey, recentlyAddedC2Domains = [], recentlyRemovedC2Domains = []) => {
482
649
  const diffsToApply = hotlistDiffs.filter(
483
650
  ({ timestamp, targetList }) => timestamp > listState.lastUpdated && splitStringByPeriod(targetList)[0] === listKey
484
651
  );
@@ -486,7 +653,8 @@ var applyDiffs = (listState, hotlistDiffs, listKey) => {
486
653
  const listSets = {
487
654
  allowlist: new Set(listState.allowlist),
488
655
  blocklist: new Set(listState.blocklist),
489
- fuzzylist: new Set(listState.fuzzylist)
656
+ fuzzylist: new Set(listState.fuzzylist),
657
+ c2DomainBlocklist: new Set(listState.c2DomainBlocklist)
490
658
  };
491
659
  for (const { isRemoval, targetList, url, timestamp } of diffsToApply) {
492
660
  const targetListType = splitStringByPeriod(targetList)[1];
@@ -499,7 +667,16 @@ var applyDiffs = (listState, hotlistDiffs, listKey) => {
499
667
  listSets[targetListType].add(url);
500
668
  }
501
669
  }
670
+ if (listKey === "eth_phishing_detect_config" /* EthPhishingDetectConfig */) {
671
+ for (const hash of recentlyAddedC2Domains) {
672
+ listSets.c2DomainBlocklist.add(hash);
673
+ }
674
+ for (const hash of recentlyRemovedC2Domains) {
675
+ listSets.c2DomainBlocklist.delete(hash);
676
+ }
677
+ }
502
678
  return {
679
+ c2DomainBlocklist: Array.from(listSets.c2DomainBlocklist),
503
680
  allowlist: Array.from(listSets.allowlist),
504
681
  blocklist: Array.from(listSets.blocklist),
505
682
  fuzzylist: Array.from(listSets.fuzzylist),
@@ -564,9 +741,14 @@ var matchPartsAgainstList = (source, list) => {
564
741
  return target.every((part, index) => source[index] === part);
565
742
  });
566
743
  };
744
+ var sha256Hash = (hostname) => {
745
+ const hashBuffer = sha256(new TextEncoder().encode(hostname.toLowerCase()));
746
+ return bytesToHex(hashBuffer);
747
+ };
567
748
 
568
749
  export {
569
750
  fetchTimeNow,
751
+ roundToNearestMinute,
570
752
  applyDiffs,
571
753
  validateConfig,
572
754
  domainToParts,
@@ -576,18 +758,23 @@ export {
576
758
  domainPartsToDomain,
577
759
  domainPartsToFuzzyForm,
578
760
  matchPartsAgainstList,
761
+ sha256Hash,
579
762
  PhishingDetector,
580
763
  PHISHING_CONFIG_BASE_URL,
581
764
  METAMASK_STALELIST_FILE,
582
765
  METAMASK_HOTLIST_DIFF_FILE,
766
+ CLIENT_SIDE_DETECION_BASE_URL,
767
+ C2_DOMAIN_BLOCKLIST_ENDPOINT,
768
+ C2_DOMAIN_BLOCKLIST_REFRESH_INTERVAL,
583
769
  HOTLIST_REFRESH_INTERVAL,
584
770
  STALELIST_REFRESH_INTERVAL,
585
771
  METAMASK_STALELIST_URL,
586
772
  METAMASK_HOTLIST_DIFF_URL,
773
+ C2_DOMAIN_BLOCKLIST_URL,
587
774
  ListKeys,
588
775
  ListNames,
589
776
  phishingListKeyNameMap,
590
777
  PhishingController,
591
778
  PhishingController_default
592
779
  };
593
- //# sourceMappingURL=chunk-Q42CM6EP.mjs.map
780
+ //# sourceMappingURL=chunk-A3KBBZ6O.mjs.map