Skip to content

Commit dbbf628

Browse files
harttlecursoragent
andauthored
fix: propagate ownPropertyOnly into Context.spawn() for {% render %} (#893)
Child contexts from spawn() re-derived ownPropertyOnly from Liquid opts only, dropping per-render RenderOptions overrides. That broke the contract that parseAndRender(..., { ownPropertyOnly: true }) locks down a single render, including partials loaded via {% render %}. Add regression test matching prototype-chain leak PoC. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 26ea285 commit dbbf628

2 files changed

Lines changed: 23 additions & 1 deletion

File tree

src/context/context.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ export class Context {
106106
return new Context(scope, this.opts, {
107107
sync: this.sync,
108108
globals: this.globals,
109-
strictVariables: this.strictVariables
109+
strictVariables: this.strictVariables,
110+
ownPropertyOnly: this.ownPropertyOnly
110111
}, {
111112
renderLimit: this.renderLimit,
112113
memoryLimit: this.memoryLimit

test/integration/tags/render.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,27 @@ describe('tags/render', function () {
271271
return expect(staticLiquid.renderFile('parent.html')).rejects.toThrow(/Failed to lookup "..\/bar\/child.html"/)
272272
})
273273

274+
describe('per-render ownPropertyOnly', function () {
275+
it('should propagate to {% render %} partial (spawned context)', async function () {
276+
mock({
277+
'/_user.liquid': '{{ user.passwordHash }}'
278+
})
279+
const engine = new Liquid({ ownPropertyOnly: false, root: '/' })
280+
class User {
281+
name: string
282+
constructor (n: string) {
283+
this.name = n
284+
}
285+
}
286+
Object.assign(User.prototype, { passwordHash: 'secret-from-prototype' })
287+
const u = new User('alice')
288+
const tpl = 'Direct:[{{ user.passwordHash }}] Render:[{% render "_user.liquid", user: user %}]'
289+
const html = await engine.parseAndRender(tpl, { user: u }, { ownPropertyOnly: true })
290+
expect(html).toBe('Direct:[] Render:[]')
291+
expect(engine.parseAndRenderSync(tpl, { user: u }, { ownPropertyOnly: true })).toBe('Direct:[] Render:[]')
292+
})
293+
})
294+
274295
describe('static partial', function () {
275296
let staticLiquid: Liquid
276297
beforeEach(() => {

0 commit comments

Comments
 (0)