VDB
Sign up
HIGH8.1

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

Quick fix

GHSA-mxm6-v9r6-r94c — @nuxtjs/mdc: upgrade to the fixed version with the command below.

npm install @nuxtjs/mdc@0.22.1

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](https://github.com/user-attachments/files/29175603/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.

Are you affected?

Enter the version of the package you're using.

Affected packages

npm/@nuxtjs/mdc
Introduced in: 0Fixed in: 0.22.1
Fixnpm install @nuxtjs/mdc@0.22.1

References