<?xml version='1.0' encoding='UTF-8'?>
<?xml-stylesheet href="/static/style.xsl" type="text/xsl"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title>Most recent entries from all</title>
    <link>https://vulnerability.circl.lu</link>
    <description>Contains only the most 10 recent entries.</description>
    <docs>http://www.rssboard.org/rss-specification</docs>
    <generator>python-feedgen</generator>
    <language>en</language>
    <lastBuildDate>Tue, 06 Oct 2026 00:41:53 +0000</lastBuildDate>
    <item>
      <title>GHSA-56p5-8mhr-2fph — LiquidJS: Root restriction bypass for partial and layout loading through symlinked templates</title>
      <link>https://vulnerability.circl.lu/vuln/ghsa-56p5-8mhr-2fph</link>
      <description>&lt;p&gt;&lt;strong&gt;Affected:&lt;/strong&gt; npm: liquidjs&lt;/p&gt;
&lt;p&gt;### Summary&lt;/p&gt;
&lt;p&gt;LiquidJS enforces partial and layout root restrictions using the resolved pathname string, but it does not resolve the canonical filesystem path before opening the file. A symlink placed inside an allowed partials or layouts directory can therefore point to a file outside that directory and still be loaded.&lt;/p&gt;
&lt;p&gt;### Details&lt;/p&gt;
&lt;p&gt;For `{% include %}`, `{% render %}`, and `{% layout %}`, LiquidJS checks whether the candidate path is inside the configured partials or layouts roots before reading it. That check is path-based, not realpath-based.&lt;/p&gt;
&lt;p&gt;Because of that, a file like `partials/link.liquid` passes the directory containment check as long as its pathname is under the allowed root. If `link.liquid` is actually a symlink to a file outside the allowed root, the filesystem follows the symlink when the file is opened and LiquidJS renders the external target.&lt;/p&gt;
&lt;p&gt;So the restriction is applied to the path string that was requested, not to the file that is actually read.&lt;/p&gt;
&lt;p&gt;This matters in environments where an attacker can place templates or otherwise influence files under a trusted template root, including uploaded themes, extracted archives, mounted content, or repository-controlled template trees.&lt;/p&gt;
&lt;p&gt;### PoC&lt;/p&gt;
&lt;p&gt;```js
const { Liquid } = require(&amp;#39;liquidjs&amp;#39;);
const fs = require(&amp;#39;fs&amp;#39;);&lt;/p&gt;
&lt;p&gt;fs.rmSync(&amp;#39;/tmp/liquid-root&amp;#39;, { recursive: true, force: true });
fs.mkdirSync(&amp;#39;/tmp/liquid-root&amp;#39;, { recursive: true });&lt;/p&gt;
&lt;p&gt;fs.writeFileSync(&amp;#39;/tmp/secret-outside.liquid&amp;#39;, &amp;#39;SECRET_OUTSIDE&amp;#39;);
fs.symlinkSync(&amp;#39;…&lt;/p&gt;</description>
      <content:encoded>&lt;p&gt;&lt;strong&gt;Affected:&lt;/strong&gt; npm: liquidjs&lt;/p&gt;
&lt;p&gt;### Summary&lt;/p&gt;
&lt;p&gt;LiquidJS enforces partial and layout root restrictions using the resolved pathname string, but it does not resolve the canonical filesystem path before opening the file. A symlink placed inside an allowed partials or layouts directory can therefore point to a file outside that directory and still be loaded.&lt;/p&gt;
&lt;p&gt;### Details&lt;/p&gt;
&lt;p&gt;For `{% include %}`, `{% render %}`, and `{% layout %}`, LiquidJS checks whether the candidate path is inside the configured partials or layouts roots before reading it. That check is path-based, not realpath-based.&lt;/p&gt;
&lt;p&gt;Because of that, a file like `partials/link.liquid` passes the directory containment check as long as its pathname is under the allowed root. If `link.liquid` is actually a symlink to a file outside the allowed root, the filesystem follows the symlink when the file is opened and LiquidJS renders the external target.&lt;/p&gt;
&lt;p&gt;So the restriction is applied to the path string that was requested, not to the file that is actually read.&lt;/p&gt;
&lt;p&gt;This matters in environments where an attacker can place templates or otherwise influence files under a trusted template root, including uploaded themes, extracted archives, mounted content, or repository-controlled template trees.&lt;/p&gt;
&lt;p&gt;### PoC&lt;/p&gt;
&lt;p&gt;```js
const { Liquid } = require(&amp;#39;liquidjs&amp;#39;);
const fs = require(&amp;#39;fs&amp;#39;);&lt;/p&gt;
&lt;p&gt;fs.rmSync(&amp;#39;/tmp/liquid-root&amp;#39;, { recursive: true, force: true });
fs.mkdirSync(&amp;#39;/tmp/liquid-root&amp;#39;, { recursive: true });&lt;/p&gt;
&lt;p&gt;fs.writeFileSync(&amp;#39;/tmp/secret-outside.liquid&amp;#39;, &amp;#39;SECRET_OUTSIDE&amp;#39;);
fs.symlinkSync(&amp;#39;…&lt;/p&gt;</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/ghsa-56p5-8mhr-2fph</guid>
    </item>
  </channel>
</rss>
