Skip to content

& not HTML-escaped in link/autolink href, silently changing the link target #4052

Description

@maximilliangrand

Marked version: 18.0.9 (also reproduces on every version tested, back to v12)

Markdown flavor: CommonMark

Repro

const { marked } = require('marked');
marked('<https://example.com/?x=1&lt;2>');

Expected (commonmark.js and CommonMark spec example 595& in the destination is escaped to &amp;):

<p><a href="https://example.com/?x=1&amp;lt;2">https://example.com/?x=1&amp;lt;2</a></p>

Actual (raw & in the href):

<p><a href="https://example.com/?x=1&lt;2">https://example.com/?x=1&lt;2</a></p>

Because &lt; is a valid HTML character reference, a browser decodes it inside the attribute, so the link actually points to https://example.com/?x=1<2 — a different URL than was written. Confirmed by parsing marked's output with parse5:

marked href, parsed back: "https://example.com/?x=1<2"     (wrong)
commonmark href, parsed:  "https://example.com/?x=1&lt;2"   (correct)

Note marked already escapes the same URL in the anchor text (&amp;) but not in the href, so the output is internally inconsistent. Any URL containing a valid entity sequence (&lt;, &amp;, &copy;, …) is affected; this is silent link-target corruption for user- or attacker-supplied URLs.

Cause
Renderer.link() writes cleanUrl(href) straight into the attribute without escapeHtmlEntities() — while the title attribute one line below is escaped. cleanUrl only runs encodeURI, which never encodes &, so the raw & reaches the attribute. (Renderer.image() has the same pattern for src.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    L2 - annoyingSimilar to L1 - broken but there is a known workaround available for the issuecategory: links

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions