mermaid-js / mermaid

Generation of diagrams like flowcharts or sequence diagrams from text in a similar manner as markdown
https://mermaid.js.org
MIT License
71.18k stars 6.41k forks source link

Mermaid versions after v9.2.2 cannot be injected into Firefox addons #5378

Open simov opened 6 months ago

simov commented 6 months ago

Description

I have a browser extension that injects mermaid.min.js as a content script:

chrome.scripting.executeScript({
  target: {tabId: id},
  files: [
    'mermaid.min.js'
  ],
  injectImmediately: true
})

In Chromium based browsers the above code injects the latest mermaid.min.js build without any issues. However, in Firefox after v9.2.2 it is no longer possible to inject the mermaid.min.js build using the exact same code.

Initially I was thinking that it is a temporary issue, so that's why I did not report it immediately, but at this point I have users asking me why am I not updating the Mermaid version in the Firefox version of the extension (same code base as the Chromium one), and I cannot figure out what changed after v9.2.2 that might be causing this.

According to the release docs for v9.3.0 https://github.com/mermaid-js/mermaid/releases/tag/v9.3.0 the build become:

25% Smaller

So I'm wondering if it is something related to that.

Note that this is happening ever since Dec 15th 2022 when v9.3.0 got released, so through out all this time all versions of Firefox consistently fail to inject the library after v9.3.0, but they can inject v9.2.2 just fine. I have tested every Mermaid build ever since.

Steps to reproduce

A simple Firefox addon can be created to reproduce this issue:

manifest.json

{
  "manifest_version": 3,
  "name"            : "Inject Mermaid > v9.2.2 Bug",
  "version"         : "1",
  "description"     : "All Mermaid versions from v9.3.0 onwards cannot be injected",

  "background" : {
    "scripts": [
      "background.js"
    ]
  },

  "permissions": [
    "scripting"
  ],

  "host_permissions": [
    "file:///*",
    "*://*/"
  ],

  "browser_specific_settings": {
    "gecko": {
      "id": "ff-mermaid-inject@foo.bar",
      "strict_min_version": "110.0"
    }
  }
}

background.js

console.log('background')

chrome.tabs.onUpdated.addListener((id, info, tab) => {

  if (info.status === 'loading' && info.url) {

    console.log('inject')

    chrome.scripting.executeScript({
      target: {tabId: id},
      files: [
        'mermaid.min.js',
        'content.js'
      ],
      injectImmediately: true
    }, (err) => {
      if (err) {
        console.log(err)
      }
    })
  }
})

content.js

console.log('content')
console.log(mermaid)

mermaid.min.js

See below

  1. Navigate to about:debugging#/runtime/this-firefox Screenshot from 2024-03-12 11-35-01

  2. Click on the Load Temporary Add-on... button

  3. Find the folder containing the 3 files from above and double click on the manifest.json file

  4. Make sure the Access the data for all websites switch is turned on for your temporary extension in about:addons Screenshot from 2024-03-12 11-54-41

  5. Navigate to any webpage

When using Mermaid v9.2.2 https://cdnjs.cloudflare.com/ajax/libs/mermaid/9.2.2/mermaid.min.js as the source of mearmaid.min.js the injecting works and therefore you will see the following output in the console log of the page you are looking at: Screenshot from 2024-03-12 11-34-13

Meaning both the mermaid.min.js and the content.js got injected and you can see the mermaid object being printed out in the console.

Now,

  1. Replace the contents of mermaid.min.js with the contents of https://cdnjs.cloudflare.com/ajax/libs/mermaid/9.3.0/mermaid.min.js
  2. Navigate back to about:debugging#/runtime/this-firefox and click on the Reload button for your temporary addon
  3. Click on the Inspect button to view the console log for the background page
  4. Switch back to the site that you were looking at before and refresh the page

Now in the console log for the background page of the extension you can see the following: Screenshot from 2024-03-12 11-32-41

