CVE-2026-63671: @nuxtjs/mdc SVG xlink:href and iframe data:text/html XSS
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…

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.
<!-- 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.
Related research
- high · 8.2CVE-2026-84370CVE-2026-84370: svgo removeScripts Plugin XSS via Namespace and Control-Character Bypass
- high · 8.2svgo removeScripts Plugin XSS Bypass via Namespace Prefix and Case-Insensitive URI
- highCVE-2026-88060CVE-2026-88060: @angular/platform-server SSR XSS via Unescaped Template Content Across DocumentFragment Boundaries
- high · 8.1@eigenpal/docx-editor-core: CSS Injection and Print-Time XSS via Unescaped Font Name