CVE-2026-44646: Liquidjs True` Vulnerability
MODERATECVE-2026-44646 is a MODERATE (CVSS 5.3) true` vulnerability (CWE-693: Protection Mechanism Failure) affecting Liquidjs libraries in the npm ecosystem. The issue involves true` via `context.spawn()`. Remediation is available in the latest vendor maintenance releases.
CVE-2026-44646 at a Glance
Security Impact
CVSS: 5.3
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:L/I:N/A:N).
How to Fix CVE-2026-44646
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-44646, 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 & Technical Analysis
This vulnerability is classified under CWE-693: Protection Mechanism Failure, where the product does not use or incorrectly uses a protection mechanism that provides sufficient defense against directed attacks against the product. In Liquidjs, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-44646 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-44646?
Which Liquidjs versions are affected?
What version fixes CVE-2026-44646?
Is liquidjs affected?
What is the CVSS score of CVE-2026-44646?
What CWE is associated with CVE-2026-44646?
How can I check npm dependencies for CVE-2026-44646?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-9x9p-qf8f-mvjg",
"modified" : "2026-07-09T21:06:26Z",
"published" : "2026-05-27T00:28:06Z",
"aliases" : [ "CVE-2026-44646" ],
"summary" : "LiquidJS's `{% render %}` tag silently bypasses per-render `ownPropertyOnly:true` via `Context.spawn()`",
"details" : "## Summary\n\n`Context.spawn()` in liquidjs creates a child `Context` for the `{% render %}` tag but does not propagate the parent context's resolved `ownPropertyOnly` value. The new context re-derives `ownPropertyOnly` from `opts.ownPropertyOnly` (the instance-level option), silently discarding any `RenderOptions.ownPropertyOnly` override that was supplied to `parseAndRender()`. As a result, a developer who runs a Liquid instance with the backwards-compatible `ownPropertyOnly:false` and then locks down an untrusted render with `parseAndRender(..., { ownPropertyOnly: true })` still leaks prototype-chain properties from inside any `{% render %}` partial. This is a distinct exploit surface from the previously identified array-filter variants (`where`, `reject`, `group_by`, `find`, `find_index`, `has`) — the underlying root cause in `Context.spawn()` is shared, but `{% render %}` is a separately reachable sink that needs no filter usage.\n\n## Details\n\nThe bug is in `Context.spawn()`:\n\n```ts\n// src/context/context.ts:105-114\npublic spawn (scope = {}) {\n return new Context(scope, this.opts, {\n sync: this.sync,\n globals: this.globals,\n strictVariables: this.strictVariables\n // <-- ownPropertyOnly is missing here\n }, {\n renderLimit: this.renderLimit,\n memoryLimit: this.memoryLimit\n })\n}\n```\n\nThe constructor resolves `ownPropertyOnly` as:\n\n```ts\n// src/context/context.ts:47\nthis.ownPropertyOnly = renderOptions.ownPropertyOnly ?? opts.ownPropertyOnly\n```\n\nBecause `spawn()` passes a `RenderOptions` object with no `ownPropertyOnly`, the child context falls back to `opts.ownPropertyOnly` (the instance-level option), throwing away any per-render override that the parent context had applied. `this.opts` is the raw normalized instance options object; it is not mutated to reflect render-time overrides.\n\nThe `{% render %}` tag at `src/tags/render.ts:51-77` calls `spawn()` to build the partial's isolated scope:\n\n```ts\n* render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {\n const { liquid, hash } = this\n const filepath = (yield renderFilePath(this['file'], ctx, liquid)) as string\n assert(filepath, () => `illegal file path \"${filepath}\"`)\n\n const childCtx = ctx.spawn() // <-- ownPropertyOnly lost here\n const scope = childCtx.bottom()\n __assign(scope, yield hash.render(ctx))\n ...\n const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile'])) as Template[]\n yield liquid.renderer.renderTemplates(templates, childCtx, emitter)\n}\n```\n\nAll template variable lookups inside the partial then go through `childCtx.readProperty()` (`src/context/context.ts:123-135`), which calls `readJSProperty(obj, key, this.ownPropertyOnly)`. With `childCtx.ownPropertyOnly === false` (inherited from `opts`), the protective check at `src/context/context.ts:138-141` is skipped and prototype-chain properties are returned to the template:\n\n```ts\nexport function readJSProperty (obj: Scope, key: PropertyKey, ownPropertyOnly: boolean) {\n if (ownPropertyOnly && !hasOwnProperty.call(obj, key) && !(obj instanceof Drop)) return undefined\n return obj[key]\n}\n```\n\nThe `{% include %}` tag is **not** affected: it does not call `spawn()`; it pushes onto the parent context's scope stack (`src/tags/include.ts:40`), so the parent's resolved `ownPropertyOnly` continues to apply.\n\nTrust model / why this matters: `RenderOptions.ownPropertyOnly` is documented (`src/liquid-options.ts:108-111`) as \"Same as `ownPropertyOnly` on LiquidOptions, but only for current `render()` call\". It exists precisely so that developers running a non-strict instance can lock down individual untrusted renders. That contract is broken — the override is silently dropped at every partial boundary.\n\n## PoC\n\n```bash\nmkdir -p /tmp/render-poc\nprintf '{{ user.passwordHash }}' > /tmp/render-poc/_user.liquid\n\nnode -e \"\nconst { Liquid } = require('./dist/liquid.node.js');\nconst liquid = new Liquid({ ownPropertyOnly: false, root: '/tmp/render-poc' });\n\nclass User { constructor(n){ this.name = n; } }\nUser.prototype.passwordHash = 'bcrypt\\$secret';\nconst u = new User('alice');\n\nliquid.parseAndRender(\n 'Direct:[{{ user.passwordHash }}] Render:[{% render \\\"_user.liquid\\\", user: user %}]',\n { user: u },\n { ownPropertyOnly: true }\n).then(console.log);\n\"\n```\n\nVerified output on liquidjs 10.25.7:\n\n```\nDirect:[] Render:[bcrypt$secret]\n```\n\nThe top-level expression `{{ user.passwordHash }}` is correctly blocked by the per-render `ownPropertyOnly:true`, but the same expression inside the partial loaded by `{% render %}` returns the prototype-chain property — proof that `Context.spawn()` discarded the override.\n\n## Impact\n\n- **Information disclosure**: Any prototype-chain property of objects passed into a `{% render %}` partial — including secrets, hashes, internal state, framework-injected helpers — becomes readable from inside the partial template, even when the developer used the documented per-render lockdown.\n- **Realistic threat model**: Applications that maintain `ownPropertyOnly:false` for backwards compatibility (or because their data layer relies on prototype methods) and lock down untrusted-template renders with `parseAndRender(..., { ownPropertyOnly:true })` are protected at the top level but silently exposed inside any partial. User-controllable template content (CMS snippets, theme partials, email templates) that uses `{% render %}` becomes an info-leak primitive.\n- **Distinct from existing CVE-2022-25948**: the prior advisory only covered direct use of `ownPropertyOnly:false`; this is a failure of the documented mitigation (`ownPropertyOnly:true` per-render override), not a missing setting.\n- **Distinct from the array-filter variant**: same `spawn()` root cause, but exploitable without invoking `where/reject/group_by/find/find_index/has` — only requires that the template uses `{% render %}` (a basic templating feature) and that one of the rendered values has prototype-chain properties.\n\n## Recommended Fix\n\nPropagate `ownPropertyOnly` (and any other security-relevant render options) inside `Context.spawn()`:\n\n```ts\n// src/context/context.ts\npublic spawn (scope = {}) {\n return new Context(scope, this.opts, {\n sync: this.sync,\n globals: this.globals,\n strictVariables: this.strictVariables,\n ownPropertyOnly: this.ownPropertyOnly // <-- propagate resolved per-render value\n }, {\n renderLimit: this.renderLimit,\n memoryLimit: this.memoryLimit\n })\n}\n```\n\nPassing `this.ownPropertyOnly` (the resolved value, not `this.opts.ownPropertyOnly`) ensures any `RenderOptions.ownPropertyOnly` override flows into spawned child contexts. This single change closes both the `{% render %}` pathway documented here and the array-filter pathway tracked separately. A regression test should assert that a partial rendered via `{% render %}` honours `parseAndRender(..., { ownPropertyOnly: true })` against an object with prototype-chain properties.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N"
} ],
"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-9x9p-qf8f-mvjg"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-44646"
}, {
"type" : "WEB",
"url" : "https://github.com/harttle/liquidjs/commit/dbbf6288030591bf6da28d8c1cce5a17bca97bb6"
}, {
"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-693" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-05-27T00:28:06Z",
"nvd_published_at" : "2026-06-17T23:17:03Z"
}
}