-
Notifications
You must be signed in to change notification settings - Fork 219
Fix ReDoS in stripHtml #308
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,8 @@ const entities = require('entities'); | |
| const xml2js = require('xml2js'); | ||
|
|
||
| utils.stripHtml = function(str) { | ||
| str = str.replace(/([^\n])<\/?(h|br|p|ul|ol|li|blockquote|section|table|tr|div)(?:.|\n)*?>([^\n])/gm, '$1\n$3') | ||
| str = str.replace(/<(?:.|\n)*?>/gm, ''); | ||
| str = str.replace(/([^\n])<\/?(h|br|p|ul|ol|li|blockquote|section|table|tr|div)[^<>]*>([^\n])/gm, '$1\n$3') | ||
| str = str.replace(/<[^<>]*>/gm, ''); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Behavior change: a literal Measured with the old vs. new bodies of A literal Concretely: add a case to the (If you ever want the old semantics back without the ReDoS, |
||
| return str; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,5 +41,11 @@ describe('Utils', function() { | |
| Expect('|' + utils.getSnippet(tc.input) + '|').to.equal('|' + tc.output + '|', tc.input); | ||
| }); | ||
| }) | ||
|
|
||
| it('should handle repeated unterminated HTML tags efficiently', function() { | ||
| this.timeout(2000); | ||
| var input = 'a<br'.repeat(40000); | ||
| Expect(utils.getSnippet(input)).to.equal(input); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implicit timeout dependency. This test only fails on a performance regression because the pre-fix runtime (~19 s) exceeds Mocha's default 2 s timeout. If someone later raises the suite-wide timeout (a common CI tweak), this test silently degrades into a plain correctness check and stops guarding against the ReDoS. Make the intent explicit — e.g. use a it('should handle repeated unterminated HTML tags efficiently', function() {
this.timeout(2000);
var input = 'a<br'.repeat(40000);
Expect(utils.getSnippet(input)).to.equal(input);
});There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test's performance guarantee is implicit, and the assertion is the wrong shape for a 160 KB string. The test name promises "efficiently", but nothing here asserts anything about time. It only fails pre-fix because ~8.5 s (measured locally) exceeds Mocha's 2 s default timeout — there is no Make the bound explicit. Note the arrow function on line 45 also prevents it('should handle repeated unterminated HTML tags efficiently', function() {
this.timeout(1000);
var input = 'a<br'.repeat(40000);
Expect(utils.getSnippet(input)).to.equal(input);
})Secondary: |
||
| }) | ||
| }); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edge case — behavior change for
<in quoted attribute values. With the old lazy regex,a<p title="5 < 3">bstripped toa\nb. With[^<>]*the first regex can no longer cross the inner<, and the second regex on line 7 then consumes< 3">, leavinga<p title="5 b— a raw tag fragment leaks into the snippet. A literal<inside a quoted attribute value is valid HTML5, so this isn't strictly a malformed-input-only regression.This is a reasonable trade-off for eliminating the ReDoS (and this function is a best-effort stripper, not a parser), but it should be a deliberate choice. If you want to keep coverage for this case, an alternative that stays linear is allowing quoted strings inside the tag, e.g.
(?:[^<>"']|"[^"]*"|'[^']*')*in place of[^<>]*. Otherwise, consider noting the limitation so a future "why is a tag fragment in my snippet" report doesn't get reverted back to the vulnerable pattern.