NUXT · GHSA-96x6-fvhx-49hg · Package
FIX → 4.6.4
When a page, plugin or route middleware throws a non-HTTP error during server rendering, Nuxt 4 before 4.6.4 sends that error's message and data to the browser in the error page payload and JSON error responses. An attacker who can trigger the failure, for example by requesting an id an upstream service rejects, can read the failed URL including query-string tokens and the upstream error body.
first public Nov 2, 2026 · 1 source
Could this affect your app?
Free first check from your website. Connect your repo for the full answer.
Applications built with Nuxt 4 before 4.6.4, or any Nuxt 3 app, that run server-rendered code which can throw errors containing sensitive details such as tokens in upstream URLs or internal hostnames. Errors created with createError or showError, server routes under server/api, and useFetch or useAsyncData errors are not affected by this flaw.
Not affected if default-config, server-routes-only, static-export
Who cares nuxt, self-hosted, ssr
npm ls nuxt; versions >= 4.0.0 and < 4.6.4, or any 3.x, are affected.$fetch or throws internal errors with secrets or internal hosts in the URL or message.When a page, plugin or route middleware throws an error that is not an HTTP error, Nuxt wraps it with createError before rendering the error page. The wrapped error was treated as one the app raised deliberately, so in production its message and data were sent to the client in the error page payload (__NUXT_DATA__) and in JSON error responses. For a failed $fetch, that is the URL that was requested (including its query string) and the body of the upstream error response.
An application is affected if server-rendered code lets an error with sensitive details reach the error page, for example:
<script setup lang="ts">
const route = useRoute()
// the upstream error body, and the token in the URL, reach the client if this request fails
await $fetch(`http://users.internal/users/${route.params.id}?token=${useRuntimeConfig().usersToken}`)
</script>
Anyone who can make the request fail (for example by requesting an id the upstream service rejects) receives [GET] "http://users.internal/users/1?token=...": 502 and the upstream response body. This happens regardless of what a custom error.vue renders, because the details are part of the payload. With the default configuration, the HTTP status line is not affected.
Check it with your coding agent
It reads this page and tells you if your code is affected.
Are you affected?
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
Upgrade to 4.6.4
nuxt 4.6.4 for Nuxt 4 users.The same applies to any other error thrown during server rendering, such as a database driver error whose message names an internal host.
Errors created deliberately with createError (or showError) are not affected: their message and data are meant for the client. Errors returned by useFetch and useAsyncData are also unchanged, as they are part of the payload by design. Server routes (server/api) are not affected.
Fixed in nuxt@4.6.4. Nuxt now marks any thrown value that is not already an HTTP error as unhandled, so the error page and JSON responses receive its status and a generic message. As a result, the error page no longer shows the message of an error thrown with throw new Error(...) in production; throw a createError with what the client may see instead.
Nuxt 3 has reached end of life and will not receive a fix. Upgrade to Nuxt 4, or apply the workaround below.
Catch errors from server-side requests and rethrow them with only the details the client may see:
try {
await $fetch(url)
} catch (error) {
throw createError({ statusCode: 502, statusMessage: 'Upstream request failed' })
}
Alternatively, add a Nitro plugin that hides the details of errors Nuxt wrapped. This also hides the details of a createError called with an Error (or with a cause):
// server/plugins/hide-error-details.ts
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('error', (error) => {
const cause = (error as { cause?: unknown }).cause
if (cause instanceof Error && !(cause.constructor as { __h3_error__?: boolean }).__h3_error__) {
Object.assign(error, { unhandled: true, data: undefined })
}
})
})
If secrets such as tokens in upstream URLs may have been exposed this way, rotate them.
Thanks to abhijit_dave for reporting this through the Vercel HackerOne program.