CVE-2026-61556: LiquidJS strip_html Infinite Loop (DoS)
A two-character input to LiquidJS's built-in strip_html filter causes the Node.js event loop to spin forever, making any app that renders untrusted content permanently unresponsive.

The problem
The strip_html filter in LiquidJS (versions 10.26.0 through 10.27.0) rewrites its HTML-stripping logic as a manual single-pass scanner to avoid a prior ReDoS. That scanner contains an off-by-one in its loop-exit guard.
When input contains a < with at least one character before it and no closing > anywhere after it, the loop variable i is never advanced. The check if (i === lt) only exits the loop when i and lt start at the same position, which does not happen for input like "a<".
The loop restarts from the same state indefinitely, hanging the process and enabling DoS with just two characters of input.
Proof of concept
A working proof-of-concept for CVE-2026-61556 in liquidjs, with the exact payload below.
const { Liquid } = require('liquidjs');
const engine = new Liquid();
// Two characters are enough to hang the process forever.
engine.parseAndRender('{{ html | strip_html }}', {
html: 'a<'
}).then(console.log);
console.log('This line is never reached.');The root cause is a wrong equality check in the loop-exit guard inside src/filters/html.ts. After the inner for loop exhausts all openers without finding a closer, execution falls through to if (i === lt). For input "a<", i is 0 and lt is 1, so the condition is false and the loop does not exit.
The outer while loop restarts with i still at 0, finds lt = 1 again, and repeats forever (CWE-835).
The one-character patch changes === to <=. This makes the guard fire whenever i has not advanced past lt, which covers the off-by-one case and all similar stuck states, correctly returning the remaining unprocessed input instead of looping.
The fix
Upgrade to liquidjs 10.27.1. The patch changes a single operator in src/filters/html.ts: the loop-exit check if (i === lt) becomes if (i <= lt), ensuring the function always returns when no closer is found after an opener. No configuration workaround exists for older versions; upgrading is the only remediation.
Related research
- highCVE-2026-55575CVE-2026-55575: LiquidJS pop Filter memoryLimit Accounting Bypass
- high · 7.5CVE-2026-59879CVE-2026-59879: Immutable.js List 32-bit Trie Overflow leading to Denial of Service
- high · 7.5CVE-2026-59874CVE-2026-59874: node-tar Infinite Loop via Negative Base-256 Entry Size
- high · 8.2CVE-2026-65842CVE-2026-65842: @platejs/docx-io Server-Side Request Forgery via Remote Image Fetch