So apparently on line 23 in mermaid.min.js now a call to Function() is being blocked by the CSP and therefore the inject fails as a whole. The inject is failing from v9.3.0 onward because of a Content Security Policy set for browser addons in Firefox. Note that the whole purpose of injecting mermaid.min.js instead of adding it as a script tag into the page is to circumvent another set of CSP issues, but in the content page context afterwards, so there is really no other way for me to use Mermaid other than injecting it in the background page.

I have examined the offending line 23 in both v9.2.2 and v9.3.0, but I cannot spot any significant difference between the two.

Screenshots

No response

Code Sample

No response

Setup

Suggested Solutions

No response

Additional Context

No response

simov commented 6 months ago

As it turns out the problem can be resolved by replacing all occurrences of Function("return this") with (() => globalThis). Not sure how is it working in v9.2.2 if that version contains Function("return this") too, but it does not trigger the CSP error when injecting the library in Firefox.

sidharthv96 commented 6 months ago

Can you check if https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.0.0-alpha.6/mermaid.min.js works? We changed the bundler to esbuild in v11. Although that also contains Function("return this")

simov commented 6 months ago

No it doesn't work, still the same issue in 11.0.0-alpha.6

simov commented 6 months ago

As far as I understand the Function constructor is considered unsafe and for that reason it is blocked by the CSP (Content Security Policy), even though they say that using the Function constructor with a fixed string is generally safe as it cannot offer remote code execution, and of course Function("return this") is completely harmless too, but the rule is to not allow the Function constructor in general.

One guy showed me a WebPack plugin that he built to do just that - replace Function("return this") with (() => globalThis) so apparently other people are having issues with their builds too.

I don't know how and if changing this is going to affect backward compatibility for example.

sidharthv96 commented 6 months ago

Rollup was updated to v3 in 9.3.0, we also switched to using an esm version of lodash as well. Can you try using webpack with that plugin to bundle mermaid and generate your content.js file, instead of injecting mermaid separately?

sidharthv96 commented 6 months ago

@remcohaszing @aloisklink @nirname @Yokozuna59 @Yash-Singh1 does anyone of you folks have any insights?

simov commented 6 months ago

Rollup was updated to v3 in 9.3.0, we also switched to using an esm version of lodash as well. Can you try using webpack with that plugin to bundle mermaid and generate your content.js file, instead of injecting mermaid separately?

No, in fact I'm not building Mermaid myself, but following what this guy did I am simply going to replace all occurrences of Function("return this") with (() => globalThis) in the official mermaid.min.js build provided by you, which is part of my packaging script.

That works, however I am going to have some issues with the review process of my extension afterwards because I am modifying the official build, and that is being compared by the reviewer. So hopefully that's going to pass the validation, but of course it would be best if we have that fixed in the official build if there is no other downside related to it.

Yash-Singh1 commented 6 months ago

@remcohaszing @aloisklink @nirname @Yokozuna59 @Yash-Singh1 does anyone of you folks have any insights?

This seems to be coming from the lodash-es _root.js: https://unpkg.com/browse/lodash-es@4.17.21/_root.js

https://github.com/lodash/lodash/blob/51ed7e7707047aae23433bebaf461064787b006a/_root.js#L7

I think it would make sense to patch lodash-es to use one of the polyfills here:

https://mathiasbynens.be/notes/globalthis#robust-polyfill

...or we could just replace it with window because Mermaid.js runs only in the browser.

sidharthv96 commented 6 months ago

@simov does https://github.com/mermaid-js/mermaid/issues/5383 mean that this issue was solved?

simov commented 6 months ago

@sidharthv96, no these are two separate issues. Firefox cannot inject any version of Mermaid after v9.3.0 (this issue), any Chromium based browser (the rest of them) cannot inject Mermaid after v10.9.0 (#5383).

What I will do for the upcoming release of my extension is:

This will make it work in all browsers for the time being, but of course if the official build does not get fixed I will have to think about something else next time I have to do a release.