diff --git a/src/filters/html.ts b/src/filters/html.ts
index 0dd5f77d3..21cb8c4a9 100644
--- a/src/filters/html.ts
+++ b/src/filters/html.ts
@@ -42,8 +42,25 @@ export function newline_to_br (this: FilterImpl, v: string) {
return str.replace(/\r?\n/gm, '
\n')
}
+// Raw-text blocks (HTML5) plus '<...>' as the catch-all kind; a regex
+// equivalent is O(n^2) in V8 on unclosed openers.
export function strip_html (this: FilterImpl, v: string) {
const str = stringify(v)
this.context.memoryLimit.use(str.length)
- return str.replace(/'], ['Ulysses?" | strip_html }}',
'Ulysses?')
diff --git a/test/integration/liquid/dos.spec.ts b/test/integration/liquid/dos.spec.ts
index 796f2db8e..85783564e 100644
--- a/test/integration/liquid/dos.spec.ts
+++ b/test/integration/liquid/dos.spec.ts
@@ -79,5 +79,30 @@ describe('DoS related', function () {
await expect(liquid.parseAndRender(src, { array, count: 3 })).resolves.toBe('a a a a a a a a')
await expect(liquid.parseAndRender(src, { array, count: 100 })).rejects.toThrow('memory alloc limit exceeded, line:1, col:26')
})
+ it('should charge strip_html input length to memoryLimit', () => {
+ const liquid = new Liquid({ memoryLimit: 100 })
+ expect(() => liquid.parseAndRenderSync('{{ s | strip_html }}', { s: 'a'.repeat(200) }))
+ .toThrow('memory alloc limit exceeded')
+ })
+ })
+ describe('strip_html ReDoS', () => {
+ // Regression for O(n^2) backtracking on unclosed ` in linear time', () => {
+ const liquid = new Liquid()
+ const payload = '