fix(security): reject render/include partial recursion

Detect cyclic partial rendering via a shared partialStack register (same pattern as CVE-2026-41311 block tag fix). Self-referential or circular {% render %} and {% include %} now throw immediately instead of hanging or OOM.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Yang Jun
2026-06-14 22:56:36 +08:00
co-authored by Cursor
parent 8a0c74a7fc
commit a7efcc8f96
6 changed files with 116 additions and 28 deletions
+17
View File
@@ -117,4 +117,21 @@ describe('.parseAndRender()', function () {
await expect(liquid.renderFile('template')).rejects.toThrow(/block tag cannot be nested/)
})
})
describe('render/include: self-referential partial regression', function () {
it('should reject self-referential {% render %} via in-memory templates (no hang / OOM)', async function () {
const liquid = new Liquid({ templates: { self: '{% render "self" %}' } })
await expect(liquid.parseAndRender('{% render "self" %}')).rejects.toThrow(/render tag cannot be nested/)
})
it('should reject self-referential {% include %} (no hang / OOM)', async function () {
let root: string
root = mkdtempSync(join(tmpdir(), 'liquid-e2e-include-nested-'))
try {
writeFileSync(join(root, 'self.html'), 'A{% include "self.html" %}B')
const liquid = new Liquid({ root, extname: '.html' })
await expect(liquid.renderFile('self')).rejects.toThrow(/include tag cannot be nested/)
} finally {
rmSync(root, { recursive: true, force: true })
}
})
})
})