fix(security): charge pop filter allocation to memoryLimit (CWE-770)

The `pop` array filter cloned the input via `[...toArray(v)]` without
charging `this.context.memoryLimit.use(...)`, bypassing the memoryLimit
DoS guard that its sibling filters (shift, unshift, compact, etc.) apply.
Mirror `shift` to account for the O(N) allocation.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Yang Jun
2026-06-14 00:14:00 +08:00
co-authored by Cursor
parent ed15a52c26
commit 8734b5fc6e
2 changed files with 9 additions and 2 deletions
+4 -2
View File
@@ -88,8 +88,10 @@ export function unshift<T> (this: FilterImpl, v: T[], arg: T): T[] {
return clone
}
export function pop<T> (v: T[]): T[] {
const clone = [...toArray(v)]
export function pop<T> (this: FilterImpl, v: T[]): T[] {
const array = toArray(v)
this.context.memoryLimit.use(array.length)
const clone = [...array]
clone.pop()
return clone
}
+5
View File
@@ -79,6 +79,11 @@ describe('DoS related', function () {
await expect(liquid.parseAndRender(src, { array, count: 3 })).resolves.toBe('a a a a a a a a')
await expect(liquid.parseAndRender(src, { array, count: 100 })).rejects.toThrow('memory alloc limit exceeded, line:1, col:26')
})
it('should charge pop allocation to memoryLimit', async () => {
const array = Array(1e3).fill(0)
const liquid = new Liquid({ memoryLimit: 100 })
await expect(liquid.parseAndRender('{{ array | pop | size }}', { array })).rejects.toThrow('memory alloc limit exceeded')
})
it('should charge strip_html input length to memoryLimit', () => {
const liquid = new Liquid({ memoryLimit: 100 })
expect(() => liquid.parseAndRenderSync('{{ s | strip_html }}', { s: 'a'.repeat(200) }))