high · 8.1CVE-2026-63671Sep 16, 2026

CVE-2026-63671: @nuxtjs/mdc SVG xlink:href and iframe data:text/html XSS

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A flaw in the @nuxtjs/mdc URL sanitizer lets an attacker embed JavaScript in SVG links and data-URI iframes inside untrusted Markdown, executing scripts in the page's origin with no special…

Package@nuxtjs/mdc
Ecosystemnpm
Affected< 0.22.1
Fixed in0.22.1
CVE-2026-63671: @nuxtjs/mdc SVG xlink:href and iframe data:text/html XSS

The problem

The parseMarkdown API enables raw HTML by default (allowDangerousHtml: true), making validateProp the only barrier against executable URLs in parsed Markdown.

Two sibling gaps exist. First, validateProp only scheme-checks attributes named exactly href or src, so an SVG xlink:href (stored in hast as xLinkHref) carrying a javascript: URL passes through unchecked and the renderer re-emits it as the real xlink:href attribute.

Second, the unsafeLinkPrefix denylist compares data:text/html against url.protocol, which is always just "data:", so "data:".startsWith("data:text/html") is always false and every data:text/* entry in the list is dead code. An <iframe src="data:text/html,..."> therefore survives sanitization entirely.

Proof of concept

A working proof-of-concept for CVE-2026-63671 in @nuxtjs/mdc, with the exact payload below.

html
<!-- Vector 1: SVG xlink:href javascript: (executes in page origin on click) -->
<svg><a xlink:href="javascript:alert(document.origin)"><rect width="100" height="100"/></a></svg>

<!-- Vector 2: iframe data:text/html (executes in opaque origin on load) -->
<iframe src="data:text/html,<script>alert(document.origin)<\/script>"></iframe>

Vector 1 works because validateProp guards only attribute === "href" || attribute === "src". The SVG property xLinkHref matches neither branch, so isAnchorLinkAllowed() is never called and any javascript: URL is returned as-is. The renderer then maps xLinkHref back to the real xlink:href DOM attribute, giving a fully functional clickable XSS link in the document origin.

Vector 2 works because url.protocol for any data: URI is the fixed string "data:". The prefix check url.protocol.toLowerCase().startsWith("data:text/html") evaluates to "data:".startsWith("data:text/html"), which is always false. The iframe tag itself is not in the render-time dangerousTags set (["script","base"]), so it renders without any blocking.

The patch adds xLinkHref to the set of scheme-checked attributes in validateProp, and fixes the prefix comparison to test against the full raw URL string rather than url.protocol, making data:text/html entries functional again. CWE-184 (Incomplete List of Disallowed Inputs) describes both root causes precisely.

The fix

Upgrade @nuxtjs/mdc to version 0.22.1 or later (patch commit 61d636c, PR #491). If upgrading immediately is not possible, avoid passing untrusted user-controlled Markdown to parseMarkdown without a separate HTML sanitizer (such as DOMPurify) applied to the rendered output.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research