Skip to content

chore(deps): update dependency nuxt to v3.21.10 [security] - #698

Open
renovate[bot] wants to merge 1 commit into
nextfrom
renovate/npm-nuxt-vulnerability
Open

chore(deps): update dependency nuxt to v3.21.10 [security]#698
renovate[bot] wants to merge 1 commit into
nextfrom
renovate/npm-nuxt-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Mar 19, 2025

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
nuxt (source) 3.15.13.21.10 age confidence

Nuxt allows DOS via cache poisoning with payload rendering response

CVE-2025-27415 / GHSA-jvhm-gjrh-3h93

More information

Details

Summary

By sending a crafted HTTP request to a server behind an CDN, it is possible in some circumstances to poison the CDN cache and highly impacts the availability of a site.

It is possible to craft a request, such as https://mysite.com/?/_payload.json which will be rendered as JSON. If the CDN in front of a Nuxt site ignores the query string when determining whether to cache a route, then this JSON response could be served to future visitors to the site.

Impact

An attacker can perform this attack to a vulnerable site in order to make a site unavailable indefinitely. It is also possible in the case where the cache will be reset to make a small script to send a request each X seconds (=caching duration) so that the cache is permanently poisoned making the site completely unavailable.

Conclusion :

This is similar to a vulnerability in Next.js that resulted in CVE-2024-46982 (and see this article, in particular the "Internal URL parameter and pageProps" part, the latter being very similar to the one concerning us here.)

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt has Client-Side Path Traversal in Nuxt Island Payload Revival

CVE-2025-59414 / GHSA-p6jq-8vc4-79f6

More information

Details

Summary

A client-side path traversal vulnerability in Nuxt's Island payload revival mechanism allowed attackers to manipulate client-side requests to different endpoints within the same application domain when specific prerendering conditions are met.

Technical Details

The vulnerability occurs in the client-side payload revival process (revive-payload.client.ts) where Nuxt Islands are automatically fetched when encountering serialized __nuxt_island objects. The issue affects the following flow:

  1. During prerendering, if an API endpoint returns user-controlled data containing a crafted __nuxt_island object
  2. This data gets serialized with devalue.stringify and stored in the prerendered page
  3. When a client navigates to the prerendered page, devalue.parse deserializes the payload
  4. The Island reviver attempts to fetch /__nuxt_island/${key}.json where key could contain path traversal sequences
Prerequisites for Exploitation

This vulnerability requires all of the following conditions:

  1. Prerendered pages: The application must use Nuxt's prerendering feature (nitro.prerender)
  2. Attacker-controlled API responses: The attacker must be able to control the response content of an API endpoint that is called during prerendering via useFetch, useAsyncData, or similar composables
  3. Client-side navigation: A user must navigate to the prerendered page (not during initial SSR hydration)
Attack Scenario
// Malicious API response during prerendering
{
  "__nuxt_island": {
    "key": "../../../../internal/service",
    "params": { "action": "probe" }
  }
}

This could cause the client to make requests to /__nuxt_island/../../../../internal/service.json if path traversal is not properly handled by the server.

Impact Assessment
  • Limited Impact: The vulnerability has a low severity due to the highly specific prerequisites
  • No Direct Data Exfiltration: The vulnerability does not directly expose sensitive data
  • Client-Side Only: Requests originate from the client, not the server
Mitigation

Action Required:

  • Update to Nuxt 3.19.0+ or 4.1.0+ immediately
  • Review any prerendered pages that fetch external or user-controlled data

Temporary Workarounds (if immediate update is not possible):

  1. Disable prerendering for pages that fetch user-controlled data
  2. Implement strict input validation on API endpoints used during prerendering
  3. Use allowlists for API response structures during prerendering
Fix Details

The fix implemented validation for Island keys in revive-payload.server.ts:

  • Island keys must match the pattern /^[a-z][a-z\d-]*_[a-z\d]+$/i
  • Maximum length of 100 characters
  • Prevents path traversal and special characters

Severity

  • CVSS Score: 3.1 / 10 (Low)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt: Reflected XSS in navigateTo() external redirect

CVE-2026-45669 / GHSA-fx6j-w5w5-h468

More information

Details

Summary

navigateTo() with external: true generates a server-side HTML redirect body containing a <meta http-equiv="refresh"> tag. The destination URL is only sanitized by replacing " with %22, leaving <, >, &, and ' unencoded. An attacker who can influence the URL passed to navigateTo(url, { external: true }) can break out of the content="…" attribute and inject arbitrary HTML/JavaScript that executes under the application's origin.

This is a different root cause from CVE-2024-34343 (GHSA-vf6r-87q4-2vjf), which addressed javascript: protocol bypass. The issue here is triggered by any valid URL containing >.

Impact

Applications that pass user-controlled input to navigateTo(url, { external: true }) — typically via a ?next= / ?redirect= query parameter used for post-login or "return to" flows — are vulnerable to reflected cross-site scripting. The injected script runs in the context of the application's origin during the server-rendered redirect response, before the meta-refresh fires.

Details

