Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

High Severity Vulnerability in html-minifier #2589

Open
PavelBurya opened this issue Nov 30, 2022 · 50 comments · May be fixed by #2858
Open

High Severity Vulnerability in html-minifier #2589

PavelBurya opened this issue Nov 30, 2022 · 50 comments · May be fixed by #2858

Comments

@PavelBurya
Copy link

Hello, our security check has found a high severity vulnerability in html-minifier, which is a dependency of mjml.

Dependency hierarchy:

  • mjml-4.13.0.tgz (Root Library)
  • mjml-cli-4.13.0.tgz
  • ❌ html-minifier-4.0.0.tgz (Vulnerable Library)

Vulnerability description:
A Regular Expression Denial of Service (ReDoS) flaw was found in kangax html-minifier 4.0.0 via the candidate variable in htmlminifier.js.

Here is a link to a similar issue in html-minifier. It does not seem to be worked on.

kangax/html-minifier#1135

Can you update your repository to get rid of this vulnerability?

@iRyusa
Copy link
Member

iRyusa commented Nov 30, 2022

There's nothing we can do to mitigate this on our end. Just wait for them to patch it.

@PavelBurya
Copy link
Author

Unfortunately, this is a high severity vulnerability, so we can't wait years for them to fix it. Since html-minifier's last update was back on October 2021, I have concerns if that package is still supported and if anything will be fixed on their side.

And if neither your team nor theirs will work on this, we'll probably have to replace mjml with other libraries.

@iRyusa
Copy link
Member

iRyusa commented Nov 30, 2022

You can just not minify the code with the option we provide to be totally safe. This exploit is a concern if you're using MJML server-side.

It's FOSS at the end of the day, so you can also work on it. html-minifier was forked in html-minifier-terser and we're totally fine to replace it.

Edit: there's a huge drawback to use the terser version it's a way bigger dep that html-minifier tho.

@IsaaX
Copy link

IsaaX commented Dec 9, 2022

Hello, same concern here. I've been trying ways to address this vuln but seems I couldn't do a quick turn around. I'm also in the camp of having to remove mjml if we cannot use the alternative html-minifier-terser. I have PCI compliance that I have to abide by and have to address security issues. As of now I plan on disabling minification.

Just to add more context to this discussion

Comparing html-minifier vs html-minifier-terser

Unpacked size
96.8kb vs 4.55MB

Weekly Downloads
3.7M vs 12.2M

Last publish
4 years ago vs 18 days ago

There are pro and cons to each implementation.

@juansedo
Copy link

Any update about this?
Is there any mjml's fork that solve the issue?

@ArielPrevu3D
Copy link

ArielPrevu3D commented Jan 27, 2023

You guys need to replace html-minifier, the dependency is abandoned, vulnerable, slow and has popular alternatives which don't have these issues.

I propose html-minifier-terser or minify for a replacement

@iRyusa
Copy link
Member

iRyusa commented Feb 8, 2023

html-minifer-terser is just way too big for now to embed consider as a replacement for now.

@eddeee888
Copy link

eddeee888 commented Feb 13, 2023

Hello 👋 Since there are drawbacks in both options, would you consider letting the consumer pass in the minifier implementation?

For example, the mjml-core is currently importing html-minifier and calling it here. Could it take htmlMinify as an option? e.g.

// Remove import { minify as htmlMinify } from 'html-minifier' at the top
export default function mjml2html(mjml, options = {}) {
  // ... all the stuff

  content = options.htmlMinify(content, {
      collapseWhitespace: true,
      minifyCSS: false,
      caseSensitive: true,
      removeEmptyAttributes: true,
      ...minifyOptions,
    })
}

From what I can see, most options like collapseWhitespace, minifyCSS, etc. exist in html-minifier-terser too. So, in theory this could work?

@morphar
Copy link

morphar commented Feb 13, 2023

If html-minifier-terser is a fork of html-minifier, would it work to just override it in your own package.json?
Something like:

{
  ...
  "overrides": {
    "html-minifier": "npm:[email protected]"
  }
}

@iRyusa
Copy link
Member

