GHSA-v273-448j-v4qj

Suggest an improvement
Source
https://github.com/advisories/GHSA-v273-448j-v4qj
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-v273-448j-v4qj/GHSA-v273-448j-v4qj.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-v273-448j-v4qj
Aliases
  • CVE-2026-39859
Downstream
Related
Published
2026-04-08T15:04:44Z
Modified
2026-04-10T01:29:18.999146784Z
Severity
  • 6.3 (Medium) CVSS_V4 - CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N CVSS Calculator
Summary
LiquidJS: `renderFile()` / `parseFile()` bypass configured `root` and allow arbitrary file read
Details

liquidjs 10.25.0 documents root as constraining filenames passed to renderFile() and parseFile(), but top-level file loads do not enforce that boundary.

The published npm package liquidjs@10.25.0 on Linux 6.17.0 with Node v22.22.1. A Liquid instance configured with an empty temporary directory as root still returned the contents of /etc/hosts when renderFile('/etc/hosts') was called. I have not exhaustively checked older releases yet; 10.25.0 is the latest tested version.

Root cause: - src/parser/parser.ts:83-85 calls loader.lookup(file, LookupType.Root, ...) and then reads the returned file. - src/fs/loader.ts:38 passes type !== LookupType.Root into candidates(). - For LookupType.Root, enforceRoot is false, so src/fs/loader.ts:47-66 accepts resolved absolute paths and fallback results without any contains() check.

This appears adjacent to the March 10, 2026 fix for CVE-2026-30952, which hardened include / render / layout but not the top-level file-loading APIs.

Proof of concept:

const fs = require('fs');
const os = require('os');
const path = require('path');
const { Liquid } = require('liquidjs');

const safeRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'liquidjs-safe-root-'));
const engine = new Liquid({ root: [safeRoot], extname: '.liquid' });

engine.renderFile('/etc/hosts').then(console.log);

Expected result: a path outside root should be rejected. Actual result: /etc/hosts is rendered successfully.

Impact: any application that treats root as a sandbox boundary and forwards attacker-controlled template names into renderFile() or parseFile() can disclose arbitrary local files readable by the server process.

Suggested fix: apply the same containment checks used for partial/layout lookups to LookupType.Root, and reject absolute or fallback paths unless they remain within an allowed root. A regression test should verify that renderFile('/etc/hosts') fails when root points to an unrelated directory.

Database specific
{
    "nvd_published_at": "2026-04-08T20:16:26Z",
    "github_reviewed_at": "2026-04-08T15:04:44Z",
    "cwe_ids": [
        "CWE-22"
    ],
    "github_reviewed": true,
    "severity": "MODERATE"
}
References

Affected packages

npm / liquidjs

Package

Affected ranges

Type
SEMVER
Events
Introduced
0Unknown introduced version / All previous versions are affected
Fixed
10.25.5

Database specific

last_known_affected_version_range
"<= 10.25.4"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-v273-448j-v4qj/GHSA-v273-448j-v4qj.json"