---
id: "GHSA-96x6-fvhx-49hg"
page: "https://infrastack.ai/live/GHSA-96x6-fvhx-49hg"
kind: "vulnerability"
aliases: ["GHSA-96x6-fvhx-49hg"]
title: "Nuxt exposes the message and data of unhandled errors thrown during server rendering"
severity: "medium"
cvss: 5.3
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "nuxt", "fixed": ["4.6.4"], "affected": ">= 4.0.0, < 4.6.4", "ecosystem": "npm"}, {"name": "nuxt", "fixed": [], "affected": ">= 3.0.0, < 4.0.0", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["cwe-209", "nuxt", "package", "self-hosted", "ssr"]
not_affected_if: ["default-config", "server-routes-only", "static-export"]
published_at: "2026-11-02T20:59:31+00:00"
first_seen_at: "2026-11-09T22:53:16+00:00"
updated_at: "2026-11-09T22:56:31+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/nuxt/nuxt/security/advisories/GHSA-96x6-fvhx-49hg", "first_seen_at": "2026-11-09T22:53:16+00:00"}]
exploit_sources: []
---

# Nuxt exposes the message and data of unhandled errors thrown during server rendering

Link for people: https://infrastack.ai/live/GHSA-96x6-fvhx-49hg (this .md is for agents)

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.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:nuxt affected >= 4.0.0, < 4.6.4 fixed 4.6.4
- npm:nuxt affected >= 3.0.0, < 4.0.0 fixed no fixed version listed

## How to check

- Check the Nuxt version in the project with `npm ls nuxt`; versions `>= 4.0.0` and `< 4.6.4`, or any `3.x`, are affected.
- Look for server-rendered code that calls `$fetch` or throws internal errors with secrets or internal hosts in the URL or message.
- Run the app in production mode and trigger a failing request to see whether the error page or JSON response includes the message.

## Fix

- `nuxt` `4.6.4` for Nuxt 4 users.
- Nuxt 3 has reached end of life and will not receive a fix; the sources say to upgrade to Nuxt 4 or apply the workaround.

## Timeline

- 2026-11-09T22:53:16+00:00 ghsa-repo https://github.com/nuxt/nuxt/security/advisories/GHSA-96x6-fvhx-49hg

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/nuxt/nuxt/security/advisories/GHSA-96x6-fvhx-49hg

## From the sources

### Summary

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.

### Impact

An application is affected if server-rendered code lets an error with sensitive details reach the error page, for example:

```vue
<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.

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.

### Patches

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.

### Workarounds

Catch errors from server-side requests and rethrow them with only the details the client may see:

```ts
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`):

```ts
// 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.

### Credits

Thanks to abhijit_dave for reporting this through the Vercel HackerOne program.

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
