MetaMask / metamask-extension

:globe_with_meridians: :electric_plug: The MetaMask browser extension enables browsing Ethereum blockchain enabled websites
https://metamask.io
Other
11.87k stars 4.85k forks source link

Inpage injection fails in Firefox under some CSP settings #3133

Open marcusmolchany opened 6 years ago

marcusmolchany commented 6 years ago

Hey, as far as I can tell, my content security policy is preventing MetaMask from injecting its scripts. This is only happening in Firefox. It works correctly in Chrome, Safari, Opera, and Brave. My script-src directive looks like this:

script-src 'self';

and I'm seeing this csp violation in the js console:

Content Security Policy: The page’s settings blocked the loading of a resource at self. Source: (function e(t,n,r){function s(o,u){if(!n ....

Unfortunately Firefox only shows a preview of the blocked script. I've tried sha256 hashing each of the scripts in the latest Metamask release and adding them to the CSP, but that did not work. If you have any ideas that would be great!

Browser: Firefox 58.0.1 Operating System: Mac OSX 10.13.2

gitcoinbot commented 6 years ago

This issue now has a funding of 0.105 ETH (96.44 USD) attached to it.

marcusmolchany commented 6 years ago

I've added funding to this issue. I'd love for my site to work on FireFox with MetaMask without having to change my CSP. Thanks!

evgeniuz commented 6 years ago

I think I can fix this issue, but want to verify that my approach would be correct one.

So the problem is that CSP won't allow inline script that is used here https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/contentscript.js#L30. There's manifest.json property web_accessible_resources that seems to allow injecting scripts that are specified there, but it's only applied if script is loaded with src, not with textContent.

I've tried quickly and replacing scriptTag.textContent = [script_content] with scriptTag.src = [script_url] seems to resolve issue with CSP in Firefox. If this approach is ok, I'll proceed with implementation.

evgeniuz commented 6 years ago

Ok, this might be trickier than I expected. I see that loading with src caused race condition in the past, going to look more closely into this.

marcusmolchany commented 6 years ago

thank you for starting @evgeniuz! I can set up an example application that uses my current CSP if that would help.

evgeniuz commented 6 years ago

Sorry, but it seems that those two issues are inherently conflicting: when using scriptTag.src there seems to be no way to ensure that script will load before all other scripts and using scriptTag.textContent is disallowed by CSP.

I've noticed that you've tried to use hash to whitelist the script for inline, the file to hash is scripts/inpage.js, but the problem is that sourceURL comment is added here: https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/contentscript.js#L12. Since this sourceURL depends on extension ID, it may be different for each installation, so you cannot whitelist it reliably.

Removing sourceURL from final build will allow to generate hash reliably, so you can whitelist it in CSP. And it can still be added in development build for easier debugging. But I cannot make this decision myself, need some input from Metamask team: is it ok to remove sourceURL suffix in that line so that inline script is same on every installation and can be hashed and whitelisted in CSP?

Going to unassign myself from bounty, as I cannot find another solution at the moment.

marcusmolchany commented 6 years ago

@evgeniuz thank you very much for all the help here.

dmihal commented 6 years ago

@marcusmolchany would you mind setting up a sample application? I'd like to play with setting up a workaround...

marcusmolchany commented 6 years ago

Hey @dmihal here is a sample application. If you open it in chrome, you should see that web3 is injected into window.web3. If you open it using firefox, you should see that the CSP blocks web3 from being injected.

Here is the repo for the sample application. And this specifically is the CSP.

vs77bb commented 6 years ago

@dmihal Would you like to give this one a go? Feel free to claim it on Gitcoin by clicking 'Start Work', if so!

KennethAshley commented 6 years ago

Is this still an issue? MetaMask seems to be working fine on FF for me.

marcusmolchany commented 6 years ago

Hey @KennethAshley, this is only an issue if you have a Content Security Policy. Does MetaMask successfully inject web3 on my sample application when you use Firefox?

skkiran-pro commented 6 years ago

This is a known bug in firefox. https://bugzilla.mozilla.org/show_bug.cgi?id=1267027. Comment on that issue to get it fixed soon. :)

gitcoinbot commented 6 years ago

@dmxsf1 are you still working on this issue?

gitcoinbot commented 6 years ago

@dmxsf1 are you still working on this issue?

gitcoinbot commented 6 years ago

@dmxsf1 are you still working on this issue?

gitcoinbot commented 6 years ago

@amitkumar991 Hello from Gitcoin Core - are you still working on this issue? Please submit a WIP PR or comment back within the next 3 days or you will be removed from this ticket and it will be returned to an ‘Open’ status. Please let us know if you have questions!

Funders only: Snooze warnings for 1 day | 3 days | 5 days | 10 days | 100 days

filips123 commented 5 years ago

Any update? Is this fixed in newer versions of Firefox or Metamask?

filips123 commented 5 years ago

Could this be fixed by Metamask or it should be reported to Firefox bugzilla? This should be fixed because many websites don't work because of that.

MicahZoltu commented 4 years ago

I believe the "correct" long term solution here is for dapps to change the way they interact with browser signers to better align with the official browser recommendation for pages and extensions communicating, which is via postMessage. I'm currently working on a solution to this, but it will be a big change that will require both dapp and extension buy-in and likely will be very slow to gain adoption.

I wanted to drop a comment here just to make people aware of the potential futures, but at the moment I don't believe there is a great solution. If I am successful, my hope is to get this new mechanism implemented in MetaMask eventually (likely side-by-side with the existing injection technique) and then from there we will try to get dapps to adopt this technique over time.

tuxayo commented 4 years ago

try to get dapps to adopt this technique over time.

Does it mean extending the Web3 protocol? (if that's correct to call that a protocol)

MicahZoltu commented 4 years ago

It means changing the way dapps communicate with "web3 enabled browsers". Rather than calling functions that were attached to window.ethereum, instead fire and listen for events.

jurosh commented 4 years ago

Hey guys, seems this issue is very old problem, but probably there doesn't exist even hotfix for this issue yet ?

So there is currently no way to use Metamask in Firefox ? (edit: with CSP enabled)

Going to follow also this thread https://bugzilla.mozilla.org/show_bug.cgi?id=1267027 but it also seems to be there for 4 years, so we should probably not expect this to be fixed anytime soon.

As I understand, to fix Metamask in FF, there would be need to change it's communication protocol to postMessages? Is that something what is planned?

MicahZoltu commented 4 years ago

I use MetaMask on Firefox and it seems to work, though I think I'm on a pretty old version at the moment. What page are you unable to use? Is it served via HTTP or HTTPS?

jurosh commented 4 years ago

I use MetaMask on Firefox and it seems to work, though I think I'm on a pretty old version at the moment. What page are you unable to use? Is it served via HTTP or HTTPS?

Both, I was testing on localhost:3000 (http) and also live site with https. Tested with nightly FF (v77) and stable v75.

There is error regarding blocked contentscript.js: Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”).

And there is no window.web3 neither ethereum included.

Edit: Without enabled CSP it works. But we cannot turn that off and reduce web security.

skkiran-pro commented 4 years ago

It was failing only if there is a CSP specified on the page (via meta tag or response header). I have not tested it recently.

MicahZoltu commented 4 years ago

https://dai-hrd.keydonix.com works for me in Firefox 75.0 with MetaMask 7.7.8. I am able to click the connect button in the middle of the page, which pops up a MM prompt, and upon accepting I can interact with the UI. I get one error in my console which is from the Uniswap iframe in the page, not from MetaMask.

jurosh commented 4 years ago

https://dai-hrd.keydonix.com works for me in Firefox 75.0 with MetaMask 7.7.8. I am able to click the connect button in the middle of the page, which pops up a MM prompt, and upon accepting I can interact with the UI. I get one error in my console which is from the Uniswap iframe in the page, not from MetaMask.

Yep, works for me too, but that site seems not to use CSP feature.

kumavis commented 4 years ago

There is a possible workaround for pages with a CSP that disallows metamask injection. If the dapp includes the equivalent of our inpage.js bundle, the dapp should be able to talk to metamask. The inpage.js bundle is rarely updated. Until this usecase/limitation is identified to be more common, we won't officially support this but it will work. You can use the inpage.js bundle as is or you can build your own with these parts: https://github.com/MetaMask/metamask-extension/blob/d908102636b6e55b6cbcf592d2d99e727b1b278f/app/scripts/inpage.js#L36-L70

jurosh commented 4 years ago

There is a possible workaround for pages with a CSP that disallows metamask injection. If the dapp includes the equivalent of our inpage.js bundle, the dapp should be able to talk to metamask. The inpage.js bundle is rarely updated. Until this usecase/limitation is identified to be more common, we won't officially support this but it will work. You can use the inpage.js bundle as is or you can build your own with these parts:

Thanks @kumavis , seems this code HOT-fixed MM issue.

(function() {
  if (
     !window.ethereum &&
     !window.web3 &&
     navigator.userAgent.includes('Firefox')
   ) {
     const script = document.createElement('script');
     script.src = '/inpage-metamask.js';
     script.type = 'text/javascript';
     document.getElementsByTagName('head')[0].appendChild(script);
   }
})();

I took inpage.js directly from archive of latest extension from Firefox store.

Also I will keep eye on this topic, so in case there is proper solution I can get rid of this temporary hot-fix.

danfinlay commented 4 years ago

I'll just highlight again what @kumavis said above: You don't need the whole inpage.js bundle, you should be able to simply include these lines to construct your own ethereum provider:

import LocalMessageDuplexStream from 'post-message-stream'
import MetamaskInpageProvider from 'metamask-inpage-provider'

// setup background connection
const metamaskStream = new LocalMessageDuplexStream({
  name: 'inpage',
  target: 'contentscript',
})

// compose the inpage provider
const inpageProvider = new MetamaskInpageProvider(metamaskStream)

// set a high max listener count to avoid unnecesary warnings
inpageProvider.setMaxListeners(100)

// Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound
// `sendAsync` method on the prototype, causing `this` reference issues
const ethereum = new Proxy(inpageProvider, {
  // straight up lie that we deleted the property so that it doesnt
  // throw an error in strict mode
  deleteProperty: () => true,
})
ntninja commented 3 years ago

I first wanted to suggest browser.contentScripts for Firefox here, but the issue here is that the injected script must run as page code rather then extension code, so that wouldn't help.

Additionally, web_accessible_resources has also been rejected at this point. (Maybe somebody can explain why having web pages wait for DOMContentLoaded before accessing metamask is a problem though?)

So it appears to me that the solution we've all been waiting for has already been summarized as part of a content script comment here:

// Eventually this streaming injection could be replaced with: // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction // // But for now that is only Firefox // If we create a FireFox-only code path using that API, // MetaMask will be much faster loading and performant on Firefox.

So do the following steps on Firefox:

I suppose the bounty on this has expired though? 😬

Gudahtt commented 3 years ago

I'm labelling this as "blocked" because the upstream issue has not been addressed yet: https://bugzilla.mozilla.org/show_bug.cgi?id=1446231

Gudahtt commented 3 years ago

@ntninja I think that API was since deprecated? :thinking: So that might not be a viable option anymore. Not 100% sure though.

ntninja commented 3 years ago

@Gudahtt: Which one? Where did you see that? I have extensions depending on that content-script API.

Gudahtt commented 3 years ago

@ntninja See the warning at the beginning of this page: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM

I thought they had decided to pursue the cross-browser Web Extensions API, rather than pursue this or any other Firefox-specific API

ntninja commented 3 years ago

To my knowledge that applies to writing extension stuff using XPCOM APIs, note here that while the documentation of exportFunction is on the XPCOM part of the wiki, it is not exposed as an XPCOM API (at names like Components.utils.exportFunction) but specifically as part of the content script global scope. While you can never be sure with Mozilla, I don't believe that they will stop exposing these 3 functions given their usefulness and given that they need a comparable mechanism for their own stuff in-browser anyways. There also is simply no alternative to this API to solving this anyways, so we might as well use it and make sure that they know about this and its important to this extension so that they at least don't remove without prior notice.

Gudahtt commented 3 years ago

My read of this is that it's still deprecated, since it's part of the XPCOM API, despite it being exposed directly on the window. But I don't see any removal notice, so I don't see any reason not to use it at least as an interim workaround in the meantime. I'll remove the blocked label then.

ntninja commented 3 years ago

Anybody interested in sponsoring me on this one?

Gudahtt commented 3 years ago

I was debugging something related to this recently and noticed that the underlying Firefox bug only seems to affect CSPs set via header, not those set in a <meta> tag. If anyone wants to try and repro this, keep that in mind! I used Heroku to add the CSP as a header, following the example linked in an earlier comment, and that worked.

This means that the problem has a fairly simple workaround; set your CSP via a <meta> tag instead of using a header, and this bug shouldn't affect you! A CSP set via a <meta> tag should still accomplish the same thing as one sent via header (as far as I know), but it bypasses this bug.

michelbio commented 3 years ago

@Gudahtt Currently facing the same issue (FF 87 and white screen on MetaMask). Was trying to extract the vault, but keep on getting this error:

Setting up Sentry Remote Error Reporting for 'production': SENTRY_DSN initSentry.js:1:1426
Removing intrinsics.Promise.any initSentry.js:8:36495
Removing intrinsics.Reflect.@@toStringTag initSentry.js:8:36495
Removing intrinsics.%StringPrototype%.replaceAll initSentry.js:8:36495
Removing intrinsics.%ErrorPrototype%.stack initSentry.js:8:36495
unreachable code after return statement
ui-libs.js:1:439240
unreachable code after return statement
ui-libs.js:1:443126
unreachable code after return statement
ui-libs.js:1:444747
unreachable code after return statement
ui-libs.js:13:10549
unreachable code after return statement
ui-libs.js:13:10864
unreachable code after return statement
ui-libs.js:13:13651
Content Security Policy: The page’s settings blocked the loading of a resource at eval (“script-src”).

When visiting the home.html page from the extension. What's a practical way to make it work? I tried temporarily disabling any CSP policies (using an extension and in the Firefox config) but that didn't work.

icole commented 3 years ago

I am encountering this as well. Updating the CSP to a <meta> tag does not seem to work. Here is my CSP for reference

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; font-src 'self' https://www.slant.co data: https://fonts.gstatic.com; connect-src 'self'; img-src 'self'; style-src 'self' https://fonts.googleapis.com; media-src 'self'; ">
TimDaub commented 3 years ago

Seems this issue is connected to users getting blank screens on FF: https://github.com/MetaMask/metamask-extension/issues/9196#issuecomment-867661016

I still wasn't able to resolve my problem.

jmrossy commented 2 years ago

For anyone working around this issue, I was able to get Firefox working by copying MyCrypto's workaround here: https://github.com/MyCryptoHQ/MyCrypto/pull/3292/files#diff-18092debc1d7fd5a37fa36476baa06bb492fe33df7dc8dde4576abfffaf73c88

Note, I had to use the exact versions of post-message-stream (3.0.0) and @metamask/inpage-provider (6.0.1) to get it to work, which is unfortunate because those packages are deprecated. I tested with the latest releases of those packages and also their replacements (@metamask/post-message-stream and @metamask/providers) but neither worked. No errors printed to console but Metamask never reacted to the connection request. @kumavis @rekmarks Any theories why that may be? Or other combinations I should try? Would love to move off the deprecated versions.

Finally, a few folks here have mentioned moving the CSP to a meta tag. That may work but it's not quite as secure as the server declaring the CSP before the browser receives any content. Better than nothing though.

dmlayton commented 2 years ago

Further to other comments, the MyCryptoHQ workaround doesn't seem to work and depends on libraries that have been abandoned or renamed and changed the relevant API's.

I couldn't find any updated solution, but after some time got this working:

const {WindowPostMessageStream} = require('@metamask/post-message-stream');
const { initializeProvider } = require('@metamask/providers');

// Firefox Metamask Hack
// Due to https://github.com/MetaMask/metamask-extension/issues/3133

(() => {
  if (window.ethereum || window.web3) {
    return;
  }
  if (navigator.userAgent.includes('Firefox')) {
    // setup background connection
    const metamaskStream = new WindowPostMessageStream({
      name: 'metamask-inpage',
      target: 'metamask-contentscript'
    });

    // this will initialize the provider and set it as window.ethereum
    initializeProvider({
      connectionStream: metamaskStream,
      shouldShimWeb3: true
    });
  } else if (navigator.userAgent.includes('iPhone')) {
    // injectMobile();
  }
})();

I had to switch to browserify since this requires several node libraries, and I need client-side javascript obviously. It seems that's the case for most of the metamask helper modules

amilich commented 1 year ago

Would love to see this fixed!

jefflau commented 1 year ago

+1 We'd love to see this fixed at ENS

f5scott commented 1 year ago

This cant be for real ..... 0_o

fatchan commented 1 year ago

How much does Google pay MetaMask to keep them from fixing issues that prevent it working in competing browsers?

github-actions[bot] commented 11 months ago

This issue has been automatically marked as stale because it has not had recent activity in the last 90 days. It will be closed in 45 days if there is no further activity. The MetaMask team intends on reviewing this issue before close, and removing the stale label if it is still a bug. We welcome new comments on this issue. We do not intend on closing issues if they report bugs that are still reproducible. Thank you for your contributions.