Skip to content

fix: parent context traversal inside custom block helpers in each (issue #539)#619

Merged
rexm merged 2 commits into
masterfrom
worktree-agent-a719fc181ff85be4a
Jun 20, 2026
Merged

fix: parent context traversal inside custom block helpers in each (issue #539)#619
rexm merged 2 commits into
masterfrom
worktree-agent-a719fc181ff85be4a

Conversation

@rexm

@rexm rexm commented Jun 20, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #539

  • ../ path traversal now correctly resolves to the parent of #each when used inside a custom block helper's template body.
  • The bug was in BlockHelperOptions.Template(EncodedTextWriter, Context) and BlockHelperOptions.Inverse(EncodedTextWriter, Context): when called with a Context struct, they unwrapped context.Value and created a new child frame via Frame.CreateFrame(context.Value), introducing an extra level in the parent chain.
  • Fix: pass Frame directly to the original template/inverse delegate, since Frame is already the correct current binding context (the Context struct was always created from Frame).

Root cause

In BlockHelperFunctionBinder, the BlockHelperOptions is constructed with Frame = bindingContext (the #each iteration context). The Context struct passed to the helper is new Context(bindingContext, bindingContext.Value). When the user calls options.Template(writer, context) (passing that same Context back), the old code did Frame.CreateFrame(context.Value), which created a new child with Frame as its parent — so ../ inside the template resolved to Frame.Value (the loop element) instead of Frame.ParentContext.Value (the root data object).

Test plan

  • Added regression test Issue539_ParentContextInsideCustomBlockHelperInEach to IssueTests.cs — confirmed it fails before the fix and passes after.
  • All 1747 existing tests pass with the fix applied.

🤖 Generated with Claude Code

rexm and others added 2 commits June 19, 2026 20:54
…sue #539)

When options.Template/Inverse was called with a Context struct argument, it
unwrapped context.Value and created a new child frame, adding an extra level
to the parent chain. This caused ../ inside the block helper's template to
resolve to the current iteration item rather than its parent.

Fix: pass Frame directly when Template/Inverse receives a Context struct, since
Frame already represents the correct current binding context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@rexm rexm enabled auto-merge June 20, 2026 16:09
@sonarqubecloud

Copy link
Copy Markdown

@rexm rexm merged commit 51beaeb into master Jun 20, 2026
7 checks passed
@rexm rexm deleted the worktree-agent-a719fc181ff85be4a branch June 20, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parent context in an ifCond in a each is wrong

1 participant