From 8768a176460f8a01d0848a1eacbc2ae4fe1c3d65 Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Tue, 16 Jul 2024 12:41:47 +0200 Subject: [PATCH 1/3] Release/175.0.0 (#4525) ## Explanation Releases assets-controllers to new version `36.0.0` ## References ## Changelog ### `@metamask/assets-controllers` ### Added - **Added**: Add optional `topBid` property to the `NftMetadata` type. This property must be of type `TopBid`. ([#4522](https://github.com/MetaMask/core/pull/4522)) - **Added**: Add optional `floorAsk` property to the `TokenCollection` type. This property must be of type `FloorAskCollection`. ([#4522](https://github.com/MetaMask/core/pull/4522)) - **Added**: Add linea mainnet support to nft detection supported networks ([#4515](https://github.com/MetaMask/core/pull/4515)) - **Added**: Fetch NFT collections data from the NFT-API `getCollections` API when calling the `detectNfts` method of `NftDetectionController`, and the `updateNftMetadata` and `watchNft` methods of `NftController`. ([#4443](https://github.com/MetaMask/core/pull/4443)) ### Changed - **Changed**: Bump `@metamask/utils` to `^9.0.0` ([#4508](https://github.com/MetaMask/core/pull/4516)) - **Changed**: Bump `@metamask/rpc-errors` to `^6.3.1` ([#4498](https://github.com/MetaMask/core/pull/4516)) ### Fixed - **Breaking**: Changed attributes type in `NftMetadata` to `Attributes[]` ([#4522](https://github.com/MetaMask/core/pull/4522)) ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've highlighted breaking changes using the "BREAKING" category above as appropriate --- package.json | 2 +- packages/assets-controllers/CHANGELOG.md | 23 ++++++++++++++++++++++- packages/assets-controllers/package.json | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6b379eadbd..29db6e89ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/core-monorepo", - "version": "174.0.0", + "version": "175.0.0", "private": true, "description": "Monorepo for packages shared between MetaMask clients", "repository": { diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 869c8c5b06..dba8022844 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [36.0.0] + +### Added + +- Add optional `topBid` property to the `NftMetadata` type. This property must be of type `TopBid`. ([#4522](https://github.com/MetaMask/core/pull/4522)) +- Add optional `floorAsk` property to the `TokenCollection` type. This property must be of type `FloorAskCollection`. ([#4522](https://github.com/MetaMask/core/pull/4522)) +- Add linea mainnet support to nft detection supported networks ([#4515](https://github.com/MetaMask/core/pull/4515)) +- The `Collection` type is expanded to include the following 'string'-type optional properties: `contractDeployedAt`, `creator`, `ownerCount`, and an optional property `topBid` of the type `TopBid & { sourceDomain?: string; }`. ([#4443](https://github.com/MetaMask/core/pull/4443)) + +### Changed + +- Fetch NFT collections data from the NFT-API `Get Collections` endpoint when calling the `detectNfts` method of `NftDetectionController`, and the `updateNftMetadata` and `watchNft` methods of `NftController`. ([#4443](https://github.com/MetaMask/core/pull/4443)) +- Bump `@metamask/utils` to `^9.0.0` ([#4516](https://github.com/MetaMask/core/pull/4516)) +- Bump `@metamask/rpc-errors` to `^6.3.1` ([#4516](https://github.com/MetaMask/core/pull/4516)) + +### Fixed + +- **BREAKING:** The `attributes` property of the `NftMetadata` type must be of type `Attributes[]` ([#4522](https://github.com/MetaMask/core/pull/4522)) + - The `attributes` property was added and typed as `Attributes` on `v28.0.0`. + ## [35.0.0] ### Changed @@ -993,7 +1013,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use Ethers for AssetsContractController ([#845](https://github.com/MetaMask/core/pull/845)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@35.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@36.0.0...HEAD +[36.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@35.0.0...@metamask/assets-controllers@36.0.0 [35.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@34.0.0...@metamask/assets-controllers@35.0.0 [34.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@33.0.0...@metamask/assets-controllers@34.0.0 [33.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@32.0.0...@metamask/assets-controllers@33.0.0 diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index 5238835699..e51b1267cf 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/assets-controllers", - "version": "35.0.0", + "version": "36.0.0", "description": "Controllers which manage interactions involving ERC-20, ERC-721, and ERC-1155 tokens (including NFTs)", "keywords": [ "MetaMask", From 4511d583baaecc5a714ca3bd79909f3654a92cef Mon Sep 17 00:00:00 2001 From: Harry <409H@users.noreply.github.com> Date: Tue, 16 Jul 2024 19:30:55 +0100 Subject: [PATCH 2/3] PhishingDetect to support IPFS CID blocking (#4465) This allows us to push IPFS cIDs to the blocklist and have them blocked by the eth-phishing-detect service. We only need to note the CIDs and not block the gateways. --------- Co-authored-by: Elliot Winkler --- .../src/PhishingController.test.ts | 75 +++++--- .../src/PhishingDetector.test.ts | 179 +++++++++++++++--- .../src/PhishingDetector.ts | 52 ++++- .../phishing-controller/src/tests/utils.ts | 18 ++ packages/phishing-controller/tsconfig.json | 2 +- 5 files changed, 264 insertions(+), 62 deletions(-) create mode 100644 packages/phishing-controller/src/tests/utils.ts diff --git a/packages/phishing-controller/src/PhishingController.test.ts b/packages/phishing-controller/src/PhishingController.test.ts index 84fbf6b593..2fdfb885a5 100644 --- a/packages/phishing-controller/src/PhishingController.test.ts +++ b/packages/phishing-controller/src/PhishingController.test.ts @@ -12,6 +12,7 @@ import { type PhishingControllerActions, type PhishingControllerOptions, } from './PhishingController'; +import { formatHostnameToUrl } from './tests/utils'; const controllerName = 'PhishingController'; @@ -218,14 +219,18 @@ describe('PhishingController', () => { expect(controller.isStalelistOutOfDate()).toBe(false); await controller.maybeUpdateState(); expect( - controller.test('this-should-not-be-in-default-blocklist.com'), + controller.test( + formatHostnameToUrl('this-should-not-be-in-default-blocklist.com'), + ), ).toMatchObject({ result: false, type: 'all', }); expect( - controller.test('this-should-not-be-in-default-allowlist.com'), + controller.test( + formatHostnameToUrl('this-should-not-be-in-default-allowlist.com'), + ), ).toMatchObject({ result: false, type: 'all', @@ -235,14 +240,18 @@ describe('PhishingController', () => { await controller.maybeUpdateState(); expect( - controller.test('this-should-not-be-in-default-blocklist.com'), + controller.test( + formatHostnameToUrl('this-should-not-be-in-default-blocklist.com'), + ), ).toMatchObject({ result: true, type: 'blocklist', }); expect( - controller.test('this-should-not-be-in-default-allowlist.com'), + controller.test( + formatHostnameToUrl('this-should-not-be-in-default-allowlist.com'), + ), ).toMatchObject({ result: false, type: 'allowlist', @@ -479,7 +488,7 @@ describe('PhishingController', () => { .reply(200, { data: [] }); const controller = getPhishingController(); await controller.updateStalelist(); - expect(controller.test('metamask.io')).toMatchObject({ + expect(controller.test(formatHostnameToUrl('metamask.io'))).toMatchObject({ result: false, type: 'allowlist', name: ListNames.MetaMask, @@ -513,7 +522,7 @@ describe('PhishingController', () => { const controller = getPhishingController(); await controller.updateStalelist(); - expect(controller.test('i❤.ws')).toMatchObject({ + expect(controller.test(formatHostnameToUrl('i❤.ws'))).toMatchObject({ result: false, type: 'all', }); @@ -546,7 +555,7 @@ describe('PhishingController', () => { const controller = getPhishingController(); await controller.updateStalelist(); - expect(controller.test('xn--i-7iq.ws')).toMatchObject({ + expect(controller.test(formatHostnameToUrl('xn--i-7iq.ws'))).toMatchObject({ result: false, type: 'all', }); @@ -579,7 +588,7 @@ describe('PhishingController', () => { const controller = getPhishingController(); await controller.updateStalelist(); - expect(controller.test('etnerscan.io')).toMatchObject({ + expect(controller.test(formatHostnameToUrl('etnerscan.io'))).toMatchObject({ result: true, type: 'blocklist', name: ListNames.MetaMask, @@ -612,7 +621,9 @@ describe('PhishingController', () => { .reply(200, { data: [] }); const controller = getPhishingController(); await controller.updateStalelist(); - expect(controller.test('myetherẉalletṭ.com')).toMatchObject({ + expect( + controller.test(formatHostnameToUrl('myetherẉalletṭ.com')), + ).toMatchObject({ result: true, type: 'blocklist', name: ListNames.MetaMask, @@ -646,7 +657,9 @@ describe('PhishingController', () => { const controller = getPhishingController(); await controller.updateStalelist(); - expect(controller.test('xn--myetherallet-4k5fwn.com')).toMatchObject({ + expect( + controller.test(formatHostnameToUrl('xn--myetherallet-4k5fwn.com')), + ).toMatchObject({ result: true, type: 'blocklist', name: ListNames.MetaMask, @@ -689,7 +702,9 @@ describe('PhishingController', () => { const controller = getPhishingController(); await controller.updateStalelist(); expect( - controller.test('e4d600ab9141b7a9859511c77e63b9b3.com'), + controller.test( + formatHostnameToUrl('e4d600ab9141b7a9859511c77e63b9b3.com'), + ), ).toMatchObject({ result: true, type: 'blocklist', @@ -725,7 +740,9 @@ describe('PhishingController', () => { const controller = getPhishingController(); await controller.updateStalelist(); expect( - controller.test('e4d600ab9141b7a9859511c77e63b9b3.com'), + controller.test( + formatHostnameToUrl('e4d600ab9141b7a9859511c77e63b9b3.com'), + ), ).toMatchObject({ result: false, type: 'all', @@ -758,7 +775,7 @@ describe('PhishingController', () => { .reply(200, { data: [] }); const controller = getPhishingController(); await controller.updateStalelist(); - expect(controller.test('opensea.io')).toMatchObject({ + expect(controller.test(formatHostnameToUrl('opensea.io'))).toMatchObject({ result: false, type: 'allowlist', name: ListNames.MetaMask, @@ -791,7 +808,7 @@ describe('PhishingController', () => { .reply(200, { data: [] }); const controller = getPhishingController(); await controller.updateStalelist(); - expect(controller.test('ohpensea.io')).toMatchObject({ + expect(controller.test(formatHostnameToUrl('ohpensea.io'))).toMatchObject({ result: true, type: 'fuzzy', name: ListNames.MetaMask, @@ -825,7 +842,9 @@ describe('PhishingController', () => { const controller = getPhishingController(); await controller.updateStalelist(); expect( - controller.test('this-is-the-official-website-of-opensea.io'), + controller.test( + formatHostnameToUrl('this-is-the-official-website-of-opensea.io'), + ), ).toMatchObject({ result: false, type: 'all', @@ -860,12 +879,12 @@ describe('PhishingController', () => { await controller.updateStalelist(); const unsafeDomain = 'electrum.mx'; assert.equal( - controller.test(unsafeDomain).result, + controller.test(formatHostnameToUrl(unsafeDomain)).result, true, 'Example unsafe domain seems to be safe', ); - controller.bypass(unsafeDomain); - expect(controller.test(unsafeDomain)).toMatchObject({ + controller.bypass(formatHostnameToUrl(unsafeDomain)); + expect(controller.test(formatHostnameToUrl(unsafeDomain))).toMatchObject({ result: false, type: 'all', }); @@ -899,13 +918,13 @@ describe('PhishingController', () => { await controller.updateStalelist(); const unsafeDomain = 'electrum.mx'; assert.equal( - controller.test(unsafeDomain).result, + controller.test(formatHostnameToUrl(unsafeDomain)).result, true, 'Example unsafe domain seems to be safe', ); - controller.bypass(unsafeDomain); - controller.bypass(unsafeDomain); - expect(controller.test(unsafeDomain)).toMatchObject({ + controller.bypass(formatHostnameToUrl(unsafeDomain)); + controller.bypass(formatHostnameToUrl(unsafeDomain)); + expect(controller.test(formatHostnameToUrl(unsafeDomain))).toMatchObject({ result: false, type: 'all', }); @@ -939,12 +958,12 @@ describe('PhishingController', () => { await controller.updateStalelist(); const unsafeDomain = 'myetherẉalletṭ.com'; assert.equal( - controller.test(unsafeDomain).result, + controller.test(formatHostnameToUrl(unsafeDomain)).result, true, 'Example unsafe domain seems to be safe', ); - controller.bypass(unsafeDomain); - expect(controller.test(unsafeDomain)).toMatchObject({ + controller.bypass(formatHostnameToUrl(unsafeDomain)); + expect(controller.test(formatHostnameToUrl(unsafeDomain))).toMatchObject({ result: false, type: 'all', }); @@ -978,12 +997,12 @@ describe('PhishingController', () => { await controller.updateStalelist(); const unsafeDomain = 'xn--myetherallet-4k5fwn.com'; assert.equal( - controller.test(unsafeDomain).result, + controller.test(formatHostnameToUrl(unsafeDomain)).result, true, 'Example unsafe domain seems to be safe', ); - controller.bypass(unsafeDomain); - expect(controller.test(unsafeDomain)).toMatchObject({ + controller.bypass(formatHostnameToUrl(unsafeDomain)); + expect(controller.test(formatHostnameToUrl(unsafeDomain))).toMatchObject({ result: false, type: 'all', }); diff --git a/packages/phishing-controller/src/PhishingDetector.test.ts b/packages/phishing-controller/src/PhishingDetector.test.ts index 9d33f58b0e..2279d96e50 100644 --- a/packages/phishing-controller/src/PhishingDetector.test.ts +++ b/packages/phishing-controller/src/PhishingDetector.test.ts @@ -2,6 +2,7 @@ import { PhishingDetector, type PhishingDetectorOptions, } from './PhishingDetector'; +import { formatHostnameToUrl } from './tests/utils'; describe('PhishingDetector', () => { describe('constructor', () => { @@ -215,7 +216,9 @@ describe('PhishingDetector', () => { describe('with recommended config', () => { it('allows a domain when no config is provided', async () => { await withPhishingDetector([], async ({ detector }) => { - const { result, type } = detector.check('default.com'); + const { result, type } = detector.check( + formatHostnameToUrl('default.com'), + ); expect(result).toBe(false); expect(type).toBe('all'); @@ -243,7 +246,9 @@ describe('PhishingDetector', () => { }, ], async ({ detector }) => { - const { result, type } = detector.check('default.com'); + const { result, type } = detector.check( + formatHostnameToUrl('default.com'), + ); expect(result).toBe(false); expect(type).toBe('all'); @@ -273,7 +278,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'blocked-by-first.com', + formatHostnameToUrl('blocked-by-first.com'), ); expect(result).toBe(true); @@ -305,7 +310,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'blocked-by-second.com', + formatHostnameToUrl('blocked-by-second.com'), ); expect(result).toBe(true); @@ -337,7 +342,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'blocked-by-both.com', + formatHostnameToUrl('blocked-by-both.com'), ); expect(result).toBe(true); @@ -368,7 +373,9 @@ describe('PhishingDetector', () => { }, ], async ({ detector }) => { - const { result, type, name } = detector.check('fuzzy-first.com'); + const { result, type, name } = detector.check( + formatHostnameToUrl('fuzzy-first.com'), + ); expect(result).toBe(true); expect(type).toBe('fuzzy'); @@ -398,7 +405,9 @@ describe('PhishingDetector', () => { }, ], async ({ detector }) => { - const { result, type, name } = detector.check('fuzzy-firstab.com'); + const { result, type, name } = detector.check( + formatHostnameToUrl('fuzzy-firstab.com'), + ); expect(result).toBe(true); expect(type).toBe('fuzzy'); @@ -428,7 +437,9 @@ describe('PhishingDetector', () => { }, ], async ({ detector }) => { - const { result, type } = detector.check('fuzzy-firstabc.com'); + const { result, type } = detector.check( + formatHostnameToUrl('fuzzy-firstabc.com'), + ); expect(result).toBe(false); expect(type).toBe('all'); @@ -457,7 +468,9 @@ describe('PhishingDetector', () => { }, ], async ({ detector }) => { - const { result, type, name } = detector.check('fuzzy-second.com'); + const { result, type, name } = detector.check( + formatHostnameToUrl('fuzzy-second.com'), + ); expect(result).toBe(true); expect(type).toBe('fuzzy'); @@ -487,7 +500,9 @@ describe('PhishingDetector', () => { }, ], async ({ detector }) => { - const { result, type, name } = detector.check('fuzzy-secondab.com'); + const { result, type, name } = detector.check( + formatHostnameToUrl('fuzzy-secondab.com'), + ); expect(result).toBe(true); expect(type).toBe('fuzzy'); @@ -517,7 +532,9 @@ describe('PhishingDetector', () => { }, ], async ({ detector }) => { - const { result, type } = detector.check('fuzzy-secondabc.com'); + const { result, type } = detector.check( + formatHostnameToUrl('fuzzy-secondabc.com'), + ); expect(result).toBe(false); expect(type).toBe('all'); @@ -546,7 +563,9 @@ describe('PhishingDetector', () => { }, ], async ({ detector }) => { - const { result, type, name } = detector.check('fuzzy-both.com'); + const { result, type, name } = detector.check( + formatHostnameToUrl('fuzzy-both.com'), + ); expect(result).toBe(true); expect(type).toBe('fuzzy'); @@ -577,7 +596,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'blocked-by-first.com', + formatHostnameToUrl('blocked-by-first.com'), ); expect(result).toBe(true); @@ -608,7 +627,9 @@ describe('PhishingDetector', () => { }, ], async ({ detector }) => { - const { result, type, name } = detector.check('fuzzy-first.com'); + const { result, type, name } = detector.check( + formatHostnameToUrl('fuzzy-first.com'), + ); expect(result).toBe(true); expect(type).toBe('fuzzy'); @@ -639,7 +660,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'allowed-by-first.com', + formatHostnameToUrl('allowed-by-first.com'), ); expect(result).toBe(false); @@ -671,7 +692,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'allowed-by-second.com', + formatHostnameToUrl('allowed-by-second.com'), ); expect(result).toBe(false); @@ -703,7 +724,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'allowed-and-blocked-first.com', + formatHostnameToUrl('allowed-and-blocked-first.com'), ); expect(result).toBe(false); @@ -735,7 +756,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'allowed-and-fuzzy-first.com', + formatHostnameToUrl('allowed-and-fuzzy-first.com'), ); expect(result).toBe(false); @@ -767,7 +788,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'allowed-and-blocked-second.com', + formatHostnameToUrl('allowed-and-blocked-second.com'), ); expect(result).toBe(false); @@ -799,7 +820,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'allowed-and-fuzzy-second.com', + formatHostnameToUrl('allowed-and-fuzzy-second.com'), ); expect(result).toBe(false); @@ -831,7 +852,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'allowed-by-both.com', + formatHostnameToUrl('allowed-by-both.com'), ); expect(result).toBe(false); @@ -863,7 +884,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'fuzzy-first-allowed-second.com', + formatHostnameToUrl('fuzzy-first-allowed-second.com'), ); expect(result).toBe(false); @@ -895,7 +916,7 @@ describe('PhishingDetector', () => { ], async ({ detector }) => { const { result, type, name } = detector.check( - 'allowed-first-fuzzy-second.com', + formatHostnameToUrl('allowed-first-fuzzy-second.com'), ); expect(result).toBe(false); @@ -918,13 +939,111 @@ describe('PhishingDetector', () => { }, ], async ({ detector }) => { - const { result, type } = detector.check('blocked.com.'); + const { result, type } = detector.check( + formatHostnameToUrl('blocked.com.'), + ); expect(result).toBe(true); expect(type).toBe('blocklist'); }, ); }); + + it('blocks ipfs cid across various formats (cids located in subdomains and paths)', async () => { + // CID should not blocked + await withPhishingDetector( + [ + { + allowlist: [], + blocklist: [ + 'QmUDBVyGwqKdSayk7kDKUaj9J41Ft1DWizcKUx5UmgMgGy', + 'bafybeifx7yeb55armcsxwwitkymga5xf53dxiarykms3ygqic223w5sk3m', + 'example.com', + ], + fuzzylist: [], + name: 'first', + tolerance: 2, + version: 1, + }, + ], + async ({ detector }) => { + const { result, type } = detector.check( + formatHostnameToUrl( + 'cf-ipfs.com/ipfs/bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze', + ), + ); + + expect(result).toBe(false); + expect(type).toBe('all'); + }, + ); + + // Gateways differ on where the CID is... sometimes in the path, sometimes in a magic subdomain + const expectedToBeBlocked = [ + 'ipfs.io/ipfs/bafybeifx7yeb55armcsxwwitkymga5xf53dxiarykms3ygqic223w5sk3m#x-ipfs-companion-no-redirect', + 'gateway.pinata.cloud/ipfs/bafybeifx7yeb55armcsxwwitkymga5xf53dxiarykms3ygqic223w5sk3m#x-ipfs-companion-no-redirect', + 'cloudflare-ipfs.com/ipfs/bafybeifx7yeb55armcsxwwitkymga5xf53dxiarykms3ygqic223w5sk3m#x-ipfs-companion-no-redirect', + 'ipfs.eth.aragon.network/ipfs/bafybeifx7yeb55armcsxwwitkymga5xf53dxiarykms3ygqic223w5sk3m#x-ipfs-companion-no-redirect', + 'bafybeifx7yeb55armcsxwwitkymga5xf53dxiarykms3ygqic223w5sk3m.ipfs.dweb.link/#x-ipfs-companion-no-redirect', + 'bafybeifx7yeb55armcsxwwitkymga5xf53dxiarykms3ygqic223w5sk3m.ipfs.cf-ipfs.com/#x-ipfs-companion-no-redirect', + 'example.com', + 'example.com/foo/bar', + ]; + + // CID should be blocked + for await (const entry of expectedToBeBlocked) { + await withPhishingDetector( + [ + { + allowlist: [], + blocklist: [ + 'QmUDBVyGwqKdSayk7kDKUaj9J41Ft1DWizcKUx5UmgMgGy', + 'bafybeifx7yeb55armcsxwwitkymga5xf53dxiarykms3ygqic223w5sk3m', + 'example.com', + ], + fuzzylist: [], + name: 'first', + tolerance: 2, + version: 1, + }, + ], + async ({ detector }) => { + const { result, type } = detector.check( + formatHostnameToUrl(entry), + ); + + expect(result).toBe(true); + expect(type).toBe('blocklist'); + }, + ); + } + }); + + it('returns a result without a version when a config lacks a version and the blocklist contains an ipfs cid', async () => { + await withPhishingDetector( + [ + { + allowlist: [], + blocklist: [ + 'bafybeifx7yeb55armcsxwwitkymga5xf53dxiarykms3ygqic223w5sk3m', + ], + fuzzylist: [], + name: 'first', + tolerance: 2, + }, + ], + async ({ detector }) => { + const { result, version } = detector.check( + formatHostnameToUrl( + 'ipfs.io/ipfs/bafybeifx7yeb55armcsxwwitkymga5xf53dxiarykms3ygqic223w5sk3m#x-ipfs-companion-no-redirect', + ), + ); + + expect(result).toBe(true); + expect(version).toBeUndefined(); + }, + ); + }); }); describe('with legacy config', () => { @@ -937,7 +1056,9 @@ describe('PhishingDetector', () => { whitelist: ['allowed.com'], }, async ({ detector }) => { - const { type, result } = detector.check('allowed.com'); + const { type, result } = detector.check( + formatHostnameToUrl('allowed.com'), + ); expect(type).toBe('whitelist'); expect(result).toBe(false); @@ -954,7 +1075,9 @@ describe('PhishingDetector', () => { whitelist: [], }, async ({ detector }) => { - const { type, result } = detector.check('blocked.com'); + const { type, result } = detector.check( + formatHostnameToUrl('blocked.com'), + ); expect(type).toBe('blacklist'); expect(result).toBe(true); @@ -971,7 +1094,9 @@ describe('PhishingDetector', () => { whitelist: [], }, async ({ detector }) => { - const { type, result } = detector.check('fupzy.com'); + const { type, result } = detector.check( + formatHostnameToUrl('fupzy.com'), + ); expect(type).toBe('fuzzy'); expect(result).toBe(true); diff --git a/packages/phishing-controller/src/PhishingDetector.ts b/packages/phishing-controller/src/PhishingDetector.ts index 0c1e13f1b2..66e42da05d 100644 --- a/packages/phishing-controller/src/PhishingDetector.ts +++ b/packages/phishing-controller/src/PhishingDetector.ts @@ -117,14 +117,15 @@ export class PhishingDetector { } /** - * Check if a domain is known to be malicious or similar to a common phishing - * target. + * Check if a url is known to be malicious or similar to a common phishing + * target. This will check the hostname and IPFS CID that is sometimes + * located in the path. * - * @param domain - The domain to check. + * @param url - The url to check. * @returns The result of the check. */ - check(domain: string): PhishingDetectorResult { - const result = this.#check(domain); + check(url: string): PhishingDetectorResult { + const result = this.#check(url); if (this.#legacyConfig) { let legacyType = result.type; @@ -142,7 +143,9 @@ export class PhishingDetector { return result; } - #check(domain: string): PhishingDetectorResult { + #check(url: string): PhishingDetectorResult { + const domain = new URL(url).hostname; + const fqdn = domain.endsWith('.') ? domain.slice(0, -1) : domain; const source = domainToParts(fqdn); @@ -201,7 +204,44 @@ export class PhishingDetector { } } + const ipfsCidMatch = url.match(ipfsCidRegex()); + + // Check for IPFS CID related blocklist entries + if (ipfsCidMatch !== null) { + // there is a cID string somewhere + // Determine if any of the entries are ipfs cids + // Depending on the gateway, the CID is in the path OR a subdomain, so we do a regex match on it all + const cID = ipfsCidMatch[0]; + for (const { blocklist, name, version } of this.#configs) { + const blocklistMatch = blocklist + .filter((entries) => entries.length === 1) + .find((entries) => { + return entries[0] === cID; + }); + if (blocklistMatch) { + return { + name, + match: cID, + result: true, + type: 'blocklist', + version: version === undefined ? version : String(version), + }; + } + } + } + // matched nothing, PASS return { result: false, type: 'all' }; } } + +/** + * Runs a regex match to determine if a string is a IPFS CID + * @returns Regex string for IPFS CID + */ +function ipfsCidRegex() { + // regex from https://stackoverflow.com/a/67176726 + 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,}'; + return new RegExp(reg, 'u'); +} diff --git a/packages/phishing-controller/src/tests/utils.ts b/packages/phishing-controller/src/tests/utils.ts new file mode 100644 index 0000000000..75e1128213 --- /dev/null +++ b/packages/phishing-controller/src/tests/utils.ts @@ -0,0 +1,18 @@ +/** + * Formats a hostname into a URL so we can parse it correctly + * and pass full URLs into the PhishingDetector class. Previously + * only hostnames were supported, but now only full URLs are + * supported since we want to block IPFS CIDs. + * + * @param hostname - the hostname of the URL. + * @returns the href property of a URL object. + */ +export const formatHostnameToUrl = (hostname: string): string => { + let url = ''; + try { + url = new URL(hostname).href; + } catch (e) { + url = new URL(['https://', hostname].join('')).href; + } + return url; +}; diff --git a/packages/phishing-controller/tsconfig.json b/packages/phishing-controller/tsconfig.json index 7ee9852347..d1cf743018 100644 --- a/packages/phishing-controller/tsconfig.json +++ b/packages/phishing-controller/tsconfig.json @@ -7,5 +7,5 @@ { "path": "../base-controller" }, { "path": "../controller-utils" } ], - "include": ["../../types", "./src"] + "include": ["../../types", "./src", "./tests"] } From 83957e2522d723fbfff7f87f9108d1d28b927af2 Mon Sep 17 00:00:00 2001 From: Owen Craston Date: Tue, 16 Jul 2024 15:04:25 -0700 Subject: [PATCH 3/3] Release/176.0.0 (#4527) ## Explanation Release new versions of the following packages, - `@metamask/keyring-controller`: 17.1.1 - `@metamask/message-manager`: 10.0.1 ## References ## Changelog ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've highlighted breaking changes using the "BREAKING" category above as appropriate --- package.json | 2 +- packages/accounts-controller/package.json | 2 +- packages/assets-controllers/package.json | 2 +- packages/keyring-controller/CHANGELOG.md | 13 +++++++++++- packages/keyring-controller/package.json | 4 ++-- packages/message-manager/CHANGELOG.md | 14 ++++++++++++- packages/message-manager/package.json | 2 +- .../package.json | 2 +- packages/preferences-controller/package.json | 2 +- packages/signature-controller/package.json | 4 ++-- .../user-operation-controller/package.json | 2 +- yarn.lock | 20 +++++++++---------- 12 files changed, 46 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 29db6e89ca..373cfcfa19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/core-monorepo", - "version": "175.0.0", + "version": "176.0.0", "private": true, "description": "Monorepo for packages shared between MetaMask clients", "repository": { diff --git a/packages/accounts-controller/package.json b/packages/accounts-controller/package.json index b224445f96..18b6274f2c 100644 --- a/packages/accounts-controller/package.json +++ b/packages/accounts-controller/package.json @@ -45,7 +45,7 @@ "@metamask/base-controller": "^6.0.1", "@metamask/eth-snap-keyring": "^4.3.1", "@metamask/keyring-api": "^8.0.0", - "@metamask/keyring-controller": "^17.1.0", + "@metamask/keyring-controller": "^17.1.1", "@metamask/snaps-sdk": "^4.2.0", "@metamask/snaps-utils": "^7.4.0", "@metamask/utils": "^9.0.0", diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index e51b1267cf..29adf9d52f 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -53,7 +53,7 @@ "@metamask/contract-metadata": "^2.4.0", "@metamask/controller-utils": "^11.0.1", "@metamask/eth-query": "^4.0.0", - "@metamask/keyring-controller": "^17.1.0", + "@metamask/keyring-controller": "^17.1.1", "@metamask/metamask-eth-abis": "^3.1.1", "@metamask/network-controller": "^20.0.0", "@metamask/polling-controller": "^9.0.0", diff --git a/packages/keyring-controller/CHANGELOG.md b/packages/keyring-controller/CHANGELOG.md index ad9c384ec2..73b378c4a0 100644 --- a/packages/keyring-controller/CHANGELOG.md +++ b/packages/keyring-controller/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [17.1.1] + +### Changed + +- Bump `@metamask/utils` to `^9.0.0`, `@metamask/rpc-errors` to `^6.3.1` ([#4516](https://github.com/MetaMask/core/pull/4516)) + +### Fixed + +- Clear encryption salt and key in `setLocked` and `#createNewVaultWithKeyring` to ensure that encryption key is always generated with the latest password ([#4514](https://github.com/MetaMask/core/pull/4514)) + ## [17.1.0] ### Added @@ -497,7 +507,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@17.1.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@17.1.1...HEAD +[17.1.1]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@17.1.0...@metamask/keyring-controller@17.1.1 [17.1.0]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@17.0.0...@metamask/keyring-controller@17.1.0 [17.0.0]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@16.1.0...@metamask/keyring-controller@17.0.0 [16.1.0]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@16.0.0...@metamask/keyring-controller@16.1.0 diff --git a/packages/keyring-controller/package.json b/packages/keyring-controller/package.json index 63816a13e7..29880d23a2 100644 --- a/packages/keyring-controller/package.json +++ b/packages/keyring-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/keyring-controller", - "version": "17.1.0", + "version": "17.1.1", "description": "Stores identities seen in the wallet and manages interactions such as signing", "keywords": [ "MetaMask", @@ -49,7 +49,7 @@ "@metamask/eth-sig-util": "^7.0.1", "@metamask/eth-simple-keyring": "^6.0.1", "@metamask/keyring-api": "^8.0.0", - "@metamask/message-manager": "^10.0.0", + "@metamask/message-manager": "^10.0.1", "@metamask/utils": "^9.0.0", "async-mutex": "^0.5.0", "ethereumjs-wallet": "^1.0.1", diff --git a/packages/message-manager/CHANGELOG.md b/packages/message-manager/CHANGELOG.md index 1cb9b3b848..a48880c0f4 100644 --- a/packages/message-manager/CHANGELOG.md +++ b/packages/message-manager/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [10.0.1] + +### Changed + +- Bump `@metamask/utils` to `^9.0.0`, `@metamask/rpc-errors` to `^6.3.1` ([#4516](https://github.com/MetaMask/core/pull/4516)) + +### Fixed + +- Add `EventEmitter` type annotation to the `hub` class field of `AbstractMessageManager` ([#4510](https://github.com/MetaMask/core/pull/4510)) + - This ensures that `hub` is not inferred to be a generic type, which would break types for downstream consumers. + ## [10.0.0] ### Changed @@ -255,7 +266,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/message-manager@10.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/message-manager@10.0.1...HEAD +[10.0.1]: https://github.com/MetaMask/core/compare/@metamask/message-manager@10.0.0...@metamask/message-manager@10.0.1 [10.0.0]: https://github.com/MetaMask/core/compare/@metamask/message-manager@9.0.0...@metamask/message-manager@10.0.0 [9.0.0]: https://github.com/MetaMask/core/compare/@metamask/message-manager@8.0.2...@metamask/message-manager@9.0.0 [8.0.2]: https://github.com/MetaMask/core/compare/@metamask/message-manager@8.0.1...@metamask/message-manager@8.0.2 diff --git a/packages/message-manager/package.json b/packages/message-manager/package.json index 5d92c9b7dc..de3c905a0d 100644 --- a/packages/message-manager/package.json +++ b/packages/message-manager/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/message-manager", - "version": "10.0.0", + "version": "10.0.1", "description": "Stores and manages interactions with signing requests", "keywords": [ "MetaMask", diff --git a/packages/notification-services-controller/package.json b/packages/notification-services-controller/package.json index 158b097f1d..01bf65a02c 100644 --- a/packages/notification-services-controller/package.json +++ b/packages/notification-services-controller/package.json @@ -44,7 +44,7 @@ "@contentful/rich-text-html-renderer": "^16.5.2", "@metamask/base-controller": "^6.0.1", "@metamask/controller-utils": "^11.0.1", - "@metamask/keyring-controller": "^17.1.0", + "@metamask/keyring-controller": "^17.1.1", "@metamask/profile-sync-controller": "^0.1.3", "bignumber.js": "^4.1.0", "contentful": "^10.3.6", diff --git a/packages/preferences-controller/package.json b/packages/preferences-controller/package.json index 5015c94a0a..c22ab3240c 100644 --- a/packages/preferences-controller/package.json +++ b/packages/preferences-controller/package.json @@ -46,7 +46,7 @@ }, "devDependencies": { "@metamask/auto-changelog": "^3.4.4", - "@metamask/keyring-controller": "^17.1.0", + "@metamask/keyring-controller": "^17.1.1", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", diff --git a/packages/signature-controller/package.json b/packages/signature-controller/package.json index db14addcc9..f304015ba6 100644 --- a/packages/signature-controller/package.json +++ b/packages/signature-controller/package.json @@ -44,9 +44,9 @@ "@metamask/approval-controller": "^7.0.1", "@metamask/base-controller": "^6.0.1", "@metamask/controller-utils": "^11.0.1", - "@metamask/keyring-controller": "^17.1.0", + "@metamask/keyring-controller": "^17.1.1", "@metamask/logging-controller": "^5.0.0", - "@metamask/message-manager": "^10.0.0", + "@metamask/message-manager": "^10.0.1", "@metamask/rpc-errors": "^6.3.1", "@metamask/utils": "^9.0.0", "lodash": "^4.17.21" diff --git a/packages/user-operation-controller/package.json b/packages/user-operation-controller/package.json index 6e890e58d8..fb12d59d4c 100644 --- a/packages/user-operation-controller/package.json +++ b/packages/user-operation-controller/package.json @@ -47,7 +47,7 @@ "@metamask/controller-utils": "^11.0.1", "@metamask/eth-query": "^4.0.0", "@metamask/gas-fee-controller": "^19.0.0", - "@metamask/keyring-controller": "^17.1.0", + "@metamask/keyring-controller": "^17.1.1", "@metamask/network-controller": "^20.0.0", "@metamask/polling-controller": "^9.0.0", "@metamask/rpc-errors": "^6.3.1", diff --git a/yarn.lock b/yarn.lock index a410fa0a5b..04b10cacf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2204,7 +2204,7 @@ __metadata: "@metamask/base-controller": "npm:^6.0.1" "@metamask/eth-snap-keyring": "npm:^4.3.1" "@metamask/keyring-api": "npm:^8.0.0" - "@metamask/keyring-controller": "npm:^17.1.0" + "@metamask/keyring-controller": "npm:^17.1.1" "@metamask/snaps-controllers": "npm:^8.1.1" "@metamask/snaps-sdk": "npm:^4.2.0" "@metamask/snaps-utils": "npm:^7.4.0" @@ -2322,7 +2322,7 @@ __metadata: "@metamask/eth-query": "npm:^4.0.0" "@metamask/ethjs-provider-http": "npm:^0.3.0" "@metamask/keyring-api": "npm:^8.0.0" - "@metamask/keyring-controller": "npm:^17.1.0" + "@metamask/keyring-controller": "npm:^17.1.1" "@metamask/metamask-eth-abis": "npm:^3.1.1" "@metamask/network-controller": "npm:^20.0.0" "@metamask/polling-controller": "npm:^9.0.0" @@ -3128,7 +3128,7 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-controller@npm:^17.1.0, @metamask/keyring-controller@workspace:packages/keyring-controller": +"@metamask/keyring-controller@npm:^17.1.1, @metamask/keyring-controller@workspace:packages/keyring-controller": version: 0.0.0-use.local resolution: "@metamask/keyring-controller@workspace:packages/keyring-controller" dependencies: @@ -3145,7 +3145,7 @@ __metadata: "@metamask/eth-sig-util": "npm:^7.0.1" "@metamask/eth-simple-keyring": "npm:^6.0.1" "@metamask/keyring-api": "npm:^8.0.0" - "@metamask/message-manager": "npm:^10.0.0" + "@metamask/message-manager": "npm:^10.0.1" "@metamask/scure-bip39": "npm:^2.1.1" "@metamask/utils": "npm:^9.0.0" "@types/jest": "npm:^27.4.1" @@ -3182,7 +3182,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/message-manager@npm:^10.0.0, @metamask/message-manager@workspace:packages/message-manager": +"@metamask/message-manager@npm:^10.0.1, @metamask/message-manager@workspace:packages/message-manager": version: 0.0.0-use.local resolution: "@metamask/message-manager@workspace:packages/message-manager" dependencies: @@ -3306,7 +3306,7 @@ __metadata: "@metamask/auto-changelog": "npm:^3.4.4" "@metamask/base-controller": "npm:^6.0.1" "@metamask/controller-utils": "npm:^11.0.1" - "@metamask/keyring-controller": "npm:^17.1.0" + "@metamask/keyring-controller": "npm:^17.1.1" "@metamask/profile-sync-controller": "npm:^0.1.3" "@types/jest": "npm:^27.4.1" "@types/readable-stream": "npm:^2.3.0" @@ -3504,7 +3504,7 @@ __metadata: "@metamask/auto-changelog": "npm:^3.4.4" "@metamask/base-controller": "npm:^6.0.1" "@metamask/controller-utils": "npm:^11.0.1" - "@metamask/keyring-controller": "npm:^17.1.0" + "@metamask/keyring-controller": "npm:^17.1.1" "@types/jest": "npm:^27.4.1" deepmerge: "npm:^4.2.2" jest: "npm:^27.5.1" @@ -3680,9 +3680,9 @@ __metadata: "@metamask/auto-changelog": "npm:^3.4.4" "@metamask/base-controller": "npm:^6.0.1" "@metamask/controller-utils": "npm:^11.0.1" - "@metamask/keyring-controller": "npm:^17.1.0" + "@metamask/keyring-controller": "npm:^17.1.1" "@metamask/logging-controller": "npm:^5.0.0" - "@metamask/message-manager": "npm:^10.0.0" + "@metamask/message-manager": "npm:^10.0.1" "@metamask/rpc-errors": "npm:^6.3.1" "@metamask/utils": "npm:^9.0.0" "@types/jest": "npm:^27.4.1" @@ -3893,7 +3893,7 @@ __metadata: "@metamask/controller-utils": "npm:^11.0.1" "@metamask/eth-query": "npm:^4.0.0" "@metamask/gas-fee-controller": "npm:^19.0.0" - "@metamask/keyring-controller": "npm:^17.1.0" + "@metamask/keyring-controller": "npm:^17.1.1" "@metamask/network-controller": "npm:^20.0.0" "@metamask/polling-controller": "npm:^9.0.0" "@metamask/rpc-errors": "npm:^6.3.1"