In packages/nuxt/src/app/composables/router.ts, the SSR redirect path builds an HTML response body with only " percent-encoded in the destination URL:

const encodedLoc = location.replace(/"/g, '%22')
nuxtApp.ssrContext!['~renderResponse'] = {
status: sanitizeStatusCode(options?.redirectCode || 302, 302),
body: `<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${encodedLoc}"></head></html>`,
headers: { location: encodeURL(location, isExternalHost) },
}

The Location header is normalised through encodeURL() (which uses the URL constructor and correctly percent-encodes attribute-significant characters). The HTML body uses a narrower sanitiser. That mismatch is the root cause.

Proof of concept

Global middleware that forwards a query parameter to navigateTo:

// middleware/redirect.global.ts
export default defineNuxtRouteMiddleware((to) => {
const next = to.query.next as string | undefined
if (next) {
 return navigateTo(next, { external: true })
}
})

Request:

GET /?next=https://evil.example/x><img src=x onerror=alert(document.domain)>

Response body:

<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=https://evil.example/x><img src=x onerror=alert(document.domain)>"></head></html>

The > after evil.example/x terminates the content="…" attribute, and the <img onerror> tag executes JavaScript in the application's origin before any redirect
occurs.

Patches

Fixed in nuxt@4.4.6 and nuxt@3.21.6 by #​35052. The fix percent-encodes the full set of HTML-attribute-significant characters (&, ", ', <, >) before interpolating the URL into the meta-refresh body

Workarounds

If you can't upgrade immediately, validate user-controlled URLs before passing them to navigateTo(url, { external: true }). At minimum, normalise through new URL(input).toString() and reject inputs containing < or > (a normalised URL with these characters is malformed and safe to refuse).

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt: __nuxt_island endpoint does not bind responses to request props, enabling shared-cache poisoning

CVE-2026-46342 / GHSA-g8wj-3cr3-6w7v

More information

Details

Summary

The /__nuxt_island/* endpoint accepts attacker-controlled props query/body parameters and renders any island component without verifying that the URL-resident hash (<Name>_<hashId>.json) was actually issued for those inputs by <NuxtIsland>. The hash is computed and embedded client-side but never validated server-side, so the same path can return materially different responses depending on the query.

Island components are documented as rendering independently of route context - page middleware does not apply to them, and they are intentionally cacheable as a function of their props. This advisory does not treat that contract as a vulnerability. It treats the absence of a binding between the URL the cache keys on and the response served at that URL as one.

Impact

In applications where a CDN or reverse-proxy in front of the app caches /__nuxt_island/* keyed by path only (ignoring query) - a documented misconfiguration class, see GHSA-jvhm-gjrh-3h93 - an attacker can prime the cache for a path with their own choice of props, and subsequent users requesting the same path receive the attacker's rendered HTML rather than the response intended for them. The cache entry persists until normal expiry.

Where the affected island has any prop flowing into an unsafe HTML sink in application code (v-html, innerHTML, a third-party renderer treating a prop as HTML), this becomes stored XSS in the embedding page's origin until the cache entry expires. HttpOnly cookies remain out of reach but anything else in the origin (other cookies, in-origin requests, DOM state) is reachable by the injected script.

Preconditions:

  • experimental.componentIslands enabled (or the default 'auto' with at least one server / island component in the app).
  • A shared intermediary cache (CDN, reverse-proxy, edge cache) keyed on path only.
  • For the XSS pivot specifically: an application-authored island that puts a prop through an unsafe HTML sink.

Without the second precondition, the response shape is per-request and unaffected. Without the third, the worst case is content-swap / inert HTML injection rather than script execution.

Patches

Patched in nuxt@4.4.6 and nuxt@3.21.6 by #​35077. The island handler now recomputes the expected hashId from (name, props, context) using the same ohash function <NuxtIsland> already uses to embed the hash in the URL, and rejects requests (HTTP 400) whose URL-resident hash does not match. The response is now a pure function of the request path: a path-keyed shared cache returns the correct response to every requester for that path, and an attacker cannot synthesise a path whose hash matches arbitrary props.

Workarounds

For users unable to upgrade immediately:

  • Ensure any intermediary cache keys /__nuxt_island/* on the full query string, not on the path alone. This is the recommended configuration regardless.
  • Audit application-authored islands for props flowing into v-html / innerHTML / similar HTML sinks; treat island props as untrusted user input.
Note on island authentication

[!IMPORTANT]
It's important to remember that route middleware does not run when rendering island components, and islands cannot rely on routing-layer auth. Applications gating sensitive data behind page middleware should enforce that auth inside the island's own data layer (server-only routes, useRequestEvent + manual session checks, etc.) rather than relying on the embedding page's middleware - this was true before this advisory and remains true after it.

A separate advisory addresses *.server.vue pages registered as page_<routeName> islands, where the documented "middleware doesn't run for islands" contract collides with the page's own definePageMeta({ middleware }) declaration in a way that constitutes a genuine bug rather than documented behaviour.

Severity

  • CVSS Score: 2.3 / 10 (Low)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt's route middleware is not enforced when rendering .server.vue pages via /__nuxt_island/page_*

CVE-2026-47200 / GHSA-hg3f-28rg-4jxj

More information

Details

Summary

When experimental.componentIslands is enabled (default in Nuxt 4), any .server.vue file under pages/ is automatically registered as a server island under the key page_<routeName> and exposed via the /__nuxt_island/:name endpoint. Until this fix, requests through that endpoint rendered the page component directly via the SSR renderer without instantiating Vue Router, which meant route middleware declared on the page (including definePageMeta({ middleware })) did not run.

For Nuxt applications that gate a .server.vue page behind route middleware as their sole auth check, an unauthenticated attacker could bypass that check by requesting /__nuxt_island/page_<routeName>_<anyhash> directly and receiving the server-rendered HTML.

Affected configurations

All three conditions must hold for an application to be vulnerable:

  1. experimental.componentIslands is enabled (the default in Nuxt 4; opt-in in Nuxt 3).
  2. The application defines one or more .server.vue files under pages/, registering them as routed pages.
  3. Authentication / authorization for at least one such page is enforced solely via route middleware (middleware/*.ts referenced from definePageMeta), without a server-side check inside the page or its data layer.

Applications that enforce auth inside the island's own data layer (server-only API routes, useRequestEvent + manual session checks, etc.) were not affected. The general "route middleware does not run for non-page island components" behaviour is documented and unchanged; this advisory concerns the .server.vue page case specifically, where running middleware is the user's clear expectation.

Details
  • Build (packages/nuxt/src/components/templates.ts): .server.vue pages are registered as island components with page_ prefix, making them addressable through /__nuxt_island/page_<routeName>_<hashId>.
  • Runtime (packages/nitro-server/src/runtime/handlers/island.ts): the handler resolves the requested island component and renders it via renderer.renderToString(ssrContext). The Vue Router plugin previously short-circuited middleware execution whenever ssrContext.islandContext was set.
  • The two paths interact so that route middleware declared on the source page never runs.
Proof of concept

Given a page app/pages/secret.server.vue:

<script setup lang="ts">
definePageMeta({ middleware: 'auth' })
</script>

<template>
<h1>SECRET DATA</h1>
</template>

with middleware/auth.ts blocking unauthenticated access:

##### Direct page request: blocked by middleware
curl -i http://localhost:3000/secret

##### -> 403 / redirect, depending on the middleware

##### Island request: middleware did not run before this fix
curl -i 'http://localhost:3000/__nuxt_island/page_secret_anyhash'

##### -> 200 OK, body includes <h1>SECRET DATA</h1>
Patches

Patched in nuxt@4.4.6 and nuxt@3.21.6 by #​35092. The Vue Router plugin now runs middleware and redirect handling for page_* islands (i.e. islands that originate from .server.vue files in pages/). The island handler propagates middleware-issued responses (~renderResponse), and a new beforeResolve guard returns HTTP 400 when the requested page_<name> does not match the route component the URL resolves to.

Non-page island components are unaffected - they continue to render without route middleware, by design.

Workarounds

If you cannot upgrade immediately:

  • Enforce authentication inside the .server.vue page itself, not via route middleware. Read the session from useRequestEvent() and throw createError({ statusCode: 401 }) (or redirect) before returning data. This is the recommended pattern for islands regardless of this advisory.
  • Disable experimental.componentIslands if your app does not use the feature.
  • If your app must keep route-middleware-only auth, gate the /__nuxt_island/page_* URL prefix at your reverse proxy or in a server middleware.

Severity

  • CVSS Score: 6.3 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt: Route-rule middleware bypass via case-sensitivity mismatch between vue-router and the routeRules matcher

CVE-2026-53721 / GHSA-mm7m-92g8-7m47

More information

Details

Impact

Nuxt looks up routeRules for the current navigation by calling
getRouteRules({ path: to.path }) from the page-router plugin and the
no-pages router plugin. The compiled routeRules matcher (built on
rou3) performs case-sensitive matching, while vue-router is configured
with its default sensitive: false and matches paths case-insensitively.

The two routers therefore disagree on which rules apply to a given
request path: vue-router still matches the page record for
/Admin/dashboard, but the routeRules lookup for the same path
returns no match. Any appMiddleware declared via routeRules is never
added to the middleware set and never runs, on both SSR and client
navigations. The same path skips other path-keyed route rules in the
same way (ssr, redirect, appLayout, and the prerender / payload
hints used client-side).

For applications using routeRules with appMiddleware as an
authorization gate (a documented pattern), an attacker can flip the case
of any static segment in a protected URL (for example /Admin/dashboard
instead of /admin/dashboard) to render the protected page with the
middleware skipped. The server returns the fully server-rendered page
including any useFetch / useAsyncData results captured during SSR.

This is an instance of CWE-178 (Improper Handling of Case Sensitivity)
leading to CWE-863 (Incorrect Authorization) for apps that treat
appMiddleware as an authorization boundary.

Mitigating factors
  • Only affects apps that use routeRules.appMiddleware. The more
    idiomatic definePageMeta({ middleware }) is bound to the matched
    route record and is unaffected.
  • Nuxt route middleware is documented as an app-layer concern, not a
    server-side auth boundary; well-built apps enforce authorization
    again at the API / data-fetching layer.
  • Apps that explicitly set router.options.sensitive = true are not
    affected.
Patches

Fixed in nuxt@4.4.7 (commit 07e39cd6) and backported to nuxt@3.21.7 (commit 3f3e3fa7). The fix normalizes the path used for routeRules lookups so it matches vue-router's default case-insensitive semantics.

Workarounds

Until you can upgrade, you can mitigate by either:

  1. Setting router.options.sensitive = true so vue-router matches
    case-sensitively (this changes route-matching behaviour app-wide).
  2. Moving security-critical middleware off routeRules.appMiddleware
    and onto definePageMeta({ middleware: [...] }) on the protected
    page components, which is bound to the matched record.
  3. Enforcing authorization at the API / data-fetching layer (which you
    should be doing in any case).
Credit

Reported by Anthropic / Claude through Anthropic's coordinated
vulnerability disclosure process. Reference: ANT-2026-9FSEBYMC.

Severity

  • CVSS Score: 8.8 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Cross-site scripting via slot content in Nuxt's head components

GHSA-m3q2-p4fw-w38m

More information

Details

Impact

Nuxt's globally registered <NoScript> component (from @unhead/vue head components, re-exported by Nuxt) wrote its default-slot content to the innerHTML of the <noscript> head tag, bypassing the HTML escaping that {{ }} interpolation normally applies in Vue templates.

Applications that placed untrusted, attacker-controllable data inside a <NoScript> slot, for example:

<NoScript>{{ route.query.banner }}</NoScript>

would emit that value unescaped inside <noscript> in the server-rendered HTML. With scripting enabled, the HTML parser treats <noscript> content in <head> under the "in head noscript" insertion mode: any tag other than link, meta, noframes, or style implicitly closes <noscript> and is re-processed in the head. A payload such as <script>...</script> therefore escapes the element and executes in the document context.

Sibling head components (<Style>, <Title>) were not affected because they already routed slot text through the safe textContent path.

Affected versions

All currently supported versions of nuxt that ship the <NoScript> global component.

Patches

Fixed in nuxt@4.4.7 (commit 4b054e9d) and backported to nuxt@3.21.7 (commit 7fea9fd6). The fix escapes <NoScript> slot content with escapeHtml from @vue/shared and writes it to textContent rather than innerHTML. Slot content is now rendered as text; intentional markup inside <NoScript> is no longer parsed as HTML.

Workarounds

Until you can upgrade:

  • Do not interpolate untrusted input into <NoScript> slots. Replace <NoScript>{{ x }}</NoScript> with a static string, or sanitise / HTML-escape x at the source.
  • If you must render dynamic noscript content, write the tag yourself via useHead({ noscript: [{ textContent: escapedValue }] }) after escaping escapedValue.
Credit

Reported to Anthropic's coordinated vulnerability disclosure pipeline by Claude (Anthropic's AI assistant) and triaged by the Anthropic security team. Reference: ANT-2026-4NJYDFFM.

Independently reported by @​alcls01111 via GitHub's coordinated disclosure flow (GHSA-8grp-wcq9-925q), closed as a duplicate of this advisory.

Severity

  • CVSS Score: 2.3 / 10 (Low)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt: Unauthenticated out-of-memory crash via unbounded v-for expansion in island rendering

CVE-2026-71314 / GHSA-hxcr-hm88-mpq6

More information

Details

Impact

An unauthenticated attacker can crash a Nuxt server that renders any island / server component containing a v-for over a prop (for example v-for="n in count" or a <slot v-for>). Because the island URL hash is a non-secret digest of the request, the attacker can compute a valid hash for arbitrary props and send the iterated prop as a large integer. The server then expands the v-for to that many nodes during SSR, allocating memory proportional to the attacker's number. Reporter figures: count=8000000 produced a 142.9 MB response; count=40000000 (and items=4000000 on a slot list) produced an out-of-memory crash of the worker from a single ~130-byte request. Both the plain v-for path (Vue's ssrRenderList) and the slot path (vforToArray) are affected.

Patches

Fixed in nuxt@4.5.1 and nuxt@3.21.10. Island/server-component v-for sources are now clamped to a maximum iteration count (MAX_VFOR_LENGTH = 100000) at the render boundary, covering the plain path, the <slot v-for> element, and the vforToArray slot-props helper. Combined with the body-size cap (GHSA-9pgf-384g-p7mv), a single island render can no longer allocate without bound regardless of which v-for path is used or whether the prop arrives as an integer or an array.

Workarounds

Avoid v-for directly over an unclamped prop in server components, or clamp the count in the component (v-for="n in Math.min(count, 1000)"). A body-size limit in front of /__nuxt_island/ only mitigates array-shaped inputs, not the integer-amplification case.

References
  • Bound helper: packages/nuxt/src/app/components/vfor.ts
  • Transform: packages/nuxt/src/components/plugins/islands-transform.ts
  • Slot helper: packages/nuxt/src/app/components/utils.ts (vforToArray)

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt: Unauthorized Component Instantiation via Server Island Props

CVE-2026-71318 / GHSA-48hr-524c-v5w3

More information

Details

Impact

Nuxt server islands accept props via the /__nuxt_island/ endpoint. When an application has a server island component that forwards props directly into Vue's dynamic component resolution (<component :is>, resolveDynamicComponent, or h()), an attacker can pass a plain string value (rather than a component definition) to instantiate any globally-registered Vue component or any native HTML element.

For example:

{ "as": "SomeGlobalComponent" }

...resolves and renders SomeGlobalComponent if it is globally registered, even though the attacker should only be able to drive props for the island's declared component. Similarly, { "as": "iframe" } renders an <iframe> element.

Unlike the primary RCE vector (GHSA-9473-5f9j-94wq), this does not require vue.runtimeCompiler to be enabled. A plain string prop is sufficient to trigger component resolution. The template/render key guard that addresses the RCE vector does not block plain string values.

Some component libraries expose a polymorphic as / asChild prop that forwards its value into <component :is>; @nuxt/ui (via reka-ui) is a widely used example. An application is affected if such a component receives the attacker-controlled value inside a server island. Note this does not require explicit prop forwarding: island props the island component does not declare fall through as attributes onto its single root element, so an island whose root is a reka-ui / @nuxt/ui component receives the attacker's as value implicitly. Unlike the RCE vector, no vue.runtimeCompiler is required, which makes this vector reachable in more configurations. These libraries are not themselves vulnerable; they are noted only because they commonly provide the dynamic-component sink. Installing @nuxt/ui does not by itself register any component as a server island: the application must define the island (a .server.vue file).

Mitigating factors
  • Exploitation requires a server island component that puts an attacker-controlled value onto a dynamic-component path (<component :is>, resolveDynamicComponent, h(), or a polymorphic as / asChild prop), either explicitly or via attribute fallthrough when the island's root is such a component.
  • Reachable components are limited to what is actually in the island app's global registry. In a default pages-enabled app that is RouterView and RouterLink (both registered globally by the pages router plugin, which runs even in component islands), plus any components/global/ component and any component a module registers globally. RouterLink in particular renders an attacker-influenced <a> (and, because an island that declares no props forwards all props, to and other RouterLink props ride the same fallthrough). Vue built-ins (Transition, KeepAlive, Teleport, Suspense) and Nuxt auto-imports (ClientOnly, NuxtLink, NuxtPage, etc.) are NOT in the island app's global registry and cannot be resolved this way; an unresolved name instead renders as a native HTML element (the element-injection half of this issue).
  • Declaring the props an island accepts, or setting inheritAttrs: false on it, prevents an undeclared as from falling through to a polymorphic root and neutralizes this vector.
  • Arbitrary JavaScript execution is not possible through this vector (no template/render compilation).
  • Island component names are constrained to the build-time component registry; an attacker cannot resolve arbitrary components.
Affected versions

Nuxt >=3.1.0 <3.21.10 and >=4.0.0 <4.5.1, with component islands active. The island prop-forwarding behavior has existed since server islands were introduced in v3.1.0, and this vector does not depend on vue.runtimeCompiler. Nuxt 2 is not affected.

Note the patch (below) closes the implicit attribute-fallthrough path, which is the majority case. An island that explicitly forwards an untrusted prop into dynamic component resolution (or forwards it under a prop name other than as) remains the application's responsibility in every version; see Workarounds.

Patches

Fixed in nuxt@4.5.1 and nuxt@3.21.10. Patched releases reject a top-level as island prop (HTTP 400 at the /__nuxt_island/ endpoint). This closes the implicit path: island props an island does not declare fall through as attributes onto its single root, so a top-level as would otherwise reach a polymorphic root component's as prop (the reka-ui / @nuxt/ui convention) and drive dynamic component resolution without the author binding it. Rejecting the top-level as prop blocks that fallthrough while leaving nested data and other prop names untouched.

The framework deliberately does not attempt to block every case: it cannot safely tell a string used as data from one used as a component selector, and it has no island-local hook into Vue's h() or resolveDynamicComponent(). An island that explicitly forwards an untrusted value into <component :is> / h() / resolveDynamicComponent(), or that forwards it under a different polymorphic prop name, is therefore not covered by the patch and must follow the guidance below. The Nuxt documentation now warns against this.

Workarounds

Upgrade to nuxt@4.5.1 or nuxt@3.21.10. That upgrade also removes the related object-prop RCE (GHSA-9473-5f9j-94wq). In addition, in any version:

  1. Do not forward island props into <component :is>, resolveDynamicComponent, or h(). Map an untrusted discriminator through a closed allowlist of imported component definitions instead of passing the raw prop value.
  2. Declare the props an island accepts, or set inheritAttrs: false on it, so request input cannot fall through to a polymorphic root component.
  3. Avoid registering sensitive components globally that could leak information if instantiated by an attacker.

Severity

  • CVSS Score: 4.8 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt: Server-Side Remote Code Execution via Runtime Template Injection in Nuxt Server Island Props

CVE-2026-71320 / GHSA-9473-5f9j-94wq

More information

Details

Impact

Nuxt server islands accept props via the /__nuxt_island/ endpoint. When vue.runtimeCompiler: true is enabled (off by default) and the application has a server island component that forwards props into Vue's dynamic component resolution (<component :is>, resolveDynamicComponent, or h()), an attacker can inject a template key into the island props to achieve server-side remote code execution in the Nitro process.

{ "as": { "template": "<attacker-controlled>" } }

Vue's runtime template compiler compiles and executes the attacker-controlled template in the server process. The same primitive also works on the client side when the runtime compiler is active there, though the server-side path is the primary concern.

Some component libraries expose a polymorphic as / asChild prop that forwards its value into <component :is>; @nuxt/ui (via reka-ui) is a widely used example. An application is affected if such a component receives the attacker-controlled value, provided vue.runtimeCompiler is also enabled. Note this does not require the island author to explicitly forward a prop: island props that the island component does not declare fall through as attributes onto its single root element (standard Vue attribute inheritance), so an island whose root is a polymorphic component receives the attacker's as value implicitly. These libraries are not themselves vulnerable; they are noted only because they commonly provide the dynamic-component sink.

Common configurations that satisfy the preconditions

The flaw is in Nuxt core. The library below is not itself vulnerable; it is noted because it commonly provides the dynamic-component sink an application might inadvertently expose.

  • @nuxt/ui (via its underlying reka-ui primitives) exposes a polymorphic as / asChild prop that is forwarded into Vue's dynamic-component resolution. Installing @nuxt/ui does not by itself register any component as a server island. Exploitation requires the application to define an island component (a .server.vue file) whose rendered output puts the attacker-controlled value on such a component's as / asChild prop; with vue.runtimeCompiler: true, the attacker-controlled template is then compiled and executed. Because undeclared island props fall through as attributes to the single root, this can happen without any explicit binding: an island whose root is a reka-ui / @nuxt/ui component is enough. An example vulnerable island (the as value falls through to UButton, no explicit forwarding needed):

    <!-- components/MyWidget.server.vue -->
    <template>
      <UButton>Save</UButton>
    </template>

The island URL hash (/__nuxt_island/<Name>_<hash>.json) is a deterministic (unsalted) content hash, not an authentication token. It provides integrity relative to the URL but is not a security boundary: an attacker who knows the component name and desired props can compute a valid hash.

Mitigating factors
  • vue.runtimeCompiler is off by default in Nuxt. The vast majority of Nuxt applications are not affected.
  • Exploitation requires a second precondition: the application must have a server island component that puts an attacker-controlled value onto a dynamic-component path (<component :is>, resolveDynamicComponent, h(), or a polymorphic as / asChild prop). This can occur explicitly or via attribute fallthrough when the island's root is such a component.
  • SSG / static deployments are largely unreachable via this vector (no server process to exploit).
  • Island component names are constrained to the build-time component registry; an attacker cannot resolve arbitrary components.
Affected versions

Nuxt >=3.4.0 <3.21.10 and >=4.0.0 <4.5.1, and only when vue.runtimeCompiler: true and component islands are active. Earlier versions did not allow the Vue compiler to be enabled in the server bundle (the compiler dependencies have been mock-aliased on the server since v3.0.0-rc.1), so the runtime-compilation path is not reachable. Nuxt 2 is not affected (no server islands).

Patches

When vue.runtimeCompiler is enabled, island requests whose decoded props contain a template key at any depth are rejected with an HTTP 400 and a diagnostic suggesting the author rename the prop or disable the runtime compiler. The guard is gated on the runtime compiler being enabled, so the default configuration (compiler off) is unaffected and legitimate props that merely contain a template field (for example CMS content) continue to render. A render key is not rejected: island props arrive as JSON, so a render value can only be an inert string, which Vue ignores.

Fixed in nuxt@4.5.1 and backported to nuxt@3.21.10.

Workarounds

Upgrade to nuxt@4.5.1 or nuxt@3.21.10. If you cannot immediately upgrade, you can mitigate by:

  1. Ensure vue.runtimeCompiler is set to false (the default).
  2. Do not forward island props into <component :is>, resolveDynamicComponent, or h() without sanitization.
  3. As a defense-in-depth measure, deploy a WAF rule on /__nuxt_island/ that URL-decodes and JSON-parses the props value and blocks any object property named template or render in the decoded island props, inspecting both query and body for all methods. Note: this only covers direct and browser-originated island requests; initial-SSR internal island renders do not transit the edge and a WAF alone does not close the vector.

Severity

  • CVSS Score: 8.1 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt: Unauthenticated CPU exhaustion parsing and hashing the Nuxt island endpoint body before hash validation

CVE-2026-71321 / GHSA-9pgf-384g-p7mv

More information

Details

Impact

The internal island renderer endpoint (/__nuxt_island/...) decodes and hashes attacker-controlled request input before it validates the URL-resident hash. An unauthenticated POST /__nuxt_island/<name>_<anything>.json with a large JSON body (for example ~4.6 MB / 150k keys) is fully read, destr-parsed, and run through ohash before the request is rejected with a 400. Because Nitro runs on a single event loop, this both wastes CPU on the doomed request and delays every concurrent request. A low request rate is enough to degrade or stall the server. No valid hash and no authentication are required.

Patches

Fixed in nuxt@4.5.1 and nuxt@3.21.10. The island handler now enforces a raw body-size cap (413) and a JSON nesting-depth cap (400) before parsing or hashing, so oversized or deeply nested input is rejected cheaply.

Workarounds

Put a small request-body limit in front of /__nuxt_island/ at your reverse proxy / edge (islands legitimately send only a compact props payload), or disable server components if unused.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt: URL-handling weaknesses in navigateTo and reloadNuxtApp: SSR open redirect, client-side script execution via the open option, and protocol-relative bypass in reloadNuxtApp

CVE-2026-56326 / GHSA-c9cv-mq2m-ppp3

More information

Details

Summary

Three weaknesses in Nuxt's client-navigation URL handling, all reachable
from documented public APIs (navigateTo and reloadNuxtApp):

  1. SSR open redirect in navigateTo via path-normalisation bypass.
    navigateTo decided whether a target was external by inspecting the raw
    input with hasProtocol(..., { acceptRelative: true }). Inputs such as
    /..//evil.com, /.//evil.com, /%2e%2e//evil.com, or
    /app/..//evil.com slipped past that check because they start with
    /, but WHATWG URL parsing then normalised them to the
    protocol-relative pathname //evil.com. The normalised value was
    written to the Location response header and into the
    <meta http-equiv="refresh"> body of the SSR redirect page, so a
    victim's browser would resolve the redirect cross-origin to the
    attacker's host.

  2. Client-side script execution via navigateTo({ open: ... }). The
    client-side early-open handler called window.open(toPath, ...) without
    applying the isScriptProtocol check that gates the normal navigateTo
    path. A target of javascript:... (or another script-capable scheme)
    passed to navigateTo(url, { open: { ... } }) therefore executed in the
    application's origin instead of being rejected.

  3. Open redirect in reloadNuxtApp via protocol-relative bypass.
    reloadNuxtApp({ path }) rejects script-capable protocols by parsing
    the path with new URL(path, window.location.href) and checking the
    resolved protocol against isScriptProtocol. Protocol-relative paths
    such as //evil.com resolve to the current page's protocol (https:),
    which passes that check; the value is then assigned to
    window.location.href, which the browser treats as a cross-origin
    redirect. This is the same protocol-relative bypass family as (1), in
    a different sink.

Impact

For (1), the practical risk is phishing or OAuth-code theft against any
Nuxt app that forwards user-controlled input (for example a ?next=
query parameter on a login route) into navigateTo on the server. The
framework documents that navigateTo blocks external hosts unless
external: true is passed, so maintainers commonly rely on it as the
safe path for post-login redirects.

For (2), any app that passes a user-controlled URL into
navigateTo(url, { open: { ... } }) was vulnerable to reflected XSS in
the application's first-party origin.

For (3), any app that forwards user-controlled input into
reloadNuxtApp({ path }) could be redirected cross-origin for phishing
or OAuth-code theft, even on releases that already shipped the
isScriptProtocol guard added by #​35115.

Patches

Fixed in nuxt@4.4.7 and backported to nuxt@3.21.7. The three sinks
are addressed by:

Workarounds
  • For (1): validate redirect targets before passing them to navigateTo,
    for example reject any input where
    new URL(target, 'http://localhost').pathname starts with //, or
    only accept a known allow-list of paths.
  • For (2): reject any user-controlled URL whose protocol is not in an
    allow-list (typically just http: and https:) before passing it to
    navigateTo({ open: ... }).
  • For (3): same shape as (1). Reject paths starting with // (or where
    new URL(path, window.location.href).host !== window.location.host)
    before passing to reloadNuxtApp({ path }).
References
  • CWE-601: URL Redirection to Untrusted Site ('Open Redirect')
  • CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Credits

Reported by Anthropic / Claude as ANT-2026-S08HN6DH through Anthropic's
coordinated vulnerability disclosure programme.

The reloadNuxtApp protocol-relative bypass (sink 3) was independently
reported by @​alcls01111 via GitHub's
coordinated disclosure flow (GHSA-w7fp-2cfv-4837), closed as a
duplicate of this advisory.

Severity

  • CVSS Score: 5.1 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:L/VI:L/VA:N/SC:N/SI:L/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Nuxt: Reflected XSS in <NuxtLink> via unsanitised javascript: or data: URL

CVE-2026-53722 / GHSA-934w-87qh-qr26

More information

Details

Summary

<NuxtLink> did not validate the URL scheme of values bound to its to or href props before rendering them into the href attribute of the underlying <a> element. When an application binds attacker-controlled input (a query parameter, a CMS field, a user-supplied profile URL) to <NuxtLink :to> or :href, the attacker can supply a javascript: or vbscript: URL that is reflected verbatim into the rendered markup. Clicking the link executes the supplied script in the origin of the Nuxt application, resulting in reflected DOM-based cross-site scripting. A data:text/html,... payload reflected through the same sink does not execute in the application's origin but enables a same-tab phishing surface anchored to a l

Note

PR body was truncated to here.

@netlify

netlify Bot commented Mar 19, 2025

Copy link
Copy Markdown

Deploy Preview for enspire-staging failed.

Name Link
🔨 Latest commit 3886b46
🔍 Latest deploy log https://app.netlify.com/projects/enspire-staging/deploys/6a8eedbf0e87cd00088e529b

@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from f488d92 to f99d9a8 Compare April 1, 2025 10:25
@changeset-bot

changeset-bot Bot commented Apr 1, 2025

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 65cabe0

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from f99d9a8 to c5e8bfd Compare April 8, 2025 11:39
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from c5e8bfd to 84d9992 Compare April 24, 2025 09:07
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from 84d9992 to adbc0ff Compare May 19, 2025 19:57
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from adbc0ff to 85ba517 Compare May 28, 2025 09:22
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from 85ba517 to c1008a4 Compare June 4, 2025 10:48
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from c1008a4 to 3f1c8f8 Compare June 22, 2025 12:52
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from 3f1c8f8 to 171fc3d Compare July 2, 2025 21:32
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch 2 times, most recently from 2ad144c to 030c96a Compare August 13, 2025 13:43
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from 030c96a to d652a35 Compare August 19, 2025 18:45
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from d652a35 to 531e4f6 Compare August 31, 2025 11:11
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from 531e4f6 to e8956c4 Compare September 18, 2025 02:54
@renovate renovate Bot changed the title chore(deps): update dependency nuxt to v3.16.0 [security] chore(deps): update dependency nuxt to v3.19.0 [security] Sep 18, 2025
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from e8956c4 to e25aebe Compare September 25, 2025 13:59
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from e25aebe to a3607f4 Compare October 21, 2025 23:41
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from a3607f4 to c3f252f Compare November 11, 2025 00:41
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from c3f252f to d33e34c Compare November 18, 2025 16:04
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from d33e34c to 4421f84 Compare December 3, 2025 14:42
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from 4421f84 to f23ad05 Compare December 31, 2025 17:05
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from f23ad05 to 85aa1cf Compare January 8, 2026 17:32
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch 2 times, most recently from af061ef to 96c19f9 Compare January 24, 2026 07:05
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from 96c19f9 to f4cc29a Compare February 2, 2026 21:39
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch 2 times, most recently from d7468bc to d8f75fd Compare February 17, 2026 20:51
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from d8f75fd to 99e3bb2 Compare March 5, 2026 19:57
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from 99e3bb2 to b1deded Compare March 13, 2026 14:42
@renovate renovate Bot changed the title chore(deps): update dependency nuxt to v3.19.0 [security] chore(deps): update dependency nuxt to v3.19.0 [security] - autoclosed Mar 27, 2026
@renovate renovate Bot closed this Mar 27, 2026
@renovate
renovate Bot deleted the renovate/npm-nuxt-vulnerability branch March 27, 2026 02:13
@renovate renovate Bot changed the title chore(deps): update dependency nuxt to v3.19.0 [security] - autoclosed chore(deps): update dependency nuxt to v3.19.0 [security] Mar 30, 2026
@renovate renovate Bot reopened this Mar 30, 2026
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch 3 times, most recently from a8f8bf4 to 0a6b528 Compare April 1, 2026 20:54
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from 0a6b528 to 96a4196 Compare April 8, 2026 17:16
@renovate renovate Bot changed the title chore(deps): update dependency nuxt to v3.19.0 [security] chore(deps): update dependency nuxt to v3.19.0 [security] - autoclosed Apr 27, 2026
@renovate renovate Bot closed this Apr 27, 2026
@renovate renovate Bot changed the title chore(deps): update dependency nuxt to v3.19.0 [security] - autoclosed chore(deps): update dependency nuxt to v3.19.0 [security] Apr 27, 2026
@renovate renovate Bot reopened this Apr 27, 2026
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch 2 times, most recently from eec0ef3 to 473500b Compare April 29, 2026 10:59
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch 2 times, most recently from 288665c to 9e6c3fc Compare May 18, 2026 20:02
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch 2 times, most recently from 899b785 to ec4768a Compare June 1, 2026 22:50
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from ec4768a to e777bec Compare June 13, 2026 15:43
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch 2 times, most recently from cd2e983 to 81679dc Compare July 16, 2026 14:13
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch 2 times, most recently from b9fe4d0 to 2cc4140 Compare July 24, 2026 15:41
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from 2cc4140 to 33a3916 Compare July 30, 2026 16:55
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch 2 times, most recently from a57dd0a to 65cabe0 Compare August 14, 2026 19:07
@renovate
renovate Bot force-pushed the renovate/npm-nuxt-vulnerability branch from 65cabe0 to 3886b46 Compare August 26, 2026 13:44
@renovate renovate Bot changed the title chore(deps): update dependency nuxt to v3.19.0 [security] chore(deps): update dependency nuxt to v3.21.10 [security] Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants