GHSA-mxm6-v9r6-r94c

Suggest an improvement
Source
https://github.com/advisories/GHSA-mxm6-v9r6-r94c
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-mxm6-v9r6-r94c/GHSA-mxm6-v9r6-r94c.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-mxm6-v9r6-r94c
Aliases
Published
2026-09-16T22:14:01Z
Modified
2026-09-16T22:30:08Z
Severity
  • 8.1 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N CVSS Calculator
Summary
@nuxtjs/mdc's URL sanitizer misses SVG xlink:href and data:text/html, allowing XSS from untrusted markdown at the default configuration
Details

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 <a> 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 <a xlink:href="javascript:...">. Clicking it runs the script in the page origin. Plain <a href="javascript:..."> is correctly stripped, which is what makes this the un-patched sibling.

  2. <iframe src="data:text/html,...">. 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 <iframe src="data:text/html,<script>...</script>"> 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.

Database specific
{
    "cwe_ids":  [
        "CWE-184",
        "CWE-79"
    ],
    "github_reviewed":  true,
    "github_reviewed_at":  "2026-09-16T22:14:01Z",
    "nvd_published_at":  "2026-09-16T15:17:40Z",
    "severity":  "HIGH"
}
References

Affected packages

npm / @nuxtjs/mdc

Package

Name
@nuxtjs/mdc
View open source insights on deps.dev
Purl
pkg:npm/%40nuxtjs/mdc

Affected ranges

Type
SEMVER
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
0.22.1

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-mxm6-v9r6-r94c/GHSA-mxm6-v9r6-r94c.json"