Skip to content
Putting technology to work.
Insights to guide decisions and action.

Search articles

Vue SSR attribute-name XSS: when 3.5.42 fixes it, and updating Nuxt

Table of contents · 7 items

Running npm audit on a Nuxt site shows "high" for vue and @vue/server-renderer. The finding is GHSA-g2v6-rqmx-r4w6; the advisory was published in the vuejs/core repository on August 31 and registered in the GitHub Advisory Database on October 5 (both UTC; the latter is the morning of the 6th in Japan time). On server-side rendering (SSR) sites that, for example, output field names chosen by editors in a CMS as data-* attributes on cards, or pass API responses straight to elements with v-bind, this can become an XSS in which JavaScript runs just by opening the page (hypothetical scenario).

On the other hand, the fixed version 3.5.42 was already published to npm on August 27, and sites that updated their dependencies after that are already fixed. In this article, we explain the conditions for affected code, how to check versions, and an example in Nuxt where an old version remains even though you think you have updated. The results in the second half were confirmed by actually running Vue and Nuxt in the editorial team's test environment.

Affected: SSR that receives "attribute names" from outside

When Vue SSR spreads an object into attributes, as in <div v-bind="obj">, it builds the HTML string with ssrRenderAttrs() in @vue/server-renderer. Attribute values are escaped every time. Attribute names, however, were only checked against a list of disallowed characters (> / = " ', tab, line feed (LF), form feed and space) and were otherwise output as-is.

This list did not include the carriage return character (CR, \r). According to the advisory, browsers replace a lone CR with LF at the very first stage of reading HTML. Because LF separates attribute names, x\rautofocus\ronfocus, which Vue output as a single name, is interpreted by the browser as three attributes: x, autofocus and onfocus. If a script is placed on the value side, it runs as soon as the page loads.

The conditions listed in the advisory are as follows.

  • An object with keys that come from outside (names, not just values) is passed to an SSR element with v-bind="object" or an equivalent form
  • Examples given include CMSs and form builders where low-privilege users can set field names, components that spread configuration objects validated elsewhere onto elements, and processing that turns objects converted from database records, API responses or query strings into attributes
  • In browser-side rendering (client-side Vue), setAttribute() checks names, so the reporter says they could not reach this issue there. The issue is limited to SSR

Normal usage that receives only values from outside (such as :title="post.title") is not affected because values were always escaped. The severity is HIGH (CVSS 3.1 vector AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N).

Not just v-bind in templates

In the editorial team's test environment (Linux, Node.js v22.22.0), we passed a harmless key data-a\rdata-injected to SSR in three different ways and read the output with parse5, an HTML5 parser.

@vue/server-renderer 3.5.41
  <div v-bind="attrs"> をSSR用にコンパイル → ssrRenderAttrs を呼ぶコードになる
  ssrRenderAttrs(attrs)        → " id=\"card\" data-a\rdata-injected=\"yes\""
  parse5 の解釈              → id="card" data-a="" data-injected="yes"
  renderToString(h('div', attrs)) → 同じく data-a と data-injected に分かれた
  <Card v-bind="attrs">(ルート要素への引き継ぎ) → class, data-a, data-injected に分かれた

@vue/server-renderer 3.5.42 / 3.5.44
  3つとも出力は id="card"(または class="card")だけ。危険なキーは出力されない

In addition to v-bind in templates, h() in render functions and attributes not declared as props being passed through to a child's root element also went through the same path. When searching your code, target these three forms.

Where to look in your own code

From here on is the editorial team's breakdown of where in your repository to check the advisory's conditions.

  1. Search .vue files for v-bind="..." without an argument. Check whether the keys of the objects passed come from a CMS, API, database or URL query
  2. Check whether h( or render() is passed external data as-is in the second argument
  3. Check whether you pass things to child components in bulk with v-bind="...". If the child does not declare props, the keys become attributes of the root element
  4. Check for helper functions that build attribute objects from external field names with Object.fromEntries or ...spread

If your design needs to accept keys from outside, one possible approach, in addition to updating, is to define the allowed form and restrict to it, such as names that start with data- and consist only of alphanumeric characters and hyphens. This is the editorial team's suggestion; the measure the advisory calls for is updating.

Fixed in 3.5.42, released August 27

Release lineAffected versionsFixed versionnpm release date and time (UTC)
3.5 series3.5.41 and earlier (lower bound of the range is 0)3.5.42August 27, 2026
3.6 RC3.6.0-rc.0〜rc.53.6.0-rc.6August 28, 2026

The fix is vuejs/core PR #15266 (commit a2b40db, August 11), a change that adds the single character \u000d to the packages/shared/src/domAttrConfig.ts list. The 3.5.42 changelog also says "server-renderer: reject CR in attribute names." The advisory in the vuejs/core repository was published on August 31 (UTC), but it was added to the GitHub Advisory Database on October 5 (UTC), and npm audit began flagging it only after that.

As of October 21, npm's latest is 3.5.44 (released October 13) and rc is 3.6.0-rc.12. Upgrading to 3.5.42 or later brings in the fix.

What is worth checking is where the fix lives. The function that judges dangerous names, isSSRSafeAttrName(), is in @vue/shared, and @vue/server-renderer imports and uses it. On npm, the versions of the two are pinned to exact matches, so they normally line up, but when we used overrides in testing to combine @vue/server-renderer 3.5.42 with @vue/shared 3.5.41, the attributes were split, and npm audit reported 0 findings. In projects that pin or override dependencies, also check the version of @vue/shared.

Old Nuxt lock files keep the update from arriving

Nuxt 4.6.2 (released October 16) requires vue at ^3.5.44, so upgrading to 4.6.2 also upgrades Vue. On the other hand, the latest in the Nuxt 3 series, 3.21.11 (released August 5), requires ^3.5.40, which is satisfied even with 3.5.41.

Using a lock file created with Nuxt 3.21.11 and Vue 3.5.41, we tried the following steps locally in order (npm 10.9.4; we added --legacy-peer-deps because npm raised an error while resolving Nuxt's dependencies).

Operationvue and @vue/server-renderer in the lock filenpm audit
Change vue in package.json to ^3.5.41 and run npm installStays at 3.5.41high(GHSA-g2v6)
npm update vue3.5.44No findings
Newly created with Nuxt 4.6.23.5.44―

Even if the range of ^ includes a newer version, npm install keeps the version in the lock file. For Nuxt 3 sites, update the lock file with npm update vue (or the respective update command in pnpm or yarn), and confirm that the diff includes 3.5.42 or later for @vue/server-renderer and @vue/shared.

Do not look only at the package.json in the build output

We built a minimal app on the same Nuxt 3.21.11 (using a dangerous pattern, only for testing, that spreads URL query parameters into attributes), started .output/server, and checked.

Results the editorial team confirmed with a minimal Nuxt 3.21.11 app. Before the update, @vue/shared in .output/server/package.json was shown as 3.5.44, but @vue/server-renderer was 3.5.41, with a separate 3.5.41 @vue/shared placed under it, and a query key containing CR was split into two attributes, data-a and data-injected. After npm update vue and a rebuild, everything became 3.5.44, the dangerous key was not output, and an unsafe attribute name warning appeared in the server log

In the build before the update, .output/server/package.json said "@vue/shared": "3.5.44", and .output/server/node_modules/@vue/shared also pointed to 3.5.44. However, @vue/server-renderer was 3.5.41, with a separate node_modules/@vue/shared under it, and the build output also bundled the 3.5.41 @vue/shared. Opening /?data-a%0Ddata-injected=yes, the <div> in the response was split by parse5 into three: id, data-a and data-injected.

After running npm update vue and rebuilding, all Vue-related packages in .output/server were at 3.5.44, and the response to the same request contained only <div id="card">. The server log showed unsafe attribute name and Skipped rendering unsafe attribute name (the same output appeared when running standalone Vue with NODE_ENV=production).

When checking the build output, look not at the @vue/shared line in .output/server/package.json, but at the version of @vue/server-renderer and whether an old @vue/shared remains beneath it.

What to check after upgrading

  • @vue/server-renderer is 3.5.42 or later in the production build (npm ls @vue/server-renderer @vue/shared, and for Nuxt also .output/server/node_modules)
  • On staging, send a value whose key contains %0D to a page that expands it into attributes, and confirm that no extra attributes appear in the response. If attribute names come from somewhere other than the query, test with something like a CMS field used for verification
  • If unsafe attribute name appears in the server log after updating, investigate where the data containing invalid characters in attribute names came from (the editorial team's suggestion)

How to decide who updates dependencies and how often is covered in React2Shell sites left unpatched and revisiting maintenance contracts. What to check when updating to Nuxt 4.6 is in What to check before rewriting nuxt/server in Nuxt 4.6, and the Next.js advisories from the same period are summarized in Next.js October: 9 vulnerabilities.

Scope of verification

On October 21, 2026, we directly retrieved and checked the JSON for GHSA-g2v6-rqmx-r4w6 in github/advisory-database, the vuejs/core repository (fix commit a2b40db, tags v3.5.41 to v3.5.44 and v3.6.0-rc.5 and rc.6, and CHANGELOG.md), and the publication timestamps and dependency ranges of vue, @vue/server-renderer, @vue/shared and nuxt on the npm registry (desk research). In addition, on Linux with Node.js v22.22.0 and npm 10.9.4, we parsed with parse5 the output of @vue/server-renderer 3.5.41, 3.5.42 and 3.5.44, and of a setup combining 3.5.42 with @vue/shared 3.5.41, and tried updating the lockfile of Nuxt 3.21.11 with npm audit as well as building and starting a minimal app (hands-on testing). We did not verify script execution in a real browser, Vue 2 (vue-server-renderer), updates with pnpm or yarn, build output for Nuxt versions other than 3.21.11, or that client-side rendering is not affected. We did not open NVD or vuejs.org. On October 21, before publication, we checked the vuejs/core advisory itself (published August 31) via the GitHub API, and also confirmed that no additional Vue advisories or new Nuxt 3 releases had appeared after October 5.

For dependency updates or SSR code reviews on sites built with Vue or Nuxt, please contact us via Development, AI and automation inquiries.

References

Share this articleXFacebook
Kakeru Suzuki

Fascinated by the possibilities of technology, has had a deep interest in programming and digital art since student days

Turn this article's theme into your company's next step

Starting from what you want to achieve with your website.

We organize user goals, required features, and ongoing maintenance structures to determine the first steps in development and improvement.

  • Website objectives
  • Features and usability
  • Post-launch operations
Consult on web development and improvements

You can consult with us from the initial conceptual stage. Details from this article will be carried over to the inquiry form.

Receive the latest articles via email · Read the web production guide
Free download

Complete Guide to Web Production: Costs, Vendor Selection & Traffic Acquisition [2026 Edition]

We have compiled cost benchmarks, vendor selection criteria, and traffic acquisition strategies into a PDF.

The PDF and newsletter emails are currently in Japanese.

You will also be subscribed to our newsletter. You can unsubscribe at any time.