Skip to content

fix(markup): decode entities in link targets once, not twice - #146

Merged
ralyodio merged 1 commit into
profullstack:masterfrom
iliasabk:fix/markdown-url-amp-double-escape
Sep 21, 2026
Merged

ralyodio merged 1 commit into
profullstack:masterfrom
iliasabk:fix/markdown-url-amp-double-escape

Conversation

@iliasabk

Copy link
Copy Markdown
Contributor

renderInline captures link targets after escapeHtml has already run, so a & in a Markdown URL arrives as &. The emit path then escaped it a second time to &, which browsers decode to the literal text & — turning a query string like ?a=1&b=2 into one broken parameter (a=1 plus a stray amp;b=2) on every [text](url), ![alt](src) and bare URL. Job posts carry apply links with query parameters, so this corrupted real outbound links.

unescapeUrl now also decodes the five entities escapeHtml produces, in a single pass: matching &amp; in the alternation means a literal &lt; in the source decodes to &lt; (not twice to <), so the round trip is exactly one decode for one encode. The bare-URL path decodes before stripping trailing punctuation, so an entity's own ; is not mistaken for sentence punctuation.

Test coverage: new cases assert ?a=1&b=2 survives as &amp; (not &amp;amp;) in link targets, bare URLs and image sources, plus a literal &amp; in a target round-tripping exactly once.

tsc clean, node --test — 367/367 pass.

Submitted under the current paid Agentic Jobs bug-fix offer.

Hrefs are captured after escapeHtml, so a & in a Markdown link target
arrives as &amp;. Emitted verbatim it was escaped again to &amp;amp;,
which browsers decode to &amp; — turning a query like ?a=1&b=2 into a
single broken parameter on every link, image and bare URL.

unescapeUrl now decodes the five entities escapeHtml produces in one
pass (the alternation keeps a literal &lt; as &lt;), and the bare-URL
path decodes before stripping trailing punctuation so an entity's own
semicolon is not mistaken for it.
@ralyodio
ralyodio merged commit 6021915 into profullstack:master Sep 21, 2026
4 checks passed
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