Skip to content

Fix heading auto-link hijacking nested link labels (#668) - #949

Merged
xoofx merged 2 commits into
xoofx:mainfrom
dualfroz:dualfroz/fix-autolink-autoidentifier-668
Sep 19, 2026
Merged

xoofx merged 2 commits into
xoofx:mainfrom
dualfroz:dualfroz/fix-autolink-autoidentifier-668

Conversation

@dualfroz

@dualfroz dualfroz commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Problem

#668

When both the AutoLinks and AutoIdentifiers extensions are enabled, a Markdown link whose label
contains a nested [...] fragment that happens to match the text of an existing heading gets parsed
incorrectly. Example:

Header

Testing [Header]

Testing [test]


Expected (matches the output produced when only `AutoLinks` is enabled):

```html
<h1 id="testing-markdown">Testing Markdown</h1>
<h3 id="header">Header</h3>
<p><a href="https://www.bing.com">Testing [Header]</a></p>
<p><a href="https://www.google.com">Testing [test]</a></p>

Actual (on upstream/main, commit b07b98e1):

<h1 id="testing-markdown">Testing Markdown</h1>
<h3 id="header">Header</h3>
<p>[Testing <a href="#header">Header</a>](<a href="https://www.bing.com">https://www.bing.com</a>)</p>
<p><a href="https://www.google.com">Testing [test]</a></p>

Root cause

AutoIdentifierExtension (src/Markdig/Extensions/AutoIdentifiers/AutoIdentifierExtension.cs), when
its default AutoLink option is enabled (it is on by default via AutoIdentifierOptions.Default),
registers a HeadingLinkReferenceDefinition at the document level for every heading, keyed by the
heading's raw text. This turns any occurrence of [HeadingText] in the document into an implicit
shortcut reference link to that heading, without the author ever writing a [HeadingText]: url
reference definition.

Markdig's core link parser (src/Markdig/Parsers/Inlines/LinkInlineParser.cs) resolves brackets using
the CommonMark "nearest active opener wins" algorithm: when it hits the ] right after Header, it
looks for a link reference definition matching Header first. Because the heading ### Header
implicitly registered one, the inner [Header] resolves into a link before the outer bracket ever gets
a chance to see its own ](https://www.bing.com). Per CommonMark's "links cannot contain links" rule,
successfully resolving the inner link also deactivates the outer [ bracket, so the outer bracket can
no longer become a link and is emitted as literal text instead.

This exact resolution order is correct and spec-compliant for a reference definition the author defined
on purpose (verified: a plain CommonMark document containing an explicit [Header]: /somewhere
definition produces the identical "broken-looking" nesting, with no extensions involved at all). The bug
is that AutoIdentifierExtension's heading auto-link references are created implicitly, for every
heading, with no way for the author to know that a particular word will silently start behaving as a
link reference elsewhere in the document — including inside brackets that were never meant to be link
labels for the heading, such as the label of a larger, still-open link.

Fix

Added an internal extensibility point, LinkReferenceDefinition.AllowResolutionInsideOpenLink (default
true), and overrode it to false on HeadingLinkReferenceDefinition
(src/Markdig/Extensions/AutoIdentifiers/HeadingLinkReferenceDefinition.cs). In
LinkInlineParser.ProcessLinkReference, before resolving a shortcut/collapsed/full reference link, the
parser now checks whether the reference definition disallows resolution while nested inside another
still-open, active link/image bracket (LinkInlineParser.HasActiveAncestorLink). If so, resolution is
skipped and the normal CommonMark fallback (treat the inner bracket as literal text, let the outer
bracket complete) takes over, exactly as it already does for any word that doesn't match a heading.

This is scoped narrowly to implicitly generated heading references:

  • Regular, user-authored reference definitions ([label]: url) are completely unaffected -
    AllowResolutionInsideOpenLink defaults to true, preserving existing CommonMark-compliant behavior
    for nested brackets with explicit reference definitions.
  • Standalone heading auto-links (not nested inside another link), e.g. See [Header] for details.,
    continue to work exactly as before.
  • Footnote reference definitions and any other extension using LinkReferenceDefinition are unaffected.

With the fix (full suite)

(The single skip, ListUnorderedLooseTop, is pre-existing and unrelated to this change - present before
this fix as well.)

@dualfroz
dualfroz force-pushed the dualfroz/fix-autolink-autoidentifier-668 branch from 32c14bb to 67a2e49 Compare September 5, 2026 23:19
@xoofx xoofx added the bug label Sep 7, 2026
@xoofx
xoofx merged commit baa95a9 into xoofx:main Sep 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants