High

html-minifier CVE-2022-37620

Regular Expression Denial of Service in html-minifier (reCustomIgnore).

The problem

html-minifier <= 4.0.0 (all published versions; the html-minifier-terser successor is also affected) is affected by CVE-2022-37620 (CWE-1333 Inefficient Regular Expression Complexity (ReDoS)). The published package has no fix available — exactly what npm audit reports. html-minifier's `minify()` replaces "ignored custom fragments" (template tags like `<%...%>` and `<?...?>`) via the `reCustomIgnore` regular expression `\s*(?:...)+\s*`. The leading `\s*` backtracks catastrophically: input containing a long run of whitespace not followed by a fragment makes `minify()` take time quadratic in the input length (a ~160 KB string takes ~40 seconds), so untrusted HTML can hang the event loop. Note: the widely-used successor `html-minifier-terser` ships the identical regex and is also affected; at the time of writing there is no fixed version of either on npm.

The fix — drop-in, no code changes

Add an overrides entry so every direct and transitive dependency on html-minifier resolves to the patched fork, then reinstall:

{
  "overrides": {
    "html-minifier": "npm:@keep-lts/html-minifier@^4.0.1"
  }
}

Equivalent for Yarn: use resolutions. The public API is unchanged — nothing else to do.

✓ Live on npm: @keep-lts/html-minifier  ·  or install directly: npm i @keep-lts/html-minifier

This is rarely the only one

If html-minifier turned up in your npm audit, there are very likely other abandoned dependencies in the same tree whose CVEs also have no fix on npm. Send us your npm audit output — or your Snyk / Dependabot list — and we'll tell you, for free, which of those findings we already fix. No call, no obligation.

Mid-audit and need a vendor on the hook? Keep LTS Pro adds an SLA on future CVEs and the SBOM / VEX artefacts your auditor will accept — all async, no calls.

What we changed

The leading `\s*` in `reCustomIgnore` is bounded to `\s{0,100}`. The trailing `\s*` is left unbounded because it only runs after a fragment has matched and therefore never backtracks. No real template has 100+ whitespace characters immediately before an ignored fragment, so minified output is byte-for-byte identical for realistic input — confirmed across 75 input/option combinations against the original. Matching is now linear (a 2 MB worst-case input completes in well under a second).

Proof of concept (the vulnerability)

const { minify } = require('html-minifier');
minify('x' + ' '.repeat(200000) + '!'); // hangs for many seconds on 4.0.0
Patched version@keep-lts/html-minifier@4.0.1
Weekly downloads (affected pkg)3,322,664
Regression tests7 passing
LicenseMIT

How we keep it trustworthy

Full advisory and changelog ship inside the package (SECURITY.md, CHANGELOG.md).

← All maintained packages