Prevent Liquid evaluation in markdown code blocks - #60
Merged
Merged
Conversation
Liquid written inside a markdown code span or block is a sample, not a
statement, but a markdown field becomes html before the page renders and
`crate::tags::output` renders liquid it finds in a value — so the sample
was evaluated. Worse, comrak escapes code, so a filter argument's quotes
arrive as `"` and the value fails to parse at all, failing the build:
{{sample_menus | where: "slug", "lunch" | map: "url" | first }}
Comrak's html renderer escapes code itself, so it can't be asked for a
literal `{`. Markdown is now parsed to an ast first, every `{` that opens
a liquid delimiter in a `Code` or `CodeBlock` node is replaced with a NUL
sentinel the renderer passes through, and the sentinel becomes `{`
once the html exists. NUL is sound as a sentinel because comrak replaces
every NUL in its input with U+FFFD, as CommonMark requires, so one can't
reach the ast from the source.
Only markdown's own code nodes are treated this way: html the author
wrote by hand still renders liquid, `<code>` included.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXKzyT7wjxop7Me8egDDqZ
The gotcha still described the old architecture, where archival rendered
a finished page a second time if the output still held liquid — which is
what made a bare `{% raw %}` useless. `crate::tags::output` replaced that
with rendering a value in place, one level deep, so a template's raw tag
now behaves as it does anywhere else.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXKzyT7wjxop7Me8egDDqZ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This change prevents Liquid template syntax from being evaluated when it appears inside markdown code blocks (inline code spans, fenced code blocks, and indented code blocks). This ensures that code examples containing
{{ }}or{% %}are rendered literally rather than being processed as Liquid templates.Key Changes
Replaced
comrak::markdown_to_htmlfunction call with a custommarkdown_to_htmlimplementation that:\0){in the final HTML outputAdded
neutralize_liquidhelper function that:{{and{%patterns in code literalsNoneif no delimiters are found (optimization)Updated imports to use lower-level comrak APIs (
parse_document,format_html,Arena,NodeValue) instead of the high-levelmarkdown_to_htmlfunctionAdded comprehensive test coverage with 6 new tests validating:
Updated documentation in
authoring.mdto clarify that literal{{in markdown code doesn't need escapingImplementation Details
\0(NUL character) as a sentinel because comrak replaces all NUL bytes with U+FFFD during parsing, making it impossible for the sentinel to appear in the AST from source input{after HTML rendering, which displays as{in browsers{% raw %}for opt-outhttps://claude.ai/code/session_01YXKzyT7wjxop7Me8egDDqZ