Skip to content

fix: stop an unclosed inline raw tag from unescaping later blocks - #4130

Merged
UziTech merged 1 commit into
markedjs:masterfrom
thebriangao:fix/unclosed-inline-raw-tag
Oct 26, 2026
Merged

UziTech merged 1 commit into
markedjs:masterfrom
thebriangao:fix/unclosed-inline-raw-tag

Conversation

@thebriangao

Copy link
Copy Markdown
Contributor

Marked version: master (6a57018, 18.1.0)

Markdown flavor: CommonMark|GitHub Flavored Markdown

Description

I couldn't find an existing issue for this.

An inline <pre>, <code>, <kbd> or <script> tag sets lexer.state.inRawBlock, which makes inlineText emit text without escaping it. Nothing resets the flag between blocks. One unclosed tag therefore turns off escaping for every later block in the document: paragraphs, headings, list items and table cells.

Expectation

Wrap the shortcut in a <kbd> tag.

Then loop while i<n and stop.

Done & dusted.
<p>Wrap the shortcut in a <kbd> tag.</p>
<p>Then loop while i&lt;n and stop.</p>
<p>Done &amp; dusted.</p>

This is what the commonmark and markdown-it dev-dependencies both produce.

Result

<p>Wrap the shortcut in a <kbd> tag.</p>
<p>Then loop while i<n and stop.</p>
<p>Done & dusted.</p>

In a browser the second paragraph renders as just "Then loop while i". The unescaped <n and stop.</p is parsed as an element, and the rest of the line disappears. This matters for apps that render LLM/agent output with marked, which I do (with DOMPurify afterwards), because that prose often names a tag without backticks.

Fix

Reset inRawBlock before each block's inline run in Lexer.lex(). Raw text within a block is unchanged, for example <code>a & b</code>, including when it spans a line break. The new spec covers both cases. This follows the same approach as the outerInRawBlock restore in #4051, which fixed this leak for discarded link text.

Verification

  • New spec: test/specs/new/unclosed_inline_raw_tag uses renderExact, because the differ normalizes entities. It fails on master and passes with the fix. npm test passes in full.
  • Differential run: I compared old and new builds over 86k parses: every CommonMark/GFM example, the new/original specs, docs/, and 20k fuzzed documents, each under 4 option sets.
    • Every changed output came from an input containing an inline raw tag.
    • Every changed output is identical to the old one after entity-decoding, so only escaping changed, never structure.
  • One existing input changes: CommonMark example 170 under pedantic: true. It is a <script> spanning a blank line, which marked already splits with </p>\n<p>. Its second paragraph is now escaped as well.
  • Performance: benchmarked on a 77 KiB document with interleaved runs, the difference stays within ±2% noise.

inLink has the same problem: an unclosed <a …> stops autolinking in later blocks. Its effect is milder, so I left it out to keep this PR focused. I'm happy to add it if you'd like.

AI disclosure: the bug was found, and this fix and its verification were prepared, with Claude Code (AI-assisted).

Contributor

  • Test(s) exist to ensure functionality and minimize regression (if no tests added, list tests covering this PR); or,
  • no tests required for this PR.
  • If submitting new feature, it has been documented in the appropriate places.

Committer

In most cases, this should be a different person than the contributor.

🤖 Generated with Claude Code

An inline <pre>, <code>, <kbd> or <script> tag sets lexer.state.inRawBlock,
which makes inlineText emit text without escaping it. The flag was never
reset between blocks, so one unclosed tag left the text of every later
paragraph, heading, list item and table cell unescaped: `i<n & m` came out
as `i<n & m` instead of `i&lt;n &amp; m`, and a browser then parses the
`<n` as an element and drops the text after it.

Reset the flag before each block's inline run. Raw text within a block
is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 10, 2026

Copy link
Copy Markdown

@thebriangao is attempting to deploy a commit to the MarkedJS Team on Vercel.

A member of the Team first needs to authorize it.

@vercel

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
marked-website Ready Ready Preview Oct 10, 2026 9:56pm UTC

Request Review

@UziTech UziTech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice catch!

@UziTech
UziTech merged commit 4562b6f into markedjs:master Oct 26, 2026
8 checks passed
github-actions Bot pushed a commit that referenced this pull request Oct 26, 2026
## [18.1.2](v18.1.1...v18.1.2) (2026-10-26)

### Bug Fixes

* allow a tab or end of input after a type 6 html block tag name ([#4128](#4128)) ([e09c40d](e09c40d))
* apply emphasis flanking and rules 9-10 to the whole delimiter run ([#4145](#4145)) ([5d8e234](5d8e234))
* **cli:** await config functions before parsing ([#4138](#4138)) ([3a11ad1](3a11ad1))
* **cli:** create no-clobber output files atomically ([#4135](#4135)) ([e7c4f64](e7c4f64))
* do not continue a block quote paragraph from a whitespace-only line ([#4134](#4134)) ([42f34a8](42f34a8))
* expand a tab after the block quote marker to the next tab stop ([#4110](#4110)) ([2d3899b](2d3899b))
* honor per-call async options when composing hooks ([#4139](#4139)) ([379be9d](379be9d))
* keep a line starting with # but no space in the list item ([#4127](#4127)) ([6b63189](6b63189))
* keep duplicate link definitions in the tokens ([#4132](#4132)) ([2784acc](2784acc))
* keep lazy lines after a list in a nested blockquote ([#4137](#4137)) ([38c7e55](38c7e55))
* keep lazy setext underlines in list items as text ([#4126](#4126)) ([398234c](398234c))
* render nested image alt formatting as plain text ([#4131](#4131)) ([15f59bb](15f59bb))
* stop an unclosed inline raw tag from unescaping later blocks ([#4130](#4130)) ([4562b6f](4562b6f))

### Performance Improvements

* ~16% faster parsing by skipping em/strong masking for runs without *, _ or ~ ([#4142](#4142)) ([f0fd3e0](f0fd3e0))

This branch was successfully deployed

1 active deployment
Preview — d1ed182d Deployed Oct 10, 2026 by vercel[bot]
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