LiquidJS Vulnerable to ReDoS via Quadratic Backtracking in `strip_html` Filter Regex
Date: 2026-05
Affected: npm/liquidjs
Attack vector: dos
Category: vulnerability-disclosure
Severity: High
CVE: CVE-2026-45617
CWE: CWE-1333
CVSS: 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

## Summary The built-in `strip_html` filter in liquidjs uses a regex containing four lazy-quantified alternatives. When the input contains many `<script`, `<style`, or `<!--` opener tokens without matching closers, the V8 regex engine performs O(N²) backtracking, blocking the Node.js event loop. A single ~350 KB request (`'<script'.repeat(50000)`) stalls the process for ~10 seconds; cost grows quadratically with input size. The default `memoryLimit: Infinity` does not bound regex CPU, and even when configured `strip_html` only charges `str.length` to the limit — the regex itself runs unbounded. ## Details The vulnerable filter is at `src/filters/html.ts:45-49`: ```ts export function strip_html (this: FilterImpl, v: string) { const str = stringify(v) this.context.memoryLimit.use(str.length) return str.replace(/<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>|<.*?>|<!--[\s\S]*?-->/g, '') } ``` The regex contains four lazy patterns: 1. `<script[\s\S]*?<\/script>` 2. `<style[\s\S]*?<\/style>` 3. `<.*?>` 4. `<!--[\s\S]*?-->` For an input like `'<script'.repeat(N)`, the engine encounters N starting `<` positions. At each one it must lazily expand `[\s\S]*?` (and `.*?`) all the way to end-of-input searching for a closer that never appears, then fail and backtrack. Because each of the O(N) starts performs O(N) lazy-expansion work, total work is O(N²). Reachability: 1. `strip_html` is a default-registered filter (exported from `src/filters/html.ts`, wired up via `src/fil


Mitigations:

Architectural layers (MAESTRO):