iRyusa commented Feb 14, 2023

I was wondering if you could replace by another package on package json via overrides too but it seems you can only override the version.

@eddeee888
Copy link

I was wondering if you could replace by another package on package json via overrides too but it seems you can only override the version.

This feature seems to be available for PNPM. I cannot find anything that does this for NPM or Yarn 1.x. 🤔 (Could be wrong)

Summarising the thread so far, here are the mentioned solutions:

  • ❌ Replace html-minifier with html-minifier-terser: works as a drop-in replacement but is not great because html-minifier-terser is a lot bigger
  • ❌ Using overrides or solutions: works on some package managers but not others
  • ❓ Let the caller pass in the minifier implementation through options: This'll remove the dependency but changes the implementation, probably a major version bump. Any other drawbacks?

Happy to discuss more. I'm keen to help in any way possible :)

@iRyusa
Copy link
Member

iRyusa commented Feb 15, 2023

In a future release we want to remove both minifier/beautify by default and let the user install. Maybe we should just do that now and if html-minifier is installed the option still works in mjml-core but if not then it just skips it totally. So the dep become fully optionnal.

Most of MJML users use it locally on their machine so this vulnerability doesn't really impact them. It's more an issue for dynamic template generated server side with user input.

@khitrenovich
Copy link

This feature seems to be available for PNPM. I cannot find anything that does this for NPM or Yarn 1.x. 🤔 (Could be wrong)

You can do that with Yarn 1 and forced resolutions -

  "resolutions": {
    "html-minifier": "npm:[email protected]"
  },

Unfortunately, looks like the security check I'm evaluating right now does not know how to process that, but yours might.

@eddeee888
Copy link

eddeee888 commented Feb 20, 2023

Something to note is that html-minifier-terser returns a Promise whereas html-minifier returns a string.

So for the package switch to work, one would have to add await or .then() accordingly

@eddeee888
Copy link

In a future release we want to remove both minifier/beautify by default and let the user install. Maybe we should just do that now and if html-minifier is installed the option still works in mjml-core but if not then it just skips it totally. So the dep become fully optional.

Yea, this seems great as it solves this security issue and trims this library down further.
In your mind, would the packages be in peerDeps, or does it take a function?

@bridgetbarnes
Copy link

Hello!
We've tried to force the resolution in yarn to work around the Snyk issue, and unfortunately Snyk didn't understand this- it understood the version switch but not the package switch, so it didn't get rid of the warning in Snyk
Screen Shot 2023-02-21 at 11 56 42 am

@iRyusa
Copy link
Member

iRyusa commented Feb 21, 2023

Then we'll go to the "optional" way if require('html-minifier') is resolved then it will be available. Keep in mind if you're using MJML in production you have to minify the email.

I'll try to get this done for next month if the situation is better for me.

@khitrenovich
Copy link

We've tried to force the resolution in yarn to work around the Snyk issue, and unfortunately Snyk didn't understand this

@bridgetbarnes I got formal reply from Snyk - resolutions are not supported in Yarn v1.
https://docs.snyk.io/scan-application-code/snyk-open-source/snyk-open-source-supported-languages-and-package-managers/snyk-for-javascript/snyk-for-yarn#differences-due-to-yarn-versions

@eddeee888
Copy link

Then we'll go to the "optional" way if require('html-minifier') is resolved then it will be available. Keep in mind if you're using MJML in production you have to minify the email.
I'll try to get this done for next month if the situation is better for me.

Thanks for looking into it @iRyusa!! 🙏
To confirm my understanding, this would also move html-minifier to peerDependencies right?

Otherwise, our security tool may still recognise it as a dependency 🙂

@iRyusa
Copy link
Member

iRyusa commented Mar 1, 2023 via email

@stadikam3
Copy link

Hey @iRyusa, wanted to see if you could provide a rough timetable for this rollout and/or if end of this month is still the target?

@stadikam3
Copy link

stadikam3 commented Mar 22, 2023

Hello 👋 Since there are drawbacks in both options, would you consider letting the consumer pass in the minifier implementation?

