Skip to content

test: spec tests for inline tag hardening (#931, #932)#933

Merged
zeroedin merged 2 commits into
feature/inline-tag-hardeningfrom
feature/inline-tag-hardening-spec
Jul 11, 2026
Merged

test: spec tests for inline tag hardening (#931, #932)#933
zeroedin merged 2 commits into
feature/inline-tag-hardeningfrom
feature/inline-tag-hardening-spec

Conversation

@zeroedin

Copy link
Copy Markdown
Owner

Summary

Test summary

# Test Status Discriminator
1 returns error when _contentRoot is not set (#932) 🔴 RED Render() succeeds without error — sandbox silently skipped
2 matches file extensions case-insensitively (#931) ✅ GREEN strings.ToLower already normalizes .SVG.svg
3 rejects binary file type with mixed-case extension (#931) ✅ GREEN strings.ToLower normalizes .PNG.png before blocklist check

Existing test updates

Tests 1 ("inlines an SVG file"), 2 ("inserts content raw"), and 5 ("returns error when file not found") now set _contentRoot alongside _contentDir. These changes are green — the current code ignores _contentRoot when set (it just runs the sandbox check, which passes for same-directory files). After the developer makes _contentRoot required, these tests will continue to pass because they already provide it.

Implementation guidance

In internal/template/inline.go, the resolve method at the contentRoot check (currently if contentRoot != ""):

// Current (skip sandbox when empty):
contentRoot, _ := context.FindVariable("_contentRoot", false).(string)
if contentRoot != "" {
    // sandbox check
}

// Required (fail closed):
contentRoot, _ := context.FindVariable("_contentRoot", false).(string)
if contentRoot == "" {
    return "", fmt.Errorf("inline tag requires _contentRoot in render context")
}
contentRoot = filepath.Clean(contentRoot)
rel, err := filepath.Rel(contentRoot, resolved)
if err != nil || strings.HasPrefix(rel, "..") {
    return "", fmt.Errorf("inline path escapes content root: %s", relPath)
}

The change is minimal: invert the guard from if contentRoot != "" (skip) to if contentRoot == "" (error), then unconditionally run the sandbox check.

Refs #931, #932

🤖 Generated with Claude Code

Issue #932 (fail closed): Add test verifying inline tag returns an error
when _contentRoot is missing from the render context, rather than
silently skipping the path-traversal sandbox. Update existing tests 1,
2, and 5 to include _contentRoot to match production context shape.

Issue #931 (mixed-case extension): Add two regression tests verifying
case-insensitive extension matching — .SVG is accepted (text allowlist)
and .PNG is rejected (binary blocklist).

PLAN.md and IMPLEMENTATION.md updated with fail-closed requirement and
case-insensitive extension note.

Test status:
- returns error when _contentRoot is not set (#932): RED
- matches file extensions case-insensitively (#931): GREEN
- rejects binary file type with mixed-case extension (#931): GREEN

Refs #931, #932

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds/updates specification-level tests and planning docs around {% inline %} tag hardening in the Liquid template engine—specifically (a) requiring _contentRoot for path sandboxing (fail-closed) and (b) documenting/test-covering case-insensitive extension handling.

Changes:

  • Update PLAN/IMPLEMENTATION docs to explicitly require fail-closed behavior when _contentRoot is missing and to note case-insensitive extension normalization.
  • Adjust existing inline-tag spec tests to pass _contentRoot (to match production render context shape).
  • Add new spec tests for missing _contentRoot (expected to be red until implementation changes) and mixed-case extension behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
plans/PLAN.md Documents fail-closed _contentRoot requirement and notes case-insensitive extension handling.
plans/IMPLEMENTATION.md Updates inline-tag implementation notes with fail-closed and case-insensitive extension guidance.
internal/template/liquid_test.go Updates inline-tag tests to include _contentRoot and adds new regression/spec tests for _contentRoot and mixed-case extensions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plans/PLAN.md Outdated
Comment thread internal/template/liquid_test.go
Comment thread plans/IMPLEMENTATION.md Outdated

@zeroedin zeroedin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Results

Scope: internal/template/liquid_test.go, plans/PLAN.md, plans/IMPLEMENTATION.md
Intent: Harden the {% inline %} tag with a fail-closed sandbox test (#932) and regression tests for case-insensitive extension matching (#931). Update specs to match.
PR type: Architect spec PR — reviewing test quality, coverage, spec alignment, and PLAN.md/IMPLEMENTATION.md consistency.

Verification

All claims in the PR body confirmed by running the test suite:

Test Claimed Actual
returns error when _contentRoot is not set (#932) 🔴 RED 🔴 RED — Render() returns nil error; sandbox silently skipped
matches file extensions case-insensitively (#931) ✅ GREEN ✅ GREEN — strings.ToLower normalizes .SVG
rejects binary file type with mixed-case extension (#931) ✅ GREEN ✅ GREEN — strings.ToLower normalizes .PNG
Updated: inlines an SVG file ✅ GREEN ✅ GREEN
Updated: inserts content raw ✅ GREEN ✅ GREEN
Updated: returns error when file not found ✅ GREEN ✅ GREEN

Analysis

Test quality. All three new tests are self-contained, deterministic, use DeferCleanup for temp directory cleanup, and have specific assertions with descriptive failure messages. The red test (#932) asserts both that an error occurs AND that the error message mentions _contentRoot — this constrains the developer's implementation to produce a useful error, not just any error.

Existing test updates. The three updated tests add _contentRoot alongside _contentDir to match production render context shape. The tests that were NOT updated (returns error for binary file types, returns error for absolute paths) correctly omit _contentRoot — both error before reaching the sandbox check (inline.go:72-73 and inline.go:67-68 respectively), so adding _contentRoot would test nothing new and obscure the test's actual focus.

Spec consistency. PLAN.md and IMPLEMENTATION.md changes are internally consistent:

  • Both describe the fail-closed requirement the same way: error when _contentRoot is missing/empty rather than skipping the sandbox
  • Both document case-insensitive extension matching via strings.ToLower
  • IMPLEMENTATION.md includes concrete implementation guidance (invert the if contentRoot != "" guard to if contentRoot == "" → error) and specifies the exact error message string

No Skip() used. Red test fails with a real assertion failure as required.

Coverage

  • Testing gaps: none for the scope of this PR
  • Residual risks: none

Verdict: Ready to merge

Reasoning: Clean spec PR. The red test correctly captures the fail-closed security requirement at inline.go:85 where the if contentRoot != "" guard silently skips the sandbox. The green regression tests pin existing strings.ToLower behavior so future removals would be caught. Spec docs are consistent with the tests. Implementation guidance is concrete and minimal (one guard inversion). No issues found.

Fix allowlist/blocklist terminology in all three files — the inline tag
code enforces a binary-extension blocklist, not an allowlist. The
PLAN.md "allowed file types" list is informational only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@zeroedin zeroedin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Results (Re-review)

Scope: internal/template/liquid_test.go, plans/PLAN.md, plans/IMPLEMENTATION.md
Intent: Harden the {% inline %} tag with a fail-closed sandbox test (#932) and regression tests for case-insensitive extension matching (#931). Update specs to match.
PR type: Architect spec PR — reviewing test quality, coverage, spec alignment, and PLAN.md/IMPLEMENTATION.md consistency.

Prior Feedback Status

All three Copilot comments (allowlist → blocklist terminology in PLAN.md, IMPLEMENTATION.md, and liquid_test.go) were addressed in 395c1cd. Each fix is correct and minimal.

Re-verification

Test Expected Actual
returns error when _contentRoot is not set (#932) 🔴 RED 🔴 RED — Render() returns nil; sandbox silently skipped
matches file extensions case-insensitively (#931) ✅ GREEN ✅ GREEN
rejects binary file type with mixed-case extension (#931) ✅ GREEN ✅ GREEN
inlines an SVG file (updated) ✅ GREEN ✅ GREEN
inserts content raw (updated) ✅ GREEN ✅ GREEN
returns error when file not found (updated) ✅ GREEN ✅ GREEN
4 pre-existing inline tests ✅ GREEN ✅ GREEN

Analysis

No new issues found. The fix commit is clean — three one-line terminology corrections, no behavioral changes. The blocklist terminology now accurately describes the code at inline.go:13-20 and inline.go:71-73. PLAN.md, IMPLEMENTATION.md, and test comments are consistent with each other and with the implementation.

Coverage

  • Testing gaps: none
  • Residual risks: none

Verdict: Ready to merge

Reasoning: Prior feedback fully addressed. All test status claims verified. No new issues introduced by the fix commit.

@zeroedin zeroedin merged commit 63df3a4 into feature/inline-tag-hardening Jul 11, 2026
1 check failed
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.

2 participants