Skip to content

fix: address CVE ReDoS issue #20

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

Merged
merged 13 commits into from
Jun 21, 2025
Merged

fix: address CVE ReDoS issue #20

merged 13 commits into from
Jun 21, 2025

Conversation

j9t
Copy link
Owner

@j9t j9t commented Jun 1, 2025

Summary by CodeRabbit

  • New Features

    • Added CLI options to limit input length and custom fragment quantifiers for enhanced security.
    • Introduced input length validation and safer handling of custom fragments to prevent ReDoS attacks.
  • Bug Fixes

    • Enhanced handling of custom ignored fragments during HTML minification to prevent performance issues with very large inputs.
  • Chores

    • Updated the version number to 1.1.0.
    • Minor formatting adjustment (added a newline at end of file).
  • Tests

    • Added tests to ensure efficient processing of large inputs with custom fragments, preventing performance degradation.
    • Fixed a typo in a test name for clarity.
  • Documentation

    • Added security section detailing new options to prevent ReDoS vulnerabilities and guidance on safe regex usage.

j9t added 3 commits June 1, 2025 16:36
Ensures proper formatting by adding a newline at the end of the file. This change helps maintain consistency and adheres to coding standards.

(This commit message was AI-generated.)

Signed-off-by: Jens Oliver Meiert <[email protected]>
Improve the regex logic to handle custom fragments more robustly by accounting for nested occurrences. This ensures better accuracy when temporarily replacing ignored fragments.