For example, the mjml-core is currently importing html-minifier and calling it here. Could it take htmlMinify as an option? e.g.

// Remove import { minify as htmlMinify } from 'html-minifier' at the top
export default function mjml2html(mjml, options = {}) {
  // ... all the stuff

  content = options.htmlMinify(content, {
      collapseWhitespace: true,
      minifyCSS: false,
      caseSensitive: true,
      removeEmptyAttributes: true,
      ...minifyOptions,
    })
}

From what I can see, most options like collapseWhitespace, minifyCSS, etc. exist in html-minifier-terser too. So, in theory this could work?

And just to confirm, the optional method decided is not the one that eddeee888 provided here right? The optional method would make html-minifier an optional dependency and wouldn't conduct the minify process if require('html-minifier') isn't resolved. So if users wanted to minify with a different minifier, they would have to do it on their own outside of the package, correct?

@OasisLiveForever
Copy link

Hello @iRyusa, do you have an estimated date of when version will be released with the fix?

Thanks.

@mstudio
Copy link

mstudio commented May 18, 2023

Hi @iRyusa any update on html-minifier?

@metal450
Copy link

We're not able to pass our security audit due to this vulnerability - seemed like there was a fairly simple & promising solution, but no progress in ~6mo. Is this still on the table, or do we have to migrate away? :(

@iRyusa
Copy link
Member

iRyusa commented Aug 29, 2023

I might be able to find some time to finally test this PR : #2666 let's hope I can release this MJML5.0 before the end of September as a "beta".

If you want to help about this feel free to reach out on Slack.

We need to test that migration from MJML4.x to 5.x is smooth for user that require to still use html-minifier and js-beautify and not concerned by this CVE. Ensure that the documentation is explaining properly this change to users.

The raw output of HTML from MJML without beautify/minify is a bit weird maybe we could add prettier to format the HTML at least to have a somewhat readable output by default ?

I will commit to this on my free time so any help is welcomed.

@FedoraTipper
Copy link

Is this still being looked at?

@iRyusa
Copy link
Member

iRyusa commented Jan 23, 2024

I'm still looking for a way to have a somewhat readable output from MJML without any dep but looks really hard to do as I don't have enough time to work on this.

html-nano seems to be a good option to replace current html-minifier tho.

@boxexchanger
Copy link

Any updates

@iRyusa
Copy link
Member

iRyusa commented Apr 25, 2024

Just copy pasted from your original issue :

For what it's worth : this vulnerability isn't anything new. We started to check alternatives as html-minifier doesn't seem to be updated anymore :

  • switch to a different package html-minifier-terser is way too big as a dependency to be worth considering, htmlnano seem to be the best candidate
  • forking html-minifier as mjml-html-minifier and patch it ourself : I think that could work too as we can just remove unused feature for MJML. I'm nowhere near an expert level in tokenizer so I don't know how to properly patch out the reDos vulnerability that html-minifier has.
  • removing completely minifying as an option and let users pipe the result in w/e library: not ideal IMO as some email doesn't work in some email clients when not properly minified.

You can check https://www.npmjs.com/package/mjml/v/5.0.0-alpha.1 this version remove html-minifier and js-beautify. It relies on htmlnano and prettier instead.

➜  mjml-5 yarn audit
yarn audit v1.22.22
0 vulnerabilities found - Packages audited: 224
➜  mjml-5 npm audit
found 0 vulnerabilities

@santialbo
Copy link

santialbo commented Apr 25, 2024

Just copy pasted from your original issue :

For what it's worth : this vulnerability isn't anything new. We started to check alternatives as html-minifier doesn't seem to be updated anymore :

* switch to a different package html-minifier-terser is way too big as a dependency to be worth considering, htmlnano seem to be the best candidate

* forking html-minifier as `mjml-html-minifier` and patch it ourself : I think that could work too as we can just remove unused feature for MJML. I'm nowhere near an expert level in tokenizer so I don't know how to properly patch out the reDos vulnerability that `html-minifier` has.

* removing completely minifying as an option and let users pipe the result in w/e library: not ideal IMO as some email doesn't work in some email clients when not properly minified.

