LiquidJS has an infinite loop vulnerability in its `strip_html` filter
Summary
The current implementation of strip_html can cause an infinite loop when the input string contains <, has at least one character before <, and no > appears after <.
Details
The problem is in src/filters/html.ts.
Specifically, the following part has the infinite loop.
// 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)
const blocks = new Map([['<script', '</script>'], ['<style', '</style>'], ['<!--', '-->'], ['<', '>']])
let out = ''
let i = 0
while (i < str.length) {
const lt = str.indexOf('<', i)
if (lt < 0) return out + str.slice(i)
out += str.slice(i, lt)
for (const [opener, closer] of blocks) {
if (!str.startsWith(opener, lt)) continue
const e = str.indexOf(closer, lt + opener.length)
if (e >= 0) { i = e + closer.length; break }
blocks.delete(opener)
}
if (i === lt) return out + str.slice(lt)
}
return out
}
For the input "a<", the variable lt is updated to 1 by const lt = str.indexOf('<', i). However, the variable i is never updated from its initial value of 0. This is because in const e = str.indexOf(closer, lt + opener.length), e becomes -1, since there is no > after <. Therefore, when execution reaches if (i === lt) return out + str.slice(lt), i is 0. This is the same state as at the beginning of the loop. As a result, the same thing is repeated again from that state, causing an infinite loop.
PoC
const { Liquid } = require('liquidjs');
const engine = new Liquid();
engine.parseAndRender('{{ html | strip_html }}', {
html: 'a<'
}).then(console.log);
console.log("This is never displayed.");
Impact
This is an infinite loop vulnerability (cf. https://cwe.mitre.org/data/definitions/835.html). This results in a denial of service (DoS). Although a ReDoS vulnerability has previously been reported in the affected function (cf. https://github.com/harttle/liquidjs/security/advisories/GHSA-r7g9-xpmj-5fcq), this issue can cause a more severe impact than that ReDoS vulnerability with an input of only two characters at minimum.
Recommended Fix
There is an issue with the following conditional branch.
if (i === lt) return out + str.slice(lt);
The following should fix the issue.
if (i <= lt) return out + str.slice(lt);
Affected software
Click a technology to see all vulnerabilities affecting it.
| Platform | Package | Vulnerable versions | Fixed in |
|---|---|---|---|
| npm package | liquidjs | >= 10.26.0, < 10.27.1 | 10.27.1 |
References
- https://github.com/harttle/liquidjs/security/advisories/GHSA-m7fp-h3p4-hr49
- https://nvd.nist.gov/vuln/detail/CVE-2026-61556
- https://github.com/harttle/liquidjs/pull/917
- https://github.com/harttle/liquidjs/commit/5c3522f33928aae66f0fe85c36e1d9015c768fe2
- https://github.com/harttle/liquidjs
- https://github.com/harttle/liquidjs/releases/tag/v10.27.1
- CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N- Weakness (CWE)
CWE-835- Platform
- npm
- Package
liquidjs- Disclosed
- 03.09.2026
- Updated
- 11.10.2026 10:22
Sources and licenses
OSV / GitHub Advisory Database - GHSA-m7fp-h3p4-hr49 - CC BY 4.0
Vulnerability history: liquidjs
All published vulnerabilities of this package (npm package), newest first. Shows whether this is a one-off slip or a package that needs watching.
- Total entries
- 19
- First → latest
- 12.2022 10.2026
- Without a fix
- 6
- Exploited in attacks
- 0
Severity breakdown
- Critical 1
- High 10
- Medium 7
- Low 1
Disclosures per year
- 2022: 1
- 2023: 0
- 2024: 0
- 2025: 0
- 2026: 18
-
LiquidJS has an infinite loop vulnerability in its `strip_html` filter this entry
Know which vulnerabilities affect your application
With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.