test: assert null-prototype registries vs all Object.prototype keys

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Yang Jun
2026-05-12 01:03:40 +08:00
co-authored by Cursor
parent 0078a0d01f
commit f4bfd03b49
3 changed files with 36 additions and 32 deletions
+8 -7
View File
@@ -77,15 +77,16 @@ describe('Issues', function () {
)
expect(html).toBe('BAR')
})
it('filter/tag lookup must not inherit Object.prototype (node + UMD)', async () => {
const tpl = '{% assign r = 1 | valueOf %}{{ r.context }}{{ r.liquid }}{{ r.token }}|{{ r }}'
it('filter/tag maps are null-prototype; built-ins work (node + UMD)', async () => {
const tpl = `{{ 'a' | append: 'b' }}`
const nodeEngine = new Liquid()
expect(await nodeEngine.parseAndRender(tpl)).toBe('|1')
expect(() => nodeEngine.parse('{% constructor %}')).toThrow('tag "constructor" not found')
const umdEngine = new LiquidUMD()
expect(await umdEngine.parseAndRender(tpl)).toBe('|1')
expect(() => umdEngine.parse('{% constructor %}')).toThrow('tag "constructor" not found')
expect(Object.getPrototypeOf(nodeEngine.filters)).toBeNull()
expect(Object.getPrototypeOf(nodeEngine.tags)).toBeNull()
expect(Object.getPrototypeOf(umdEngine.filters)).toBeNull()
expect(Object.getPrototypeOf(umdEngine.tags)).toBeNull()
expect(await nodeEngine.parseAndRender(tpl)).toBe('ab')
expect(await umdEngine.parseAndRender(tpl)).toBe('ab')
})
it('lenientIf not working as expected in umd #313', async () => {
const engine = new LiquidUMD({
@@ -61,29 +61,28 @@ describe('liquid#registerFilter()', function () {
})
})
describe('filter name must not inherit from Object.prototype', () => {
it('should treat valueOf as unregistered (no FilterImpl leak)', async () => {
const out = await liquid.parseAndRender(
'{% assign r = 1 | valueOf %}{{ r.liquid.options.fs.sep }}|{{ r }}'
)
expect(out).toBe('|1')
describe('filter registry storage', () => {
it('should use a null-prototype map for filters', () => {
expect(Object.getPrototypeOf(liquid.filters)).toBeNull()
})
it('should not expose context, liquid, or token via valueOf', async () => {
const out = await liquid.parseAndRender(
'{% assign r = 1 | valueOf %}{{ r.context }}/{{ r.liquid }}/{{ r.token }}'
)
expect(out).toBe('//')
it('should still resolve built-in filters', async () => {
expect(await liquid.parseAndRender(`{{ 'a' | append: 'b' }}`)).toBe('ab')
})
it.each(['toString', 'constructor', 'hasOwnProperty', 'isPrototypeOf', '__proto__', '__defineGetter__'])(
'should treat %s as unregistered filter',
async (name) => {
const out = await liquid.parseAndRender(`{{ "x" | ${name} }}`)
expect(out).toBe('x')
it('should not resolve names that exist only on Object.prototype', async () => {
const registered = new Set(Object.keys(liquid.filters))
for (const name of Object.getOwnPropertyNames(Object.prototype)) {
if (registered.has(name)) continue
const out = await liquid.parseAndRender(`{{ x | ${name} }}`, { x: 42 })
expect(out).toBe('42')
}
)
it('should throw under strictFilters for valueOf', async () => {
})
it('should reject unknown filter names under strictFilters, including Object.prototype keys', async () => {
const strict = new Liquid({ strictFilters: true })
await expect(strict.parseAndRender('{{ 1 | valueOf }}')).rejects.toThrow('undefined filter: valueOf')
const registered = new Set(Object.keys(strict.filters))
for (const name of Object.getOwnPropertyNames(Object.prototype)) {
if (registered.has(name)) continue
await expect(strict.parseAndRender(`{{ 1 | ${name} }}`)).rejects.toThrow('undefined filter')
}
})
})
})
+10 -6
View File
@@ -39,13 +39,17 @@ describe('liquid#registerTag()', function () {
return expect(html).toBe('ABC')
})
describe('tag name must not inherit from Object.prototype', () => {
it.each(['constructor', 'toString', 'valueOf', 'hasOwnProperty', '__proto__'])(
'should report %s as unknown tag',
(name) => {
const l = new Liquid()
describe('tag registry storage', () => {
it('should use a null-prototype map for tags', () => {
expect(Object.getPrototypeOf(new Liquid().tags)).toBeNull()
})
it('should not resolve names that exist only on Object.prototype', () => {
const l = new Liquid()
const registered = new Set(Object.keys(l.tags))
for (const name of Object.getOwnPropertyNames(Object.prototype)) {
if (registered.has(name)) continue
expect(() => l.parse(`{% ${name} %}`)).toThrow(`tag "${name}" not found`)
}
)
})
})
})