From 3b72d54002d9f09a977ea481a5b7b0e0bd0c3de4 Mon Sep 17 00:00:00 2001 From: Reid Rankin Date: Thu, 18 Nov 2021 12:40:14 -0500 Subject: [PATCH 1/2] fix: ship our own metamask stub this prevents the need for 'unsafe-inline' in the CSP to talk to MM on Firefox --- headers.js | 1 - package.json | 3 + src/lib/metamaskInpageProvider.d.ts | 17 +++++ src/lib/metamaskInpageProvider.ts | 103 ++++++++++++++++++++++++++++ src/lib/polyfills.ts | 1 + yarn.lock | 97 +++++++++++++++++++++++--- 6 files changed, 213 insertions(+), 9 deletions(-) create mode 100644 src/lib/metamaskInpageProvider.d.ts create mode 100644 src/lib/metamaskInpageProvider.ts diff --git a/headers.js b/headers.js index 7b36a4803f0..3a0f6ee0087 100644 --- a/headers.js +++ b/headers.js @@ -31,7 +31,6 @@ const csp = Object.entries({ 'script-src': [ "'self'", "'unsafe-eval'", //TODO: There are still a couple of libraries we depend on that use eval; notably amqp-ts and google-protobuf. - "'unsafe-inline'", //TODO: The only inline code we need is the stub injected by Metamask. We can fix this by including the stub in our own bundle. "'report-sample'" ], 'style-src': ["'self'", "'unsafe-inline'", "'report-sample'"], diff --git a/package.json b/package.json index 87121183141..fa0765059ba 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,10 @@ "localforage": "^1.10.0", "lodash": "^4.17.21", "match-sorter": "^6.3.0", + "metamask-inpage-provider": "^5.0.0", "node-polyglot": "^2.4.0", "numeral": "^2.0.6", + "post-message-stream": "^3.0.0", "qr-image": "^3.2.0", "qs": "^6.10.1", "react": "^17.0.2", @@ -138,6 +140,7 @@ "@types/node": "^16.4.10", "@types/node-polyglot": "^2.4.2", "@types/numeral": "^2.0.1", + "@types/post-message-stream": "^3.0.1", "@types/qr-image": "^3.2.4", "@types/react": "^17.0.15", "@types/react-dom": "^17.0.9", diff --git a/src/lib/metamaskInpageProvider.d.ts b/src/lib/metamaskInpageProvider.d.ts new file mode 100644 index 00000000000..9c7703e3f71 --- /dev/null +++ b/src/lib/metamaskInpageProvider.d.ts @@ -0,0 +1,17 @@ +declare module 'metamask-inpage-provider' { + import type PostMessageStream from 'post-message-stream' + + export function initProvider({ + connectionStream, + maxEventListeners = 100, + preventPropertyDeletion = true, + shouldSendMetadata = true, + shouldSetOnWindow = true + }: { + connectionStream: PostMessageStream + maxEventListeners?: number + preventPropertyDeletion?: boolean + shouldSendMetadata?: boolean + shouldSetOnWindow?: boolean + }): unknown +} diff --git a/src/lib/metamaskInpageProvider.ts b/src/lib/metamaskInpageProvider.ts new file mode 100644 index 00000000000..96bee835bce --- /dev/null +++ b/src/lib/metamaskInpageProvider.ts @@ -0,0 +1,103 @@ +/// + +import { initProvider } from 'metamask-inpage-provider' +import PostMessageStream from 'post-message-stream' + +function universalProxy(pseudoTarget: object) { + return { + proxy: new Proxy( + {}, + new Proxy( + {}, + { + get(_, p) { + return (_t: any, p2: any, r: any) => { + switch (p) { + case 'get': { + const out = Reflect.get(pseudoTarget, p2, r) + if (typeof out === 'function') return out.bind(pseudoTarget) + return out + } + case 'getOwnPropertyDescriptor': { + const out = Reflect.getOwnPropertyDescriptor(pseudoTarget, p2) + if (out) out.configurable = true + return out + } + case 'isExtensible': + return true + case 'preventExtensions': + return false + default: + return (Reflect as any)[p](pseudoTarget, p2, r) + } + } + } + } + ) + ), + getPseudoTarget() { + return pseudoTarget + }, + setPseudoTarget(value: object) { + pseudoTarget = value + } + } +} + +if (typeof window !== 'undefined') { + const initialPseudoTarget = Object.freeze({}) + const { proxy, getPseudoTarget, setPseudoTarget } = universalProxy( + window.ethereum ?? initialPseudoTarget + ) + try { + Object.defineProperty(window, 'ethereum', { + configurable: true, + enumerable: true, + get() { + return proxy + }, + set(value: unknown) { + if (!(value && ['object', 'function'].includes(typeof value))) throw new TypeError() + setPseudoTarget(value as object) + } + }) + + // Allow MM time to try to sucessfully execute its own injected stub + setTimeout(() => { + try { + // Don't clobber an existing MM stub + if (getPseudoTarget() !== initialPseudoTarget) { + console.info('metamaskInpageProvider: MM stub was already injected') + return + } + initProvider({ + connectionStream: new PostMessageStream({ + name: 'inpage', + target: 'contentscript' + }) + }) + if (window.ethereum.isMetaMask) { + console.info('metamaskInpageProvider: injected MM stub') + } else { + console.info( + 'metamaskInpageProvider: MM stub injection failed:', + window.ethereum, + getPseudoTarget() + ) + } + } catch (e) { + console.error('metamaskInpageProvider callback:', e) + } finally { + Object.defineProperty(window, 'ethereum', { + configurable: true, + enumerable: true, + writable: true, + value: getPseudoTarget() + }) + console.info('metamaskInpageProvider: window.ethereum proxy reset') + } + }, 0) + } catch (e) { + console.error('metamaskInpageProvider:', e) + } +} diff --git a/src/lib/polyfills.ts b/src/lib/polyfills.ts index 4c68e71ec05..5825cd76c13 100644 --- a/src/lib/polyfills.ts +++ b/src/lib/polyfills.ts @@ -53,3 +53,4 @@ import '@formatjs/intl-pluralrules/locale-data/tr' import '@formatjs/intl-pluralrules/locale-data/uk' import '@formatjs/intl-pluralrules/locale-data/vi' import '@formatjs/intl-pluralrules/locale-data/zh' +import './metamaskInpageProvider' diff --git a/yarn.lock b/yarn.lock index bd60d1ad60a..984a3e17a1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5293,6 +5293,13 @@ dependencies: "@types/node" "*" +"@types/post-message-stream@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/post-message-stream/-/post-message-stream-3.0.1.tgz#919aad3921812647fe7b0a27637d47445f887c3f" + integrity sha512-j9X5t0bpDezd/pCLCddMUIWv4m3gdqrK95c3Utdw6zylv7caoyWskcJ1YjhtftAk2A70f+WtLqeedTr0lqQxzQ== + dependencies: + "@types/readable-stream" "*" + "@types/prettier@^2.0.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.1.tgz#e1303048d5389563e130f5bdd89d37a99acb75eb" @@ -5410,6 +5417,14 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/readable-stream@*": + version "2.3.11" + resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.11.tgz#942bc4574a1d7ca4368cb9cb4352e3d2b4b51dea" + integrity sha512-0z+/apYJwKFz/RHp6mOMxz/y7xOvWPYPevuCEyAY3gXsjtaac02E26RvxA+I96rfvmVH/dEMGXNvyJfViR1FSQ== + dependencies: + "@types/node" "*" + safe-buffer "*" + "@types/resolve@0.0.8": version "0.0.8" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" @@ -9575,7 +9590,7 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -10105,6 +10120,13 @@ eth-ens-namehash@2.0.8: idna-uts46-hx "^2.3.1" js-sha3 "^0.5.7" +eth-json-rpc-errors@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/eth-json-rpc-errors/-/eth-json-rpc-errors-2.0.2.tgz#c1965de0301fe941c058e928bebaba2e1285e3c4" + integrity sha512-uBCRM2w2ewusRHGxN8JhcuOb2RN3ueAOYH/0BhqdFmQkZx5lj5+fLKTz0mIVOzd4FG5/kUksCzCD7eTEim6gaA== + dependencies: + fast-safe-stringify "^2.0.6" + eth-json-rpc-filters@^4.2.1: version "4.2.2" resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-4.2.2.tgz#eb35e1dfe9357ace8a8908e7daee80b2cd60a10d" @@ -10700,6 +10722,11 @@ fake-merkle-patricia-tree@^1.0.1: dependencies: checkpoint-store "^1.1.0" +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= + fast-deep-equal@^3.0.0, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -13561,7 +13588,7 @@ json-parse-even-better-errors@^2.3.0: resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== -json-rpc-engine@^5.3.0: +json-rpc-engine@^5.1.5, json-rpc-engine@^5.3.0: version "5.4.0" resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-5.4.0.tgz#75758609d849e1dba1e09021ae473f3ab63161e5" integrity sha512-rAffKbPoNDjuRnXkecTjnsE3xLLrb00rEkdgalINhaYVYIxDwWtvYBr9UFbhTvPB1B2qUOLoFd/cV6f4Q7mh7g== @@ -13577,6 +13604,14 @@ json-rpc-engine@^6.1.0: "@metamask/safe-event-emitter" "^2.0.0" eth-rpc-errors "^4.0.2" +json-rpc-middleware-stream@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/json-rpc-middleware-stream/-/json-rpc-middleware-stream-2.1.1.tgz#06e5409e201e7ddeae47bef29f7059eafd4d5325" + integrity sha512-WZheufPN+/RKkjXQP3lK5tFYblqG0n+oYv5qpammwwY2vsJRB7mM4Txhr4ajzvYEZi1UkENnplrmaYiqaqafaA== + dependencies: + readable-stream "^2.3.3" + safe-event-emitter "^1.0.1" + json-rpc-random-id@^1.0.0, json-rpc-random-id@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz#ba49d96aded1444dbb8da3d203748acbbcdec8c8" @@ -14082,6 +14117,11 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" +loglevel@^1.6.1: + version "1.8.0" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" + integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== + loglevel@^1.6.8: version "1.7.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197" @@ -14436,6 +14476,21 @@ merkle-patricia-tree@^2.1.2, merkle-patricia-tree@^2.3.2: rlp "^2.0.0" semaphore ">=1.0.1" +metamask-inpage-provider@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/metamask-inpage-provider/-/metamask-inpage-provider-5.0.0.tgz#8655a2cacd5139573b885de8bd1d41f32dad8c82" + integrity sha512-nJpevOENFJ5Ah5WJNlFj7TsFibuCI6F1UVraD+HtD2qTtee5NrPFsrP8vCjPM+XTXcHw9Vp2vyqgi7LhmRVCOg== + dependencies: + eth-json-rpc-errors "^2.0.2" + fast-deep-equal "^2.0.1" + json-rpc-engine "^5.1.5" + json-rpc-middleware-stream "^2.1.1" + loglevel "^1.6.1" + obj-multiplex "^1.0.0" + obs-store "^4.0.3" + pump "^3.0.0" + safe-event-emitter "^1.0.1" + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -15134,6 +15189,15 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +obj-multiplex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/obj-multiplex/-/obj-multiplex-1.0.0.tgz#2f2ae6bfd4ae11befe742ea9ea5b36636eabffc1" + integrity sha1-Lyrmv9SuEb7+dC6p6ls2Y26r/8E= + dependencies: + end-of-stream "^1.4.0" + once "^1.4.0" + readable-stream "^2.3.3" + object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -15251,6 +15315,16 @@ oboe@2.1.5: dependencies: http-https "^1.0.0" +obs-store@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/obs-store/-/obs-store-4.0.3.tgz#b632ec7814baa604fae084a4c97e87c0b7a6d14c" + integrity sha512-+mm13kCRDv6IcvUDKTw0LIy5+dQhIktYaR/RwwZUFzOTi/fjMaNBnk42Adb94qZqJ00qWkjhQSZH7MXlKnTi8A== + dependencies: + readable-stream "^2.2.2" + safe-event-emitter "^1.0.1" + through2 "^2.0.3" + xtend "^4.0.1" + obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" @@ -15870,6 +15944,13 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +post-message-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/post-message-stream/-/post-message-stream-3.0.0.tgz#90d9f54bd209e6b6f5d74795b87588205b547048" + integrity sha1-kNn1S9IJ5rb110eVuHWIIFtUcEg= + dependencies: + readable-stream "^2.1.4" + postcss-attribute-case-insensitive@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" @@ -17468,7 +17549,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -18107,6 +18188,11 @@ safari-14-idb-fix@^3.0.0: resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz#450fc049b996ec7f3fd9ca2f89d32e0761583440" integrity sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog== +safe-buffer@*, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -18117,11 +18203,6 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - safe-event-emitter@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/safe-event-emitter/-/safe-event-emitter-1.0.1.tgz#5b692ef22329ed8f69fdce607e50ca734f6f20af" From f7b3ff6bdc9bb95040fa2d8fd4c1f363abb7d9da Mon Sep 17 00:00:00 2001 From: Reid Rankin Date: Thu, 18 Nov 2021 18:37:17 -0500 Subject: [PATCH 2/2] Update src/lib/metamaskInpageProvider.ts --- src/lib/metamaskInpageProvider.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/metamaskInpageProvider.ts b/src/lib/metamaskInpageProvider.ts index 96bee835bce..036b3110cd5 100644 --- a/src/lib/metamaskInpageProvider.ts +++ b/src/lib/metamaskInpageProvider.ts @@ -70,6 +70,7 @@ if (typeof window !== 'undefined') { console.info('metamaskInpageProvider: MM stub was already injected') return } + // This sets window.ethereum initProvider({ connectionStream: new PostMessageStream({ name: 'inpage',