mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-26 13:45:15 -07:00
fix(security): allow partial recursion when renderLimit is finite
Only reject render/include cycles when renderLimit is unlimited (default Infinity). With a finite time budget, recursion is bounded by renderLimit checks in renderTemplates. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
+3
-1
@@ -181,7 +181,9 @@ export function * renderFilePath (file: ParsedFileName, ctx: Context, liquid: Li
|
|||||||
|
|
||||||
export function pushPartialStack (ctx: Context, filepath: string, tag: 'render' | 'include') {
|
export function pushPartialStack (ctx: Context, filepath: string, tag: 'render' | 'include') {
|
||||||
const stack: string[] = ctx.getRegister('partialStack', [])
|
const stack: string[] = ctx.getRegister('partialStack', [])
|
||||||
if (stack.includes(filepath)) throw new Error(`${tag} tag cannot be nested`)
|
if (ctx.renderLimit.isUnlimited() && stack.includes(filepath)) {
|
||||||
|
throw new Error(`${tag} tag cannot be nested`)
|
||||||
|
}
|
||||||
stack.push(filepath)
|
stack.push(filepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,4 +19,7 @@ export class Limiter {
|
|||||||
assert(+count <= this.limit, this.message)
|
assert(+count <= this.limit, this.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
isUnlimited () {
|
||||||
|
return !Number.isFinite(this.limit)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,5 +133,9 @@ describe('.parseAndRender()', function () {
|
|||||||
rmSync(root, { recursive: true, force: true })
|
rmSync(root, { recursive: true, force: true })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
it('should allow self-referential {% render %} when renderLimit is finite', async function () {
|
||||||
|
const liquid = new Liquid({ templates: { self: '{% render "self" %}' }, renderLimit: 0.01 })
|
||||||
|
await expect(liquid.parseAndRender('{% render "self" %}')).rejects.toThrow(/template render limit exceeded/)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -311,6 +311,13 @@ describe('tags/include', function () {
|
|||||||
})
|
})
|
||||||
return expect(liquid.renderFile('/a.html')).rejects.toThrow(/include tag cannot be nested/)
|
return expect(liquid.renderFile('/a.html')).rejects.toThrow(/include tag cannot be nested/)
|
||||||
})
|
})
|
||||||
|
it('should allow self-referential {% include %} when renderLimit is finite', function () {
|
||||||
|
mock({
|
||||||
|
'/self.html': 'A{% include "self.html" %}B'
|
||||||
|
})
|
||||||
|
const limited = new Liquid({ root: '/', renderLimit: 0.01 })
|
||||||
|
return expect(limited.renderFile('/self.html')).rejects.toThrow(/template render limit exceeded/)
|
||||||
|
})
|
||||||
it('should allow legitimate nested {% include %} chain', async function () {
|
it('should allow legitimate nested {% include %} chain', async function () {
|
||||||
mock({
|
mock({
|
||||||
'/a.html': 'A{% include "b.html" %}',
|
'/a.html': 'A{% include "b.html" %}',
|
||||||
|
|||||||
@@ -407,6 +407,10 @@ describe('tags/render', function () {
|
|||||||
})
|
})
|
||||||
await expect(liquid.renderFile('/a.html')).rejects.toThrow(/render tag cannot be nested/)
|
await expect(liquid.renderFile('/a.html')).rejects.toThrow(/render tag cannot be nested/)
|
||||||
})
|
})
|
||||||
|
it('should allow self-referential {% render %} when renderLimit is finite', async function () {
|
||||||
|
const liquid = new Liquid({ templates: { self: '{% render "self" %}' }, renderLimit: 0.01 })
|
||||||
|
await expect(liquid.parseAndRender('{% render "self" %}')).rejects.toThrow(/template render limit exceeded/)
|
||||||
|
})
|
||||||
it('should allow legitimate nested {% render %} chain', async function () {
|
it('should allow legitimate nested {% render %} chain', async function () {
|
||||||
mock({
|
mock({
|
||||||
'/a.html': 'A{% render "b.html" %}',
|
'/a.html': 'A{% render "b.html" %}',
|
||||||
|
|||||||
Reference in New Issue
Block a user