Nuxt: Dev server exposes built source over LAN to malicious sites (incomplete fix for GHSA-4gf7-ff8x-hq99)
Description
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.
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 tolocalhost(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.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Nuxt dev server with webpack/rspack builders exposes source code to LAN attackers due to incomplete fetch metadata check, affecting versions using --host.
Vulnerability
This is an incomplete fix for GHSA-4gf7-ff8x-hq99 [3][4]. The Nuxt dev server used a same-origin check based on the Sec-Fetch-Mode and Sec-Fetch-Site headers to prevent cross-origin requests from stealing source code. However, browsers only send these headers for potentially trustworthy origins (e.g., HTTPS). When the dev server is bound to a non-loopback address via --host, the server is accessible over HTTP on the LAN, making it a non-trustworthy origin. This allows the check to be bypassed. The vulnerability affects Nuxt projects using the webpack or rspack builder, where the dev server is started with --host and is accessible from the local network. Any version that received the previous fix for GHSA-4gf7-ff8x-hq99 is also affected [3][4].
Exploitation
An attacker requires network access to the same LAN as the developer. The developer must have the Nuxt dev server running with --host (e.g., nuxt dev --host 0.0.0.0) and must visit a malicious website on the same network. The attacker serves a script that creates a ` tag pointing to the dev server's URL, such as http://192.168.0.31:3000/_nuxt/app.js. When the script loads, the webpack chunk is executed in the browser, and the attacker can access the global webpackChunk` variable to iterate over all modules and extract their source code. No authentication or user interaction beyond visiting the malicious site is required [3][4].
Impact
Successful exploitation allows an attacker to steal the full source code of the Nuxt application served by the dev server. This includes Vue components, server-side logic, API keys, secrets, and other sensitive information embedded in the frontend code. The attacker gains information disclosure but does not achieve remote code execution or privilege escalation on the server. The scope of compromise is limited to the application's frontend source code exposed during development [3][4].
Mitigation
As of the advisory publication, no complete fix is available. The Nuxt team has released a pull request that refactors the same-origin check for dev middleware [1], but it is not yet merged. Users should avoid using --host on the dev server; instead, bind only to localhost (loopback) to prevent network exposure. For Chrome 142+ (and other Chromium browsers), the vulnerability may be mitigated because those browsers enforce stricter fetch metadata policies. Regularly check the GitHub advisory GHSA-6m52-m754-pw2g for updates and apply the fix when available [3][4].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
29c5d0e457fc6refactor(rspack,webpack): extract same-origin check for dev middleware (#35051)
1 file changed · +25 −2
packages/webpack/src/webpack.ts+25 −2 modified@@ -137,9 +137,8 @@ async function createDevMiddleware (compiler: Compiler) { // TODO: implement upstream in `webpack-dev-middleware` function wdmToH3Handler (devMiddleware: webpackDevMiddleware.API<IncomingMessage, ServerResponse>) { return defineEventHandler(async (event) => { - // disallow cross-site requests in no-cors mode const { req, res } = 'runtime' in event ? event.runtime!.node! : event.node - if (req.headers['sec-fetch-mode'] === 'no-cors' && req.headers['sec-fetch-site'] === 'cross-site') { + if (!isSameOriginRequest(req)) { res!.statusCode = 403 res!.end('Forbidden') return @@ -170,6 +169,30 @@ function wdmToH3Handler (devMiddleware: webpackDevMiddleware.API<IncomingMessage }) } +// `Sec-Fetch-Site` is not sent in every context, so fall back to comparing the +// initiator (`Origin` / `Referer`) host against the request's `Host`. +function isSameOriginRequest (req: { headers: Record<string, string | string[] | undefined> }): boolean { + const site = firstHeader(req.headers['sec-fetch-site']) + if (site !== undefined) { + return site === 'same-origin' || site === 'none' + } + + const initiator = firstHeader(req.headers.origin) || firstHeader(req.headers.referer) + if (!initiator) { + return true + } + + try { + return new URL(initiator).host === firstHeader(req.headers.host) + } catch { + return false + } +} + +function firstHeader (value: string | string[] | undefined): string | undefined { + return Array.isArray(value) ? value[0] : value +} + async function compile (compiler: Compiler) { const nuxt = useNuxt()
3d253326854frefactor(rspack,webpack): extract same-origin check for dev middleware (#35051)
1 file changed · +25 −2
packages/webpack/src/webpack.ts+25 −2 modified@@ -137,9 +137,8 @@ async function createDevMiddleware (compiler: Compiler) { // TODO: implement upstream in `webpack-dev-middleware` function wdmToH3Handler (devMiddleware: webpackDevMiddleware.API<IncomingMessage, ServerResponse>) { return defineEventHandler(async (event) => { - // disallow cross-site requests in no-cors mode const { req, res } = 'runtime' in event ? event.runtime!.node! : event.node - if (req.headers['sec-fetch-mode'] === 'no-cors' && req.headers['sec-fetch-site'] === 'cross-site') { + if (!isSameOriginRequest(req)) { res!.statusCode = 403 res!.end('Forbidden') return @@ -170,6 +169,30 @@ function wdmToH3Handler (devMiddleware: webpackDevMiddleware.API<IncomingMessage }) } +// `Sec-Fetch-Site` is not sent in every context, so fall back to comparing the +// initiator (`Origin` / `Referer`) host against the request's `Host`. +function isSameOriginRequest (req: { headers: Record<string, string | string[] | undefined> }): boolean { + const site = firstHeader(req.headers['sec-fetch-site']) + if (site !== undefined) { + return site === 'same-origin' || site === 'none' + } + + const initiator = firstHeader(req.headers.origin) || firstHeader(req.headers.referer) + if (!initiator) { + return true + } + + try { + return new URL(initiator).host === firstHeader(req.headers.host) + } catch { + return false + } +} + +function firstHeader (value: string | string[] | undefined): string | undefined { + return Array.isArray(value) ? value[0] : value +} + async function compile (compiler: Compiler) { const nuxt = useNuxt()
Vulnerability mechanics
Root cause
"The same-origin check in the webpack/rspack dev middleware relied solely on Sec-Fetch-* headers, which are not sent by browsers for non-potentially-trustworthy origins, allowing cross-site script inclusion to bypass the check."
Attack vector
An attacker on the same network hosts a malicious website that creates a `<script>` tag pointing to the victim's Nuxt dev server (e.g., `http://192.168.0.31:3000/_nuxt/app.js`). Because the dev server is bound to a non-loopback address (`--host`) and the request originates from a non-potentially-trustworthy origin, the browser does not send `Sec-Fetch-Site` or `Sec-Fetch-Mode` headers. The original check [patch_id=898957] only blocked requests where `sec-fetch-mode === 'no-cors'` and `sec-fetch-site === 'cross-site'`, so the missing headers caused the check to be skipped entirely. The injected script executes in the victim's browser context, and the attacker reads the webpack module source code via the global `webpackChunk*` variable.
Affected code
The vulnerable code is in `packages/webpack/src/webpack.ts` within the `wdmToH3Handler` function. The original check at line 140 only blocked requests where `sec-fetch-mode === 'no-cors'` and `sec-fetch-site === 'cross-site'`, which failed when these headers were absent. The patch replaces this with a call to the new `isSameOriginRequest()` function and adds the `firstHeader()` helper.
What the fix does
The patch replaces the narrow `sec-fetch-mode`/`sec-fetch-site` check with a new `isSameOriginRequest()` function [patch_id=898957]. When `Sec-Fetch-Site` is present, it accepts `same-origin` or `none` values. When absent (the bypass scenario), it falls back to comparing the `Origin` or `Referer` header's host against the request's `Host` header. This closes the bypass because `Origin`/`Referer` are sent even for non-potentially-trustworthy origins, so cross-site requests are correctly rejected regardless of whether fetch metadata headers are available.
Preconditions
- networkAttacker and victim must be on the same network (e.g., LAN).
- configThe Nuxt dev server must be bound to a non-loopback address (e.g., using `nuxt dev --host`).
- inputThe victim must use a webpack or rspack builder (not the default Vite builder).
- authNo authentication is required; the dev server serves assets without access control.
Generated on May 20, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.