GitHub Advisoryadvisory · npm

GHSA-mxm6-v9r6-r94c @nuxtjs/mdc's URL sanitizer misses SVG xlink:href and data:text/html, allowing XSS from untrusted markdown at the default configuration

HIGH Published 2026-09-16 Modified 2026-09-16 Official page ↗
Aliases
CVE-2026-63671
Ecosystem
npm
Packages
@nuxtjs/mdc
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
Weaknesses
CWE-184, CWE-79
Reviewed
2026-09-16

Affected packages

• npm @nuxtjs/mdc: < 0.22.1 → fixed in 0.22.1

Description

Summary `@nuxtjs/mdc` renders untrusted markdown (including raw HTML) to a Vue component tree. Across two prior advisories it added a URL/attribute sanitizer to block dangerous links in that HTML: `validateProps` / `validateProp` and an `unsafeLinkPrefix` deny-list (`dist/runtime/parser/utils/props.js`). The sanitizer runs at parse time (`dist/runtime/parser/compiler.js`) and `parseMarkdown` enables raw HTML by default (`allowDangerousHtml: true`, `dist/runtime/parser/options.js`), so the sanitizer is the only barrier and it applies with no configuration required. Two sibling vectors bypass that sanitizer at the default configuration: 1. SVG anchor `xlink:href`. `validateProp` only scheme-checks attributes named exactly `href` or `src`: ``` if (attribute === "href" || attribute === "src") return isAnchorLinkAllowed(value); return true; ``` An `xlink:href` (parsed to the hast property `xLinkHref`) is neither, so a `javascript:` URL on an SVG `` is passed through. The renderer maps the property back to the real attribute (`MDCRenderer.vue`: `find(html, "xLinkHref").attribute` is `xlink:href`), so the output element is ``. Clicking it runs the script in the page origin. Plain `` is correctly stripped, which is what makes this the un-patched sibling. 2. ``. `data:text/html` is present in `unsafeLinkPrefix`, but the check compares it against `url.protocol`: ``` if (unsafeLinkPrefix.some((prefix) => url.protocol.toLowerCase().startsWith(prefix))) return false; ``` For any data URI `url.protocol` is just `"data:"`, so `"data:".startsWith("data:text/html")` is always false. Every `data:text/*` entry in the deny-list is therefore dead code, and `...">` is allowed (iframe is not in the render-time `dangerousTags`, which is only `["script","base"]`). The framed document executes script in an opaque origin. For contrast, `srcdoc` and `object` are blocked, so this is a precise gap rather than a general absence of filtering. Reproduction I will attach the zip file for POC, you can simply extract and run `./poc.sh` to install mdc and show the poc in the html file. nuxtjs-mdc-xss_poc.zip Two zero-argument checks: 1. `sh poc/poc.sh` installs `@nuxtjs/mdc` and runs `parseMarkdown` (the documented API) at default. It shows the parsed tree retains `a { xLinkHref: "javascript:..." }` and `iframe { src: "data:text/html,..." }`, while the control payloads `href="javascript:..."` and `srcdoc=...` are removed by the sanitizer. This isolates the sanitizer bypass deterministically. 2. `poc/poc.sh` also serves `poc/poc.html` over http (data: iframes and javascript: links are restricted under the file:// origin, so http is used). Open the printed URL and click the blue SVG link. The page contains the exact DOM the renderer produces for those parsed nodes; clicking the SVG link executes script in the page origin (same-origin), and the data:text/html iframe executes on load. The page prints VULNERABLE for each that fires. Both vectors were confirmed executing in a current Chromium build: the SVG `xlink:href` link runs script in the document origin on click, and the data:text/html iframe runs script on load. Suggested fix In `validateProp`, scheme-check `xlink:href` (and the hast `xLinkHref`) the same way as `href`/`src`. In `isAnchorLinkAllowed`, compare the dangerous MIME-typed entries against the full URL (or `href`), not against `url.protocol`, so `data:text/html` is actually matched; or add `iframe` to the render-time dangerous-tag set / restrict iframe `src` schemes.

Related

Alias · CVE (1)

  • CVE-2026-63671 MDC is a tool to take regular Markdown and write documents interacting deeply with a Vue component. vulnerability

Weakness · CWE (2)

  • CWE-184 Incomplete List of Disallowed Inputs weakness
  • CWE-79 Improper Neutralization of Input During Web Page Generation weakness

References