LiquidJS's strip_html filter bypass via newline characters in HTML tags enables XSS
Date: 2026-05
Affected: npm/liquidjs
Attack vector: xss
Category: vulnerability-disclosure
Severity: Medium
CVE: CVE-2026-44644
CWE: CWE-79
CVSS: 6.1 (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N)

## Summary The `strip_html` filter in liquidjs is intended to remove HTML tags from a string before rendering, and is widely used as an XSS sanitizer. The implementation uses a regex whose catch-all branch (`<.*?>`) does not match line terminators, so any HTML tag containing a `\n` or `\r` character passes through unmodified. An attacker who can place a newline inside a tag (e.g. `<img\nsrc=x\nonerror=alert(1)>`) bypasses sanitization entirely, since browsers treat newlines as whitespace within a tag and execute the resulting `onerror`/`onload`/etc. handler. This results in stored or reflected XSS in any application that relies on `strip_html` to neutralize untrusted HTML. ## Details The vulnerable code is in `src/filters/html.ts`: ```ts // src/filters/html.ts:45-49 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 has four alternations: 1. `<script[\s\S]*?<\/script>` — uses `[\s\S]`, matches across newlines. 2. `<style[\s\S]*?<\/style>` — uses `[\s\S]`, matches across newlines. 3. `<.*?>` — uses `.`, which in JavaScript does **not** match `\n` or `\r` (no `s`/dotAll flag set). 4. `<!--[\s\S]*?-->` — uses `[\s\S]`, matches across newlines. Branch 3 is the catch-all for "any other tag." Because `.` excludes line terminators, a tag containing a newline does not match any alternat


Mitigations:

Architectural layers (MAESTRO):