You can check https://www.npmjs.com/package/mjml/v/5.0.0-alpha.1 this version remove html-minifier and js-beautify. It relies on htmlnano and prettier instead.

➜  mjml-5 yarn audit
yarn audit v1.22.22
0 vulnerabilities found - Packages audited: 224
➜  mjml-5 npm audit
found 0 vulnerabilities

I've been using htmlnano* for minifying instead with preset "safe" and minifyCss: false

so far so good.

* a patched version until this is released

@iRyusa
Copy link
Member

iRyusa commented Apr 25, 2024

@santialbo Should be good as we disable it by default too on the alpha https://github.com/mjmlio/mjml/pull/2858/files#diff-17b00ecc0c06136aa56a592b0e9154976c812427d95ca6ce9c0fc3c4d2e05305R407
But we might need to bump the version when they release your fix!

@boxexchanger
Copy link

+1

@alexbjorlig
Copy link

@iRyusa I tried updating to use "mjml": "^5.0.0-alpha.4",
But I get this error:

RollupError: [vite-plugin-sveltekit-compile] node_modules/prettier/index.mjs (2:9): "createRequire" is not exported by "__vite-browser-external:module", imported by "node_modules/prettier/index.mjs".

(does not happen with "mjml": "^4.15.3",)

@iRyusa
Copy link
Member

iRyusa commented May 21, 2024

