GHSA-wmfp-5q7x-987x
liquidjs has a path traversal fallback vulnerability
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Blast Radius
Weekly download volume for affected packages — a proxy for how broadly this vulnerability is deployed.
liquidjsnpmDescription
Impact
The 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.
Patches
The 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 and published version 10.25.0 on npm.
Workarounds
Change the files in build time
In 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/,
if (fs.fallback !== undefined) {
const filepath = fs.fallback(file)
- if (filepath !== undefined) yield filepath
+ if (filepath !== undefined) {
+ for (const dir of dirs) {
+ if (!enforceRoot || this.contains(dir, filepath)) {
+ yield filepath
+ break
+ }
+ }
}
}
Overriding by fs LiquidJS option
Adding a fs option to override the default fs implementation:
const { statSync, readFileSync, promises: { stat, readFile } } = require('fs')
const { resolve, extname, dirname, sep } = require('path')
const fs = {
exists: async (fp) => { try { await stat(fp); return true; } catch { return false } },
existsSync: (fp) => { try { statSync(fp); return true } catch { return false } },
resolve: (root, file, ext) => resolve(root, file + (extname(file) ? '' : ext)),
contains: (root, file) => {
const r = resolve(root)
return file.startsWith(r.endsWith(sep) ? r : r + sep)
},
readFile: (fp) => readFile(fp, 'utf8'),
readFileSync: (fp) => readFileSync(fp, 'utf8'),
fallback: () => undefined,
dirname,
sep
};
const engine = new Liquid({ fs })
References
Discussions: https://github.com/harttle/liquidjs/pull/851 Code fix: https://github.com/harttle/liquidjs/pull/855
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | liquidjs | all versions | 10.25.0 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for liquidjs. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update liquidjs to 10.25.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-wmfp-5q7x-987x is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-wmfp-5q7x-987x is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-wmfp-5q7x-987x. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-wmfp-5q7x-987x in your dependencies?
O3 detects GHSA-wmfp-5q7x-987x across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.