mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 20:30:39 -07:00
* fix: enforce ownPropertyOnly for inherited array indices Route array index access (including negative indices, first/last, and the first/last filters) through a shared readArrayElement helper so that ownPropertyOnly hides prototype-inherited array indices, closing the GHSA-fwxr-j5w2-587m bypass. The option's scope (property/index access only, not filter transforms or iteration) is documented on the option. Co-authored-by: Cursor <[email protected]> * fix(filters): invoke Array.prototype methods on unsanitized array values Call built-ins via Array.prototype.<m>.call(...) for values that come from scope (join, compact, concat, slice, where/reject) so an overridden instance method on unsanitized data cannot hijack filter behavior. Methods on freshly-created arrays are left as-is. Co-authored-by: Cursor <[email protected]> * fix(filters): use String.prototype.slice for the string branch of slice Route the non-array branch through String.prototype.slice.call so the slice filter never dispatches through a possibly-overridden instance method, matching the Array.prototype guard. Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Cursor <[email protected]>
55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
import { Liquid } from '../../../src/liquid'
|
|
|
|
describe('ownPropertyOnly / inherited array indices', function () {
|
|
const engine = new Liquid({ ownPropertyOnly: true })
|
|
|
|
function pollutedArrays () {
|
|
// eslint-disable-next-line no-extend-native
|
|
Array.prototype[0] = 'ARRAY_PROTO_POLLUTED'
|
|
;(Object.prototype as any).secret = 'OBJECT_PROTO_POLLUTED'
|
|
const a: any[] = []
|
|
a.length = 1
|
|
const o = {}
|
|
return {
|
|
a,
|
|
o,
|
|
cleanup () {
|
|
delete (Array.prototype as any)[0]
|
|
delete (Object.prototype as any).secret
|
|
}
|
|
}
|
|
}
|
|
|
|
const cases: [string, (ctx: ReturnType<typeof pollutedArrays>) => object, string][] = [
|
|
['{{ a[0] }}', ({ a }) => ({ a }), ''],
|
|
['{{ a[-1] }}', ({ a }) => ({ a }), ''],
|
|
['{{ o.secret }}', ({ o }) => ({ o }), ''],
|
|
['{{ a.first }}', ({ a }) => ({ a }), ''],
|
|
['{{ a.last }}', ({ a }) => ({ a }), ''],
|
|
['{{ a | first }}', ({ a }) => ({ a }), ''],
|
|
['{{ a | last }}', ({ a }) => ({ a }), ''],
|
|
['{% assign x = a | first %}{{ x }}', ({ a }) => ({ a }), '']
|
|
]
|
|
|
|
it.each(cases)('%s', function (src, scopeFn, expected) {
|
|
const ctx = pollutedArrays()
|
|
try {
|
|
expect(engine.parseAndRenderSync(src, scopeFn(ctx))).toBe(expected)
|
|
} finally {
|
|
ctx.cleanup()
|
|
}
|
|
})
|
|
|
|
it('still allows array length and size', function () {
|
|
const { a, cleanup } = pollutedArrays()
|
|
try {
|
|
expect(engine.parseAndRenderSync('{{ a.size }}', { a })).toBe('1')
|
|
const arr = [1, 2]
|
|
expect(engine.parseAndRenderSync('{{ arr | first }}', { arr })).toBe('1')
|
|
expect(engine.parseAndRenderSync('{{ arr[-1] }}', { arr })).toBe('2')
|
|
} finally {
|
|
cleanup()
|
|
}
|
|
})
|
|
})
|