From 5b9c3469085e01c79e2d0af28e2a13f730e1793d Mon Sep 17 00:00:00 2001 From: Yang Jun Date: Thu, 7 May 2026 23:03:43 +0800 Subject: [PATCH] fix: enforce renderLimit for empty renderTemplates calls (#894) renderLimit was only checked inside the per-template loop, so renderTemplates([], ...) skipped it. Empty {% for %} and {% tablerow %} bodies call that path once per iteration (tablerow still does emitter work for /), bypassing the documented time budget. Check the limiter at renderTemplates entry before the loop. Add regression tests for empty for-body and empty tablerow-body. Co-authored-by: Cursor --- src/render/render.ts | 1 + test/integration/liquid/dos.spec.ts | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/src/render/render.ts b/src/render/render.ts index 6cfc9a99a..8d0b7d806 100644 --- a/src/render/render.ts +++ b/src/render/render.ts @@ -15,6 +15,7 @@ export class Render { if (!emitter) { emitter = ctx.opts.keepOutputType ? new KeepingTypeEmitter() : new SimpleEmitter() } + ctx.renderLimit.check(getPerformance().now()) const errors = [] for (const tpl of templates) { ctx.renderLimit.check(getPerformance().now()) diff --git a/test/integration/liquid/dos.spec.ts b/test/integration/liquid/dos.spec.ts index 875d299a1..796f2db8e 100644 --- a/test/integration/liquid/dos.spec.ts +++ b/test/integration/liquid/dos.spec.ts @@ -48,6 +48,16 @@ describe('DoS related', function () { await expect(liquid.parseAndRender('{% render "large" %}')).rejects.toThrow('template render limit exceeded') await expect(liquid.parseAndRender('{% render "small" %}')).resolves.toBe('12345') }) + it('should enforce renderLimit when for body has no template nodes', () => { + const liquid = new Liquid({ memoryLimit: 1e9, renderLimit: 1 }) + expect(() => liquid.parseAndRenderSync('{%- for i in (1..5000000) -%}{%- endfor -%}', {})) + .toThrow('template render limit exceeded') + }) + it('should enforce renderLimit when tablerow body has no template nodes', () => { + const liquid = new Liquid({ memoryLimit: 1e9, renderLimit: 1 }) + expect(() => liquid.parseAndRenderSync('{%- tablerow i in (1..1000000) cols:1 -%}{%- endtablerow -%}', {})) + .toThrow('template render limit exceeded') + }) }) describe('#memoryLimit', () => { it('should throw for too many array creation in filters', async () => {