Yeah that's expected as prettier isn't compatible browser side :(

Did you check if you can replace prettier by the standalone version in your rollup config https://prettier.io/docs/en/browser.html ?

@alexbjorlig
Copy link

alexbjorlig commented May 21, 2024

Ok, I'm actually using Sveltekit - but adding this to vite.config.js made everything work:

const config = {
   // rest of the config
   resolve: {
        alias: {
            prettier: 'prettier/standalone',
        },
    },
};

export default config;

Thanks!

@fahrnbach
Copy link

fahrnbach commented Jun 11, 2024

FYI for anyone using node, to use the alpha just update your package.json to change

"dependencies": { "mjml": "^5.0.0-alpha.4" },

then run npm install

@GillesB1
Copy link

GillesB1 commented Jun 26, 2024

FYI for anyone using node, to use the alpha just update your package.json to change

"dependencies": { "mjml": "^5.0.0-alpha.4" },

then run npm install

Can confirm that this worked for me, after upgrading my Node version to v22.3.0 (the dependency required a newer version than what I had)

@wilau2
Copy link

wilau2 commented Jul 18, 2024

I get
TypeError: A dynamic import callback was not specified.
When trying to update the lib to 5.0.0-alpha.4 when running my integration tests

I tried both on node18 and node22

fix :

"resolutions": {
    "prettier": "2.8.8"
  },

However the bundlesize increased enough for me to have to fix my project for the lamda max bundlesize constraint

@georgecartridge
Copy link

georgecartridge commented Jul 23, 2024

I've been having the same issue. After upgrading mjml to 5.0.0-alpha.4, I get the same error as @wilau2

Here's the error and stack trace I was seeing
node:internal/modules/esm/utils:229
    throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG();
          ^
TypeError: A dynamic import callback was invoked without --experimental-vm-modules
   at importModuleDynamicallyCallback .importModuleDynamicallyCallback (node:internal/modules/esm/utils:229:11 undefined)
   at Object. (root/functions/node_modules/prettier/index.cjs:593:23 undefined)
   at Runtime._execModule (root/functions/node_modules/jest-runtime/build/index.js:1264:24 undefined)
   at Runtime._loadModule (root/functions/node_modules/jest-runtime/build/index.js:931:12 undefined)
   at Runtime.requireModule (root/functions/node_modules/jest-runtime/build/index.js:834:12 undefined)
   at Runtime.requireModuleOrMock (root/functions/node_modules/jest-runtime/build/index.js:951:21 undefined)
   at Object. (root/functions/node_modules/mjml-core/lib/index.js:87:40 undefined)
   at Runtime._execModule (root/functions/node_modules/jest-runtime/build/index.js:1264:24 undefined)
   at Runtime._loadModule (root/functions/node_modules/jest-runtime/build/index.js:931:12 undefined)
   at Runtime.requireModule (root/functions/node_modules/jest-runtime/build/index.js:834:12 undefined)
   at Runtime.requireModuleOrMock (root/functions/node_modules/jest-runtime/build/index.js:951:21 undefined)
   at Object. (root/functions/node_modules/mjml/lib/index.js:9:41 undefined)
   at Runtime._execModule (root/functions/node_modules/jest-runtime/build/index.js:1264:24 undefined)
   at Runtime._loadModule (root/functions/node_modules/jest-runtime/build/index.js:931:12 undefined)
   at Runtime.requireModule (root/functions/node_modules/jest-runtime/build/index.js:834:12 undefined)
   at Runtime.requireModuleOrMock (root/functions/node_modules/jest-runtime/build/index.js:951:21 undefined)
   at Object. (root/functions/src/emails/utils/renderEmailTemplate.ts:5:14 undefined)
   at Runtime._execModule (root/functions/node_modules/jest-runtime/build/index.js:1264:24 undefined)
   at Runtime._loadModule (root/functions/node_modules/jest-runtime/build/index.js:931:12 undefined)
   at Runtime.requireModule (root/functions/node_modules/jest-runtime/build/index.js:834:12 undefined)
   at Runtime.requireModuleOrMock (root/functions/node_modules/jest-runtime/build/index.js:951:21 undefined)
   at Object. (root/functions/test/utilities/emails/renderEmailTemplate.spec.ts:1:1 undefined)
   at Runtime._execModule (root/functions/node_modules/jest-runtime/build/index.js:1264:24 undefined)
   at Runtime._loadModule (root/functions/node_modules/jest-runtime/build/index.js:931:12 undefined)
   at Runtime.requireModule (root/functions/node_modules/jest-runtime/build/index.js:834:12 undefined)
   at jestAdapter .jestAdapter (root/functions/node_modules/jest-circus/build/runner.js:95:13 undefined)
   at runTestInternal .runTestInternal (root/functions/node_modules/jest-runner/build/index.js:262:16 undefined)
   at runTest .runTest (root/functions/node_modules/jest-runner/build/index.js:326:7 undefined) {
  code: 'ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG'
}

The stack trace seems to point to an issue with prettier and jest. There's a couple github issues floating around the prettier and jest repo:

However, after testing upgrading to jest 30.0.0-alpha.1 and 30.0.0-alpha.1 pre-release, it still continued to throw that error for me

@iRyusa
Copy link
Member

iRyusa commented Jul 23, 2024 via email

@iRyusa
Copy link
Member

iRyusa commented Jul 23, 2024 via email

@sefasenturk95
Copy link

What types to use with 5.0.0-alpha.4?

@iRyusa
Copy link
Member

iRyusa commented Sep 10, 2024

We don't provide any types from MJML but it's exactly the same option except it's Promise.
For now I think we don't forward any minify / beautify options as we did in MJML4.x

@ericdesa
Copy link

While waiting for MJML 5, I did a fork with html-minifier-terser : https://www.npmjs.com/package/mjml-4-terser (for server-side only, the dependencies are too big to use in a browser)

@MichaelJCole
Copy link

Can this be backported until 5.0?

Can't use mjml because of this 2 year old high security issue.

@iRyusa
Copy link
Member

iRyusa commented Oct 30, 2024

It's a breaking change due to minifier going from sync to async. So no it won't be backported to 4.x branch. Use the latest MJML5, it should be released soon anyway.

@DanielRuf
Copy link

DanielRuf commented Nov 3, 2024

I suggest reading my comments and especially kangax/html-minifier#1135 (comment) - because switching to html-minifier-terser will not fix this.

@iRyusa
Copy link
Member

iRyusa commented Nov 3, 2024 via email

@DanielRuf
Copy link

@iRyusa that is good. Anyways, those suggesting to migrate to the html-minifier-terser package (I was a maintainer of it) should take some time to check my comments and do their due diligence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.