@metamask/phishing-controller
Advanced tools
Comparing version 3.0.0 to 4.0.0
@@ -9,2 +9,11 @@ # Changelog | ||
## [4.0.0] | ||
### Changed | ||
- **BREAKING:** Switch to new phishing configuration API that returns a diff since the last update ([#1123](https://github.com/MetaMask/core/pull/1123)) | ||
- The "hotlist" has been replaced by a service that returns any configuration changes since the last update. This should reduce network traffic even further. | ||
- The endpoints used are now `https://phishing-detection.metafi.codefi.network/v1/stalelist` and `https://phishing-detection.metafi.codefi.network/v1/diffsSince/:lastUpdated` | ||
- **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)) | ||
- The `listState` state property has been replaced with an array of phishing list state objects (one entry for MetaMask, one for PhishFort). | ||
- The PhishFort config is deduplicated server-side, so it should have zero overlap with the MetaMask configuration (which helps reduce memory/disk usage) | ||
## [3.0.0] | ||
@@ -61,3 +70,4 @@ ### Removed | ||
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@3.0.0...HEAD | ||
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@4.0.0...HEAD | ||
[4.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@3.0.0...@metamask/phishing-controller@4.0.0 | ||
[3.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@2.0.0...@metamask/phishing-controller@3.0.0 | ||
@@ -64,0 +74,0 @@ [2.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@1.1.2...@metamask/phishing-controller@2.0.0 |
export * from './PhishingController'; | ||
//# sourceMappingURL=index.d.ts.map |
import { BaseController, BaseConfig, BaseState } from '@metamask/base-controller'; | ||
/** | ||
* @type ListTypes | ||
* | ||
* Type outlining the types of lists provided by aggregating different source lists | ||
*/ | ||
export declare type ListTypes = 'fuzzylist' | 'blocklist' | 'allowlist'; | ||
/** | ||
* @type EthPhishingResponse | ||
@@ -24,5 +30,4 @@ * | ||
* Interface defining expected type of the stalelist.json file. | ||
* @property allowlist - List of approved origins (legacy naming "whitelist") | ||
* @property blocklist - List of unapproved origins (legacy naming "blacklist") | ||
* @property fuzzylist - List of fuzzy-matched unapproved origins | ||
* @property eth_phishing_detect_config - Stale list sourced from eth-phishing-detect's config.json. | ||
* @property phishfort_hotlist - Stale list sourced from phishfort's hotlist.json. Only includes blocklist. Deduplicated entries from eth_phishing_detect_config. | ||
* @property tolerance - Fuzzy match tolerance level | ||
@@ -33,5 +38,4 @@ * @property lastUpdated - Timestamp of last update. | ||
export interface PhishingStalelist { | ||
allowlist: string[]; | ||
blocklist: string[]; | ||
fuzzylist: string[]; | ||
eth_phishing_detect_config: Record<ListTypes, string[]>; | ||
phishfort_hotlist: Record<ListTypes, string[]>; | ||
tolerance: number; | ||
@@ -51,2 +55,3 @@ version: number; | ||
* @property version - Version of the phishing list state. | ||
* @property name - Name of the list. Used for attribution. | ||
*/ | ||
@@ -60,3 +65,3 @@ export interface PhishingListState { | ||
lastUpdated: number; | ||
name: string; | ||
name: ListNames; | ||
} | ||
@@ -92,5 +97,8 @@ /** | ||
timestamp: number; | ||
targetList: 'fuzzylist' | 'blocklist' | 'allowlist'; | ||
targetList: `${ListKeys}.${ListTypes}`; | ||
isRemoval?: boolean; | ||
} | ||
export interface DataResultWrapper<T> { | ||
data: T; | ||
} | ||
/** | ||
@@ -125,3 +133,3 @@ * @type Hotlist | ||
export interface PhishingState extends BaseState { | ||
listState: PhishingListState; | ||
phishingLists: PhishingListState[]; | ||
whitelist: string[]; | ||
@@ -131,5 +139,5 @@ hotlistLastFetched: number; | ||
} | ||
export declare const PHISHING_CONFIG_BASE_URL = "https://static.metafi.codefi.network/api/v1/lists"; | ||
export declare const METAMASK_STALELIST_FILE = "/stalelist.json"; | ||
export declare const METAMASK_HOTLIST_DIFF_FILE = "/hotlist.json"; | ||
export declare const PHISHING_CONFIG_BASE_URL = "https://phishing-detection.metafi.codefi.network"; | ||
export declare const METAMASK_STALELIST_FILE = "/v1/stalelist"; | ||
export declare const METAMASK_HOTLIST_DIFF_FILE = "/v1/diffsSince"; | ||
export declare const HOTLIST_REFRESH_INTERVAL: number; | ||
@@ -140,2 +148,25 @@ export declare const STALELIST_REFRESH_INTERVAL: number; | ||
/** | ||
* Enum containing upstream data provider source list keys. | ||
* These are the keys denoting lists consumed by the upstream data provider. | ||
*/ | ||
export declare enum ListKeys { | ||
PhishfortHotlist = "phishfort_hotlist", | ||
EthPhishingDetectConfig = "eth_phishing_detect_config" | ||
} | ||
/** | ||
* Enum containing downstream client attribution names. | ||
*/ | ||
export declare enum ListNames { | ||
MetaMask = "MetaMask", | ||
Phishfort = "Phishfort" | ||
} | ||
/** | ||
* Maps from list key sourced from upstream data | ||
* provider to downstream client attribution name. | ||
*/ | ||
export declare const phishingListKeyNameMap: { | ||
eth_phishing_detect_config: ListNames; | ||
phishfort_hotlist: ListNames; | ||
}; | ||
/** | ||
* Controller that manages community-maintained lists of approved and unapproved website origins. | ||
@@ -232,1 +263,2 @@ */ | ||
export default PhishingController; | ||
//# sourceMappingURL=PhishingController.d.ts.map |
@@ -22,2 +22,13 @@ "use strict"; | ||
}; | ||
var __rest = (this && this.__rest) || function (s, e) { | ||
var t = {}; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) | ||
t[p] = s[p]; | ||
if (s != null && typeof Object.getOwnPropertySymbols === "function") | ||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { | ||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) | ||
t[p[i]] = s[p[i]]; | ||
} | ||
return t; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -28,3 +39,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.PhishingController = 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; | ||
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; | ||
const punycode_1 = require("punycode/"); | ||
@@ -36,5 +47,5 @@ const config_json_1 = __importDefault(require("eth-phishing-detect/src/config.json")); | ||
const utils_1 = require("./utils"); | ||
exports.PHISHING_CONFIG_BASE_URL = 'https://static.metafi.codefi.network/api/v1/lists'; | ||
exports.METAMASK_STALELIST_FILE = '/stalelist.json'; | ||
exports.METAMASK_HOTLIST_DIFF_FILE = '/hotlist.json'; | ||
exports.PHISHING_CONFIG_BASE_URL = 'https://phishing-detection.metafi.codefi.network'; | ||
exports.METAMASK_STALELIST_FILE = '/v1/stalelist'; | ||
exports.METAMASK_HOTLIST_DIFF_FILE = '/v1/diffsSince'; | ||
exports.HOTLIST_REFRESH_INTERVAL = 30 * 60; // 30 mins in seconds | ||
@@ -45,2 +56,35 @@ exports.STALELIST_REFRESH_INTERVAL = 4 * 24 * 60 * 60; // 4 days in seconds | ||
/** | ||
* Enum containing upstream data provider source list keys. | ||
* These are the keys denoting lists consumed by the upstream data provider. | ||
*/ | ||
var ListKeys; | ||
(function (ListKeys) { | ||
ListKeys["PhishfortHotlist"] = "phishfort_hotlist"; | ||
ListKeys["EthPhishingDetectConfig"] = "eth_phishing_detect_config"; | ||
})(ListKeys = exports.ListKeys || (exports.ListKeys = {})); | ||
/** | ||
* Enum containing downstream client attribution names. | ||
*/ | ||
var ListNames; | ||
(function (ListNames) { | ||
ListNames["MetaMask"] = "MetaMask"; | ||
ListNames["Phishfort"] = "Phishfort"; | ||
})(ListNames = exports.ListNames || (exports.ListNames = {})); | ||
/** | ||
* Maps from downstream client attribution name | ||
* to list key sourced from upstream data provider. | ||
*/ | ||
const phishingListNameKeyMap = { | ||
[ListNames.Phishfort]: ListKeys.PhishfortHotlist, | ||
[ListNames.MetaMask]: ListKeys.EthPhishingDetectConfig, | ||
}; | ||
/** | ||
* Maps from list key sourced from upstream data | ||
* provider to downstream client attribution name. | ||
*/ | ||
exports.phishingListKeyNameMap = { | ||
[ListKeys.EthPhishingDetectConfig]: ListNames.MetaMask, | ||
[ListKeys.PhishfortHotlist]: ListNames.Phishfort, | ||
}; | ||
/** | ||
* Controller that manages community-maintained lists of approved and unapproved website origins. | ||
@@ -69,11 +113,13 @@ */ | ||
this.defaultState = { | ||
listState: { | ||
allowlist: config_json_1.default.whitelist, | ||
blocklist: config_json_1.default.blacklist, | ||
fuzzylist: config_json_1.default.fuzzylist, | ||
tolerance: config_json_1.default.tolerance, | ||
version: config_json_1.default.version, | ||
name: 'MetaMask', | ||
lastUpdated: 0, | ||
}, | ||
phishingLists: [ | ||
{ | ||
allowlist: config_json_1.default.whitelist, | ||
blocklist: config_json_1.default.blacklist, | ||
fuzzylist: config_json_1.default.fuzzylist, | ||
tolerance: config_json_1.default.tolerance, | ||
version: config_json_1.default.version, | ||
name: ListNames.MetaMask, | ||
lastUpdated: 0, | ||
}, | ||
], | ||
whitelist: [], | ||
@@ -90,12 +136,3 @@ hotlistLastFetched: 0, | ||
updatePhishingDetector() { | ||
this.detector = new detector_1.default([ | ||
{ | ||
allowlist: this.state.listState.allowlist, | ||
blocklist: this.state.listState.blocklist, | ||
fuzzylist: this.state.listState.fuzzylist, | ||
tolerance: this.state.listState.tolerance, | ||
name: `MetaMask`, | ||
version: this.state.listState.version, | ||
}, | ||
]); | ||
this.detector = new detector_1.default(this.state.phishingLists); | ||
} | ||
@@ -253,9 +290,11 @@ /** | ||
} | ||
let stalelist; | ||
let hotlistDiffs; | ||
let stalelistResponse; | ||
let hotlistDiffsResponse; | ||
try { | ||
[stalelist, hotlistDiffs] = yield Promise.all([ | ||
this.queryConfig(exports.METAMASK_STALELIST_URL), | ||
this.queryConfig(exports.METAMASK_HOTLIST_DIFF_URL), | ||
]); | ||
stalelistResponse = yield this.queryConfig(exports.METAMASK_STALELIST_URL).then((d) => d); | ||
// Fetching hotlist diffs relies on having a lastUpdated timestamp to do `GET /v1/diffsSince/:timestamp`, | ||
// so it doesn't make sense to call if there is not a timestamp to begin with. | ||
if ((stalelistResponse === null || stalelistResponse === void 0 ? void 0 : stalelistResponse.data) && stalelistResponse.data.lastUpdated > 0) { | ||
hotlistDiffsResponse = yield this.queryConfig(`${exports.METAMASK_HOTLIST_DIFF_URL}/${stalelistResponse.data.lastUpdated}`); | ||
} | ||
} | ||
@@ -271,9 +310,13 @@ finally { | ||
} | ||
if (!stalelist || !hotlistDiffs) { | ||
if (!stalelistResponse || !hotlistDiffsResponse) { | ||
return; | ||
} | ||
const _a = stalelistResponse.data, { phishfort_hotlist, eth_phishing_detect_config } = _a, partialState = __rest(_a, ["phishfort_hotlist", "eth_phishing_detect_config"]); | ||
const phishfortListState = Object.assign(Object.assign(Object.assign({}, phishfort_hotlist), partialState), { fuzzylist: [], allowlist: [], name: exports.phishingListKeyNameMap.phishfort_hotlist }); | ||
const metamaskListState = Object.assign(Object.assign(Object.assign({}, eth_phishing_detect_config), partialState), { name: exports.phishingListKeyNameMap.eth_phishing_detect_config }); | ||
// Correctly shaping eth-phishing-detect state by applying hotlist diffs to the stalelist. | ||
const newListState = (0, utils_1.applyDiffs)(stalelist, hotlistDiffs); | ||
const newPhishfortListState = (0, utils_1.applyDiffs)(phishfortListState, hotlistDiffsResponse.data, ListKeys.PhishfortHotlist); | ||
const newMetaMaskListState = (0, utils_1.applyDiffs)(metamaskListState, hotlistDiffsResponse.data, ListKeys.EthPhishingDetectConfig); | ||
this.update({ | ||
listState: newListState, | ||
phishingLists: [newMetaMaskListState, newPhishfortListState], | ||
}); | ||
@@ -287,8 +330,9 @@ this.updatePhishingDetector(); | ||
} | ||
let hotlistDiffs; | ||
const lastDiffTimestamp = Math.max(...this.state.phishingLists.map(({ lastUpdated }) => lastUpdated)); | ||
let hotlistResponse; | ||
try { | ||
hotlistDiffs = yield this.queryConfig(exports.METAMASK_HOTLIST_DIFF_URL); | ||
hotlistResponse = yield this.queryConfig(`${exports.METAMASK_HOTLIST_DIFF_URL}/${lastDiffTimestamp}`); | ||
} | ||
finally { | ||
// Set `stalelistLastFetched` even for failed requests to prevent server from being overwhelmed with | ||
// Set `hotlistLastFetched` even for failed requests to prevent server from being overwhelmed with | ||
// traffic after a network disruption. | ||
@@ -299,9 +343,9 @@ this.update({ | ||
} | ||
if (!hotlistDiffs) { | ||
if (!(hotlistResponse === null || hotlistResponse === void 0 ? void 0 : hotlistResponse.data)) { | ||
return; | ||
} | ||
// Correctly shaping MetaMask config. | ||
const newListState = (0, utils_1.applyDiffs)(this.state.listState, hotlistDiffs); | ||
const hotlist = hotlistResponse.data; | ||
const newPhishingLists = this.state.phishingLists.map((phishingList) => (0, utils_1.applyDiffs)(phishingList, hotlist, phishingListNameKeyMap[phishingList.name])); | ||
this.update({ | ||
listState: newListState, | ||
phishingLists: newPhishingLists, | ||
}); | ||
@@ -308,0 +352,0 @@ this.updatePhishingDetector(); |
@@ -1,2 +0,2 @@ | ||
import { Hotlist, PhishingListState, PhishingStalelist } from './PhishingController'; | ||
import { Hotlist, ListKeys, PhishingListState } from './PhishingController'; | ||
/** | ||
@@ -13,4 +13,6 @@ * Fetches current epoch time in seconds. | ||
* @param hotlistDiffs - the diffs to apply to the listState if valid. | ||
* @param listKey - the key associated with the input/output phishing list state. | ||
* @returns the new list state | ||
*/ | ||
export declare const applyDiffs: (listState: PhishingStalelist, hotlistDiffs: Hotlist) => PhishingListState; | ||
export declare const applyDiffs: (listState: PhishingListState, hotlistDiffs: Hotlist, listKey: ListKeys) => PhishingListState; | ||
//# sourceMappingURL=utils.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.applyDiffs = exports.fetchTimeNow = void 0; | ||
const PhishingController_1 = require("./PhishingController"); | ||
/** | ||
@@ -12,2 +13,15 @@ * Fetches current epoch time in seconds. | ||
/** | ||
* Split a string into two pieces, using the first period as the delimiter. | ||
* | ||
* @param stringToSplit - The string to split. | ||
* @returns An array of length two containing the beginning and end of the string. | ||
*/ | ||
const splitStringByPeriod = (stringToSplit) => { | ||
const periodIndex = stringToSplit.indexOf('.'); | ||
return [ | ||
stringToSplit.slice(0, periodIndex), | ||
stringToSplit.slice(periodIndex + 1), | ||
]; | ||
}; | ||
/** | ||
* Determines which diffs are applicable to the listState, then applies those diffs. | ||
@@ -17,6 +31,15 @@ * | ||
* @param hotlistDiffs - the diffs to apply to the listState if valid. | ||
* @param listKey - the key associated with the input/output phishing list state. | ||
* @returns the new list state | ||
*/ | ||
const applyDiffs = (listState, hotlistDiffs) => { | ||
const diffsToApply = hotlistDiffs.filter(({ timestamp }) => timestamp > listState.lastUpdated); | ||
const applyDiffs = (listState, hotlistDiffs, listKey) => { | ||
// filter to remove diffs that were added before the lastUpdate time. | ||
// filter to remove diffs that aren't applicable to the specified list (by listKey). | ||
const diffsToApply = hotlistDiffs.filter(({ timestamp, targetList }) => timestamp > listState.lastUpdated && | ||
splitStringByPeriod(targetList)[0] === listKey); | ||
// the reason behind using latestDiffTimestamp as the lastUpdated time | ||
// is so that we can benefit server-side from memoization due to end client's | ||
// `GET /v1/diffSince/:timestamp` requests lining up with | ||
// our periodic updates (which create diffs at specific timestamps). | ||
let latestDiffTimestamp = listState.lastUpdated; | ||
const listSets = { | ||
@@ -27,11 +50,14 @@ allowlist: new Set(listState.allowlist), | ||
}; | ||
for (const { isRemoval, targetList, url } of diffsToApply) { | ||
for (const { isRemoval, targetList, url, timestamp } of diffsToApply) { | ||
const targetListType = splitStringByPeriod(targetList)[1]; | ||
if (timestamp > latestDiffTimestamp) { | ||
latestDiffTimestamp = timestamp; | ||
} | ||
if (isRemoval) { | ||
listSets[targetList].delete(url); | ||
listSets[targetListType].delete(url); | ||
} | ||
else { | ||
listSets[targetList].add(url); | ||
listSets[targetListType].add(url); | ||
} | ||
} | ||
const now = (0, exports.fetchTimeNow)(); | ||
return { | ||
@@ -42,5 +68,5 @@ allowlist: Array.from(listSets.allowlist), | ||
version: listState.version, | ||
name: 'MetaMask', | ||
name: PhishingController_1.phishingListKeyNameMap[listKey], | ||
tolerance: listState.tolerance, | ||
lastUpdated: now, | ||
lastUpdated: latestDiffTimestamp, | ||
}; | ||
@@ -47,0 +73,0 @@ }; |
{ | ||
"name": "@metamask/phishing-controller", | ||
"version": "3.0.0", | ||
"version": "4.0.0", | ||
"description": "Maintains a periodically updated list of approved and unapproved website origins", | ||
@@ -33,3 +33,3 @@ "keywords": [ | ||
"@metamask/base-controller": "^2.0.0", | ||
"@metamask/controller-utils": "^3.0.0", | ||
"@metamask/controller-utils": "^3.1.0", | ||
"@types/punycode": "^2.1.0", | ||
@@ -36,0 +36,0 @@ "eth-phishing-detect": "^1.2.0", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
72365
16
704