{"id":"GHSA-wmfp-5q7x-987x","summary":"liquidjs has a path traversal fallback vulnerability","details":"### Impact\nThe `layout`, `render`, and `include` tags allow arbitrary file access via absolute paths (either as string literals or through Liquid variables, the latter require `dynamicPartials: true`, which is the default). This poses a security risk when malicious users are allowed to control the template content or specify the filepath to be included as a Liquid variable.\n\n### Patches\nThe root cause is LiquidJS allows `require.resolve()` as fallback but doesn't limit the directories it can resolve to. The issue is fixed via [#855](https://github.com/harttle/liquidjs/pull/855) and published version 10.25.0 on npm.\n\n### Workarounds\n#### Change the files in build time\nIn build time, through Shell script or Webpack `string-replace-loader`, change the file content of correxponding file (depending on your package `type`, for CommonJS it's `dist/liquid.node.js`) under `dist/`, \n\n```diff\n  if (fs.fallback !== undefined) {\n    const filepath = fs.fallback(file)\n-   if (filepath !== undefined) yield filepath\n+   if (filepath !== undefined) {\n+     for (const dir of dirs) {\n+       if (!enforceRoot || this.contains(dir, filepath)) {\n+         yield filepath\n+         break\n+       }\n+     }\n    }\n  }\n```\n\n#### Overriding by `fs` LiquidJS option\nAdding a [`fs` option](https://liquidjs.com/api/interfaces/FS.html) to override the [default `fs` implementation](https://github.com/harttle/liquidjs/blob/1b85fdaa9c535021f7030a239a64003af26d31b5/src/fs/fs-impl.ts#L36-L40):\n\n```javascript\nconst { statSync, readFileSync, promises: { stat, readFile } } = require('fs')\nconst { resolve, extname, dirname, sep } = require('path')\n\nconst fs = {\n    exists: async (fp) =\u003e { try { await stat(fp); return true; } catch { return false } },\n    existsSync: (fp) =\u003e { try { statSync(fp); return true } catch { return false } },\n    resolve: (root, file, ext) =\u003e resolve(root, file + (extname(file) ? '' : ext)),\n    contains: (root, file) =\u003e {\n        const r = resolve(root)\n        return file.startsWith(r.endsWith(sep) ? r : r + sep)\n    },\n    readFile: (fp) =\u003e readFile(fp, 'utf8'),\n    readFileSync: (fp) =\u003e readFileSync(fp, 'utf8'),\n    fallback: () =\u003e undefined,\n    dirname,\n    sep\n};\n\nconst engine = new Liquid({ fs })\n```\n\n### References\nDiscussions: https://github.com/harttle/liquidjs/pull/851\nCode fix: https://github.com/harttle/liquidjs/pull/855","aliases":["CVE-2026-30952"],"modified":"2026-09-10T03:50:41.571935931Z","published":"2026-03-10T01:04:34Z","database_specific":{"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-03-10T01:04:34Z","nvd_published_at":"2026-03-10T21:16:48Z","cwe_ids":["CWE-22"]},"references":[{"type":"WEB","url":"https://github.com/harttle/liquidjs/security/advisories/GHSA-wmfp-5q7x-987x"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-30952"},{"type":"WEB","url":"https://github.com/harttle/liquidjs/pull/851"},{"type":"WEB","url":"https://github.com/harttle/liquidjs/pull/855"},{"type":"WEB","url":"https://github.com/harttle/liquidjs/commit/3cd024d652dc883c46307581e979fe32302adbac"},{"type":"PACKAGE","url":"https://github.com/harttle/liquidjs"}],"affected":[{"package":{"name":"liquidjs","ecosystem":"npm","purl":"pkg:npm/liquidjs"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"10.25.0"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-wmfp-5q7x-987x/GHSA-wmfp-5q7x-987x.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N"}]}