[References: kangax/html-minifier#1135]

(This commit message was AI-generated.)

Signed-off-by: Jens Oliver Meiert <[email protected]>
Update package.json and package-lock.json to reflect the version change from 1.0.1 to 1.0.2. This ensures consistency across the project metadata.

(This commit message was AI-generated.)

Signed-off-by: Jens Oliver Meiert <[email protected]>
Copy link

coderabbitai bot commented Jun 1, 2025

Walkthrough

This update adds two new CLI options to limit input length and quantifier repetition to prevent ReDoS attacks. The HTML minifier enforces these limits by checking input size and bounding regex quantifiers for custom fragments. The package version is bumped to 1.1.0, new tests verify ReDoS protection, and documentation is updated accordingly.

Changes

File(s) Change Summary
cli.js Added CLI options customFragmentQuantifierLimit and maxInputLength with integer parsing and descriptions; added newline at EOF.
package.json Updated the "version" field from "1.0.1" to "1.1.0".
src/htmlminifier.js Added input length check throwing error if exceeding maxInputLength; modified regex for custom ignored fragments to use bounded quantifiers with configurable limit; logs warning on unlimited quantifiers.
tests/minifier.spec.js Added test case verifying ReDoS prevention by measuring minifier performance on inputs with large and repeated custom fragments; fixed typo in test name.
README.md Added documentation for new options customFragmentQuantifierLimit and maxInputLength and a new "Security" section explaining ReDoS protections.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-21T11_39_12_805Z-debug-0.log

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa03a7b and 82e14de.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • cli.js (1 hunks)
  • package.json (1 hunks)
  • src/htmlminifier.js (1 hunks)
🔇 Additional comments (2)
cli.js (1)

307-307: LGTM! Good formatting practice.

Adding a newline at the end of the file follows standard formatting conventions and improves file consistency.

package.json (1)

93-93: Appropriate version bump for security patch.

The patch version increment (1.0.1 → 1.0.2) correctly follows semantic versioning for a backward-compatible security fix.

Introduce input length validation to mitigate ReDoS risks in the custom fragments regex. Long inputs now fall back to a simpler, safer pattern. Added tests to ensure performance and correctness under various edge cases.

(This commit message was AI-generated.)

Signed-off-by: Jens Oliver Meiert <[email protected]>
@j9t
Copy link
Owner Author

j9t commented Jun 1, 2025

Used Claude Code also to explain the “reasoning” behind the updated solution:

⏺ What Happens When a Page is Larger Than 50KB

When the HTML input exceeds 50KB, the code switches to a simpler, safer regex pattern that still processes custom fragments correctly, but with slightly different behavior for edge cases.

The Safe Pattern Behavior

For inputs > 50KB:
// Uses this simpler pattern:
reCustomIgnore = new RegExp('(\s*)(' + customFragments.join('|') + ')(\s*)', 'g');

Instead of the original complex pattern:
// Original pattern (used for inputs ≤ 50KB):
reCustomIgnore = new RegExp('\s*(?:' + customFragments.join('|') + ')(?:(?:(?!\s*(?:' + customFragments.join('|') + '))\s)(?:' + customFragments.join('|') + '))\s*', 'g');

Practical Differences

✅ What Still Works Perfectly

  1. Individual Fragments: All custom fragments are still identified and preserved

<% include 'header' %>
{{ user.name }}
2. Basic Whitespace Handling: Leading/trailing whitespace around fragments is preserved

3. Core Functionality: HTML minification, compression, and fragment protection all work normally

⚠️ What Changes (Edge Cases Only)

Consecutive Fragment Grouping: The main difference is in how consecutive fragments separated only by whitespace are handled.

Example with consecutive fragments:

  <?php echo $content; ?>

For files ≤ 50KB (original pattern):

  • Groups consecutive fragments as a single unit
  • Whitespace between fragments handled as a group
  • Preserves exact original whitespace relationships

For files > 50KB (safe pattern):

  • Processes each fragment individually
  • Each fragment gets its own whitespace preservation
  • Slightly different whitespace normalization in some edge cases

Real-World Impact Assessment

Most Common Scenarios (✅ No Impact)

  1. Typical CMS Templates: WordPress, Drupal, etc.
2. Modern Frameworks: React SSR, Vue, Angular templates
{{ data | filter }}
3. Static Site Generators: Jekyll, Hugo, etc. {% include 'sidebar.html' %}

Edge Cases That Might See Minor Differences

  1. Multiple Adjacent PHP Blocks (rare):
  1. Complex Template Inheritance with many consecutive fragments
  2. Generated HTML with unusual fragment patterns

When Do You Actually Hit 50KB?

Realistic Examples of 50KB+ HTML:

  1. Large Product Catalogs: E-commerce pages with hundreds of products
  2. Data Tables: Admin dashboards with extensive tabular data
  3. Generated Reports: System-generated documents
  4. Email Templates: Complex marketing emails with lots of content
  5. Documentation Pages: Very long technical documentation

What 50KB looks like:

  • ~50,000 characters of text
  • ~1,000-2,000 lines of HTML (depending on content)
  • Equivalent to about 10-15 pages of printed text

Performance Characteristics

Monitoring and Debugging

If you need to check which pattern is being used:

// You could add logging to see when the threshold is hit
if (value.length > maxSafeLength) {
console.log(Large HTML detected (${value.length} chars), using safe pattern);
}

Should You Be Concerned?

For most developers: NO

  • ✅ The vast majority of HTML content is under 50KB
  • ✅ Even when the safe pattern is used, functionality is preserved
  • ✅ The differences are extremely subtle and rarely noticeable
  • ✅ Performance actually improves for large files

You might want to test if:

  • You regularly generate very large HTML documents (>50KB)
  • You have complex template inheritance with many consecutive fragments
  • You have strict requirements about exact whitespace preservation in edge cases

The 50KB threshold was chosen specifically because it's much larger than typical web content while being much smaller than the inputs needed to trigger ReDoS attacks (which typically require 100KB+ of pathological patterns).

@j9t
Copy link
Owner Author

j9t commented Jun 8, 2025

@DanielRuf, do you have a view on this? Would love to take you up on your offer to provide input! (I think this works, but do appreciate another opinion.)

@@ -888,8 +888,19 @@ async function minifyHTML(value, options, partialMarkup) {
return re.source;
});
if (customFragments.length) {
const reCustomIgnore = new RegExp('\\s*(?:' + customFragments.join('|') + ')+\\s*', 'g');
// temporarily replace custom ignored fragments with unique attributes
// Safe approach: Use original pattern but with input length validation to prevent ReDoS
Copy link

@DanielRuf DanielRuf Jun 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this. The original pattern is used three times here. Is this, how it should be?
A quick check with https://devina.io/redos-checker? should show you some possible ways to verify, if this resolves the aforementioned problem.

image

Using the original string and replacing * with {0,30} and + with {1,30} (just as demonstration) already makes a difference:

image

But if I set these lower and upper bounds for the expression in line 898 in these changes, it does not seem to resolve that:

image

Copy link

@DanielRuf DanielRuf Jun 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the AI made it worse by simply using capturing groups without any reasnoable quantifiers and by using a normal capturing group instead of a non-capturing group.

In pseudo language, it seems to be this now:

If value.length > maxSafeLength
Then ""unsafeRegEx"
Else "duplicatedOriginalUnsafeRegex"

image

And the duplicatedOriginalUnsafeRegex becomes this:

image

After replacing the unlimited quantifiers, the checker itself runs into a timeout:

image

Only after setting some lower limits the checker says at least, that this might not be vulnerable:

image

So now it's more complex than before. At least any + and * is a way to reintroduce ReDos in this part of the code.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DanielRuf, thank you so much!

I think the more complicated regex was a mistake—reverted to use the original regex indeed.

Does the reasoning hold that the issue is mostly one for large inputs? Then I do believe that to work (at least while lacking an alternative for all inputs)?

(Had run a few attempts here… not my usual cup of tea so I did use Claude Code here.)

Copy link

@DanielRuf DanielRuf Jun 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware of a reasonable length value. Especially since this is a bit hard to define.

My suggestion at the moment:

That should result in a more linear or predictable processing time instead of exponential growth (due to catastrophic backtracking for example) in processing due to problematic regular expressions with unlimited quantifiers like + and * and the resulting compiled automatons.

Further resources on this topic:

In general the best approach is often to not use regular expressions, when user-supplied input is accepted but to use finite and deterministic linear string manipulations.

Does that make sense?

j9t added 6 commits June 8, 2025 20:04
Simplified the regular expression used for normal inputs to improve efficiency and maintainability. The updated regex is less prone to potential issues while retaining the necessary functionality.

(This commit message was AI-generated.)

Signed-off-by: Jens Oliver Meiert <[email protected]>
…, improve docs and tests\n\n- Use bounded quantifiers in default ignoreCustomFragments\n- Add maxInputLength option and warnings\n- Warn on unsafe custom regexes\n- Update docs and CLI\n- Add/extend tests for ReDoS prevention
Update package.json and package-lock.json to reflect the new version 1.1.0. This prepares for the release with any included changes.

(This commit message was AI-generated.)

Signed-off-by: Jens Oliver Meiert <[email protected]>
Add input length validation and bounded quantifiers to prevent ReDoS vulnerabilities in HTML minification. Warn users about potential risks of using unlimited quantifiers in custom fragments. This improves the security and stability of the minification process.

(This commit message was AI-generated.)

Signed-off-by: Jens Oliver Meiert <[email protected]>
Introduce `customFragmentQuantifierLimit` and `maxInputLength` to improve security by mitigating ReDoS attack risks. These options provide limits for regex quantifiers and input length.

(This commit message was AI-generated.)

Signed-off-by: Jens Oliver Meiert <[email protected]>
Updated the README to include new security-related options (`customFragmentQuantifierLimit` and `maxInputLength`) and detailed explanations on ReDoS protection. Added a dedicated "Security" section for clarity and user guidance.

(This commit message was AI-generated.)

Signed-off-by: Jens Oliver Meiert <[email protected]>
@DanielRuf
Copy link

I think this needs more changes for the regular expressions.
Especially when I use the correct version without the lazy (*?) quantifier (I probably oversaw that and had no time to check further), I still get the following result:

image

https://www.rexegg.com/regex-quantifiers.php#lazy_solution might contain some recommendations, but I think the way forward is a completely different approach as previously outlined. To be fair, I never used the lazy solution (?) since this was always a source of different problems like the described lazy trap at rexgegg.

According to https://www.rexegg.com/regex-explosive-quantifiers.php even double-digit upper bounds can be problematic. Which just confirms that regular expressions for this project are not the ideal solution.

@j9t
Copy link
Owner Author

j9t commented Jun 14, 2025

You were quick, @DanielRuf 🙂

This should still be an improvement though, no?

Reviewing your feedback, looking into a different approach… (Thank you…!)

Signed-off-by: Jens Oliver Meiert <[email protected]>
@j9t
Copy link
Owner Author

j9t commented Jun 14, 2025

@DanielRuf, worked on this for a few more hours and just stashed some more changes (#23), but may need to table this for the time being.

My read is that this PR does contain an improvement—would I be mistaken? If not, I’d merge this and look into other improvements later. My impression is that this needs a major rewrite.

@DanielRuf
Copy link

This should still be an improvement though, no?

Probably, yes.

My read is that this PR does contain an improvement

Should be a bit better than before.

My impression is that this needs a major rewrite.

I totally agree.

j9t added 2 commits June 21, 2025 13:12
Updated the headings to follow a consistent format and restructured the security section for clarity and readability. Minor adjustments were made to improve information accessibility and invite contributions for further improvements.

(This commit message was AI-generated.)

Signed-off-by: Jens Oliver Meiert <[email protected]>
Lowered the `customFragmentQuantifierLimit` default from 1000 to 200 for improved protection against regular expression denial of service (ReDoS) attacks. Updated relevant documentation and CLI descriptions to reflect this change. Added detailed examples and enhanced guidance for defining safe custom fragment patterns.

(This commit message was AI-generated.)

Signed-off-by: Jens Oliver Meiert <[email protected]>
@j9t j9t changed the title fix: address CVE ReDoS issue (TBC) fix: address CVE ReDoS issue Jun 21, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
README.md (2)

96-96: Document maxInputLength option
The new maxInputLength entry accurately reflects the default and purpose. Consider noting in the description that the default is “unlimited” when unset (undefined).


159-171: Add Security / ReDoS protection section
Clear security section with quantifier limits, input length caps, and pattern detection. For consistency, mention the default for maxInputLength here (“disabled by default”) to match the quantifier bullet.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5aa1da and 5308689.

📒 Files selected for processing (4)
  • README.md (4 hunks)
  • cli.js (3 hunks)
  • src/htmlminifier.js (2 hunks)
  • tests/minifier.spec.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • cli.js
  • src/htmlminifier.js
  • tests/minifier.spec.js
🧰 Additional context used
🪛 LanguageTool
README.md

[misspelling] ~79-~79: This word is normally spelled with a hyphen.
Context: ...| caseSensitive | Treat attributes in case sensitive manner (useful for custom HTML tags) | ...

(EN_COMPOUNDS_CASE_SENSITIVE)


[style] ~173-~173: Consider an alternative to strengthen your wording.
Context: ...se can lead to ReDoS vulnerabilities. (Further improvements are needed. Contributions welcome.) ##...

(IMPROVEMENTS_REFINEMENTS)

🔇 Additional comments (3)
README.md (3)

3-3: Update README badge to npm version
Replacing the version badge with the npm shield is great for live status.


81-81: Document customFragmentQuantifierLimit option
The new table entry correctly lists the quantifier limit option and default; good clarity and default value.


190-196: Potentially unsafe patterns examples
The unsafe patterns block is comprehensive and includes a closing fence; good coverage of edge cases.

Comment on lines +180 to +185
ignoreCustomFragments: [
/<%[\s\S]{0,1000}?%>/, // JSP/ASP with explicit bounds
/<\?php[\s\S]{0,5000}?\?>/, // PHP with bounds
/\{\{[^}]{0,500}\}\}/ // Handlebars without nested braces
]
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing closing code fence for safe patterns block
The safe patterns example starts with “javascript” but lacks the corresponding closing “”, which will break Markdown rendering.

Apply:

   /\{\{[^}]{0,500}\}\}/          // Handlebars without nested braces
 ]
