Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-45617

CVE-2026-45617: LiquidJS Template Engine DoS Vulnerability

CVE-2026-45617 is a denial of service flaw in LiquidJS template engine caused by ReDoS in the strip_html filter. Attackers can block the event loop with crafted input. This article covers technical details, affected versions, and mitigation.

Published:

CVE-2026-45617 Overview

CVE-2026-45617 is a Regular Expression Denial of Service (ReDoS) vulnerability in LiquidJS, a Shopify and GitHub Pages compatible template engine written in pure JavaScript. The flaw affects versions 10.25.7 and below. The built-in strip_html filter uses a regex with four lazy-quantified alternatives that triggers quadratic backtracking in the V8 engine. A single unauthenticated request containing many <script, <style, or <!-- opener tokens without matching closers blocks the Node.js event loop. The vulnerability is tracked under [CWE-1333: Inefficient Regular Expression Complexity].

Critical Impact

A single ~350 KB request can stall a Node.js process for approximately 10 seconds, saturating workers and bypassing the configured memoryLimit protection.

Affected Products

  • LiquidJS versions 10.25.7 and earlier
  • Node.js applications using the strip_html filter on untrusted input
  • Shopify/GitHub Pages compatible template rendering pipelines built on LiquidJS

Discovery Timeline

  • 2026-06-17 - CVE-2026-45617 published to NVD
  • 2026-06-17 - Last updated in NVD database

Technical Details for CVE-2026-45617

Vulnerability Analysis

The strip_html filter in src/filters/html.ts removes HTML tags from a string by applying a single regex with four lazy-quantified alternatives. When the input contains many opener tokens such as <script, <style, or <!-- with no matching closers, the V8 regex engine repeatedly retries alternative branches at every position. This produces O(N²) backtracking behavior. An input of '<script'.repeat(50000) (about 350 KB) stalls the event loop for roughly 10 seconds. Larger inputs scale quadratically.

The filter's only resource guard charges str.length to the memoryLimit, which accounts for the input size but does not constrain regex CPU time. The default memoryLimit: Infinity provides no protection. The result is a low-effort, unauthenticated denial-of-service primitive against any service that exposes LiquidJS template rendering of user-supplied content.

Root Cause

The root cause is the use of catastrophic-backtracking-prone alternation in a single regex: /<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>|<[\s\S]*?>|<!--[\s\S]*?-->/g. Each lazy quantifier forces the engine to attempt match extensions character by character, and the four overlapping alternatives multiply retry paths when closers are absent.

Attack Vector

An unauthenticated remote attacker submits crafted untrusted input that flows into a Liquid template using the strip_html filter. No authentication or user interaction is required. The request causes CPU amplification that blocks the Node.js event loop and starves concurrent requests on the same worker.

typescript
// Security patch in src/filters/html.ts
// fix(strip_html): rewrite as linear single-pass scan to avoid ReDoS (#896)
   return str.replace(/\r?\n/gm, '<br />\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(/<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>|<[\s\S]*?>|<!--[\s\S]*?-->/g, '')
+  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
 }

Source: GitHub Commit 3616a744. The patch replaces the vulnerable regex with a linear single-pass scan using indexOf lookups for each opener/closer pair, guaranteeing O(N) processing time.

Detection Methods for CVE-2026-45617

Indicators of Compromise

  • HTTP requests containing repeated <script, <style, or <!-- opener tokens without matching closing tags.
  • Inbound payloads larger than typical request sizes routed to endpoints that render Liquid templates.
  • Node.js worker processes showing sustained 100% CPU on a single core during request handling.

Detection Strategies

  • Inspect application logs for request handlers that invoke strip_html on user-controlled fields and measure their execution duration.
  • Enable Node.js event-loop lag monitoring and alert when lag exceeds a threshold (for example, 1 second).
  • Add WAF rules that flag request bodies containing high repetition counts of HTML opener substrings.

Monitoring Recommendations

  • Track per-request CPU time on routes that render Liquid templates and alert on outliers.
  • Monitor process restarts, worker timeouts, and request queue saturation in the Node.js runtime.
  • Correlate spikes in 4xx/5xx responses with single source IPs submitting unusually large payloads.

How to Mitigate CVE-2026-45617

Immediate Actions Required

  • Upgrade LiquidJS to version 10.26.0 or later, which replaces the vulnerable regex with a linear scan.
  • Audit application code for uses of the strip_html filter against untrusted input and restrict where it can be invoked.
  • Enforce strict request size limits on endpoints that render Liquid templates from user input.

Patch Information

The fix is included in LiquidJS 10.26.0. See the GitHub Release v10.26.0 notes and the GitHub Security Advisory GHSA-r7g9-xpmj-5fcq for full details. The patch commit is referenced in the GitHub Commit 3616a744.

Workarounds

  • Avoid passing untrusted input through the strip_html filter until the upgrade is applied.
  • Pre-sanitize input with an HTML parser such as parse5 or sanitize-html before it reaches Liquid templates.
  • Place a reverse proxy or WAF rule in front of the application to reject payloads with anomalously high counts of <script, <style, or <!-- tokens.
  • Set explicit per-request timeouts on Node.js worker handlers to bound the impact of CPU exhaustion.
bash
# Upgrade LiquidJS to the patched release
npm install liquidjs@^10.26.0

# Verify the installed version
npm ls liquidjs

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.