+ ```
🤖 Prompt for AI Agents
In README.md around lines 180 to 185, the code block showing safe patterns
starts with a triple backtick but is missing the closing triple backtick. Add
the closing triple backtick ``` after the last line of the code block to
properly close it and fix Markdown rendering.

Comment on lines +200 to +212
```javascript
// Handlebars/Mustache
ignoreCustomFragments: [/\{\{[\s\S]{0,1000}?\}\}/]

// Liquid (Jekyll)
ignoreCustomFragments: [/\{%[\s\S]{0,500}?%\}/, /\{\{[\s\S]{0,500}?\}\}/]

// Angular
ignoreCustomFragments: [/\{\{[\s\S]{0,500}?\}\}/]

// Vue.js
ignoreCustomFragments: [/\{\{[\s\S]{0,500}?\}\}/]
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing closing code fence for template engine examples
The template-engine config block begins with “```javascript” but is missing the closing triple backticks at the end.

Apply:

   ignoreCustomFragments: [/\{\{[\s\S]{0,500}?\}\}/]
   // Vue.js
   ignoreCustomFragments: [/\{\{[\s\S]{0,500}?\}\}/]
+ ```
🤖 Prompt for AI Agents
In README.md around lines 200 to 212, the code block showing template engine
examples starts with triple backticks and "javascript" but lacks the closing
triple backticks at the end. Add the missing closing triple backticks after the
last line of the code block to properly close the fenced code block.

@j9t j9t merged commit ca2cebc into main Jun 21, 2025
14 checks passed
@j9t
Copy link
Owner Author

j9t commented Jun 21, 2025

Thanks again, @DanielRuf, for your help! (I’ve just added a little acknowledgments section as well, with #27.)

I’ll focus on testing this change, pulling up some more dependencies, and looking into other improvements, but would look forward to receiving your advice and collaborating again 🙏

@j9t
Copy link
Owner Author

j9t commented Jul 13, 2025

Hi @DanielRuf—I totally didn’t make the connection between you and html-minifier-terser! I had reached out to various people there, wondering if and when html-minifier-terser would be maintained again. But if I understand comments there correctly, there are no such plans?

@DanielRuf
Copy link

DanielRuf commented Jul 13, 2025

But if I understand comments there correctly, there are no such plans?

I can neither confirm nor deny. To be frankly clear, I am not an active GitHub user anymore and I mainly just react to mentions if really needed. So I am not involved in this project anymore.

Currently I have no maintainer status (currently I am just a normal external contributor, not collaborator or maintainer) and left the terser org on GitHub on my own. You can see the current status at the top right in the comments made by me:

image

These two users (who were previously invited and added to the project by me as you can see at terser#79) have the needed rights to maintain the project and I can see that they replied / reacted to you at terser#197:

image image

I think they are open to get things forward with your help. More important things in life always happen, so any help by contributors is always very welcome.

Maybe this clears things up a bit and you can work together with these two, resulting in new releases and you joining them as new additional maintainer.

@j9t
Copy link
Owner Author

j9t commented Jul 13, 2025

Thanks for sharing more context, @DanielRuf—much appreciated!

Would be curious to learn what your focus is on now, btw—very open to continuing per email (and all good if this isn’t the time).

@DanielRuf
Copy link

@j9t I'm focusing now on IT security and Ethical Hacking (especially creating a new blog around this topic to show how real ethical hackers like myself work - not like most of the begbounty hunters).

@j9t
Copy link
Owner Author

j9t commented Jul 14, 2025

Would be curious to learn more, and follow that work! Also open to feature posts on Frontend Dogma—feel free to hit me up at any time!

@DanielRuf
Copy link

@j9t thanks for your interest. I can only be reached via Threema: https://threema.id/74SF7MW6?text=

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 this pull request may close these issues.

2 participants