{"id":"GHSA-96x6-fvhx-49hg","kind":"vulnerability","aliases":["GHSA-96x6-fvhx-49hg"],"related":[],"title":"Nuxt exposes the message and data of unhandled errors thrown during server rendering","description":"### Summary\n\nWhen 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.\n\n### Impact\n\nAn application is affected if server-rendered code lets an error with sensitive details reach the error page, for example:\n\n```vue\n<script setup lang=\"ts\">\nconst route = useRoute()\n// the upstream error body, and the token in the URL, reach the client if this request fails\nawait $fetch(`http://users.internal/users/${route.params.id}?token=${useRuntimeConfig().usersToken}`)\n</script>\n```\n\nAnyone 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.\n\nThe same applies to any other error thrown during server rendering, such as a database driver error whose message names an internal host.\n\nErrors 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.\n\n### Patches\n\nFixed 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.\n\nNuxt 3 has reached end of life and will not receive a fix. Upgrade to Nuxt 4, or apply the workaround below.\n\n### Workarounds\n\nCatch errors from server-side requests and rethrow them with only the details the client may see:\n\n```ts\ntry {\n  await $fetch(url)\n} catch (error) {\n  throw createError({ statusCode: 502, statusMessage: 'Upstream request failed' })\n}\n```\n\nAlternatively, 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`):\n\n```ts\n// server/plugins/hide-error-details.ts\nexport default defineNitroPlugin((nitroApp) => {\n  nitroApp.hooks.hook('error', (error) => {\n    const cause = (error as { cause?: unknown }).cause\n    if (cause instanceof Error && !(cause.constructor as { __h3_error__?: boolean }).__h3_error__) {\n      Object.assign(error, { unhandled: true, data: undefined })\n    }\n  })\n})\n```\n\nIf secrets such as tokens in upstream URLs may have been exposed this way, rotate them.\n\n### Credits\n\nThanks to abhijit_dave for reporting this through the Vercel HackerOne program.","summary":"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.","severity":"medium","cvss":5.3,"epss":null,"exploited":false,"exploited_at":null,"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"],"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.\n- Look for server-rendered code that calls `$fetch` or throws internal errors with secrets or internal hosts in the URL or message.\n- 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.\n- 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.","status":"open","published_at":"2026-11-02T20:59:31+00:00","first_seen_at":"2026-11-09T22:53:16+00:00","triaged_at":"2026-11-09T22:56:31+00:00","updated_at":"2026-11-09T22:56:31+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-11-09T22:56:31Z","model":"claude-haiku-5-5","output":{"fix":"- `nuxt` `4.6.4` for Nuxt 4 users.\n- 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.","reach":"internet","title":"Nuxt: unhandled server errors leak their message and data to clients","impact":"medium","summary":"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.","audience":"developers","category":"package","nicknames":[],"who_cares":["nuxt","self-hosted","ssr"],"confidence":0.85,"fix_status":"available","needs_auth":"no","worst_case":"Secrets in failed request URLs and upstream error bodies readable by clients","analyst_note":"","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.\n- Look for server-rendered code that calls `$fetch` or throws internal errors with secrets or internal hosts in the URL or message.\n- Run the app in production mode and trigger a failing request to see whether the error page or JSON response includes the message.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["static-export","server-routes-only","default-config"],"who_is_affected":"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.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"unknown"},"cost_usd":0.000922,"input_tokens":5658,"output_tokens":713,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-209':27B '/users/1?token=...':234C '3':401C '4':51B,416C '4.6.4':53B,339C '49hg':18A,23A '502':235C,451C '96x6':16A,21A 'abhijit':537C 'add':457C 'affect':194C,268C,301C,334C 'alreadi':349C 'also':318C,470C 'altern':456C 'anyon':213C 'app':140C 'appli':271C,418C 'applic':192C 'attack':74B 'await':443C 'bodi':104B,184C,240C 'boolean':509C 'browser':63B 'call':477C 'catch':423C,446C 'caus':484C,495C,498C,500C,502C 'cause.constructor':505C 'client':154C,310C,396C,438C 'code':199C 'configur':261C 'const':494C 'creat':293C 'createerror':126C,296C,392C,449C,476C 'credit':534C 'custom':247C 'cwe':26B 'data':6A,60B,149C,161C,305C,516C 'databas':283C 'dave':538C 'default':260C,488C 'definenitroplugin':489C 'deliber':142C,294C 'design':328C 'detail':205C,252C,436C,464C,473C 'driver':284C 'end':404C 'error':9A,46B,56B,66B,71B,103B,115C,121C,130C,134C,157C,165C,188C,202C,208C,275C,285C,292C,311C,352C,357C,373C,382C,387C,424C,447C,466C,480C,492C,493C,496C,504C,508C,511C,513C 'error.vue':248C 'exampl':81B,211C,221C 'export':487C 'expos':2A,529C 'fail':93B,169C,219C,455C 'failur':79B 'fetch':170C,444C 'fix':336C,412C 'fvhx':17A,22A 'generic':367C 'get':231C 'ghsa':15A,20A 'ghsa-96x6-fvhx-49hg':14A,19A 'h3':507C,510C 'hackeron':545C 'happen':242C 'hide':462C,471C 'host':32B,291C 'http':45B,120C,263C,351C 'id':85B,225C 'impact':190C 'includ':95B,178C 'instanceof':503C 'instead':399C 'intern':290C 'json':70B,164C,360C 'let':200C 'life':406C 'line':265C 'longer':376C 'make':216C 'mark':342C 'may':397C,439C,526C 'meant':307C 'messag':4A,58B,147C,287C,303C,368C,379C 'middlewar':40B,112C 'name':288C 'new':386C 'nitro':459C 'nitroapp':490C 'nitroapp.hooks.hook':491C 'non':44B 'non-http':43B 'nuxt':1A,24A,25A,28B,50B,122C,160C,338C,340C,400C,415C,467C 'object.assign':512C 'one':138C 'packag':29B 'page':36B,67B,108C,131C,158C,209C,358C,374C 'part':254C,323C 'patch':335C 'payload':68B,159C,257C,326C 'plugin':37B,109C,460C 'product':145C,389C 'program':546C 'queri':97B,180C 'query-str':96B 'rais':141C 'reach':206C,403C 'read':91B 'receiv':230C,362C,410C 'regardless':243C 'reject':89B,229C 'render':13A,49B,128C,198C,249C,279C 'report':540C 'request':83B,177C,218C,223C,429C,454C 'respons':72B,166C,189C,239C,361C 'result':371C 'rethrow':431C 'return':312C 'rotat':532C 'rout':39B,111C,330C 'secret':519C 'see':398C,440C 'self':31B 'self-host':30B 'send':54B 'sensit':204C 'sent':151C 'server':12A,48B,197C,278C,329C,427C 'server-rend':196C 'server-sid':426C 'server/api':331C 'server/plugins/hide-error-details.ts':486C 'servic':88B,228C 'show':377C 'showerror':298C 'side':428C 'ssr':33B 'status':264C,364C 'statuscod':450C 'statusmessag':452C 'string':98B,181C 'summari':105C 'thank':535C 'throw':41B,113C,385C,390C,448C 'thrown':10A,276C,344C,383C 'token':99B,522C 'treat':136C 'tri':442C 'trigger':77B 'true':515C 'ts':441C,485C 'unchang':319C 'undefin':517C 'unhandl':8A,354C,514C 'unknown':499C 'upgrad':413C 'upstream':87B,102B,187C,227C,238C,453C,524C 'url':94B,174C,445C,525C 'useasyncdata':316C 'usefetch':314C 'users.internal':233C 'users.internal/users/1?token=...':232C 'valu':345C 'vercel':544C 'vue':212C 'way':531C 'whose':286C 'workaround':420C,422C 'wrap':123C,133C,468C","nicknames":[],"mitigations":[],"who_affected":"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.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["nuxt"],"ptokens":["nuxt"],"labels":["cwe209","medium","npm","nuxt","package","selfhosted","ssr"],"campaign_type":null,"search_a":"'49hg':18A,23A '96x6':16A,21A 'data':6A 'error':9A 'expos':2A 'fvhx':17A,22A 'ghsa':15A,20A 'ghsa-96x6-fvhx-49hg':14A,19A 'messag':4A 'nuxt':1A,24A,25A 'render':13A 'server':12A 'thrown':10A 'unhandl':8A","search_b":"'-209':27B '4':51B '4.6.4':53B 'attack':74B 'bodi':104B 'browser':63B 'cwe':26B 'data':60B 'error':46B,56B,66B,71B,103B 'exampl':81B 'fail':93B 'failur':79B 'host':32B 'http':45B 'id':85B 'includ':95B 'json':70B 'messag':58B 'middlewar':40B 'non':44B 'non-http':43B 'nuxt':28B,50B 'packag':29B 'page':36B,67B 'payload':68B 'plugin':37B 'queri':97B 'query-str':96B 'read':91B 'reject':89B 'render':49B 'request':83B 'respons':72B 'rout':39B 'self':31B 'self-host':30B 'send':54B 'server':48B 'servic':88B 'ssr':33B 'string':98B 'throw':41B 'token':99B 'trigger':77B 'upstream':87B,102B 'url':94B","search_c":"'/users/1?token=...':234C '3':401C '4':416C '4.6.4':339C '502':235C,451C 'abhijit':537C 'add':457C 'affect':194C,268C,301C,334C 'alreadi':349C 'also':318C,470C 'altern':456C 'anyon':213C 'app':140C 'appli':271C,418C 'applic':192C 'await':443C 'bodi':184C,240C 'boolean':509C 'call':477C 'catch':423C,446C 'caus':484C,495C,498C,500C,502C 'cause.constructor':505C 'client':154C,310C,396C,438C 'code':199C 'configur':261C 'const':494C 'creat':293C 'createerror':126C,296C,392C,449C,476C 'credit':534C 'custom':247C 'data':149C,161C,305C,516C 'databas':283C 'dave':538C 'default':260C,488C 'definenitroplugin':489C 'deliber':142C,294C 'design':328C 'detail':205C,252C,436C,464C,473C 'driver':284C 'end':404C 'error':115C,121C,130C,134C,157C,165C,188C,202C,208C,275C,285C,292C,311C,352C,357C,373C,382C,387C,424C,447C,466C,480C,492C,493C,496C,504C,508C,511C,513C 'error.vue':248C 'exampl':211C,221C 'export':487C 'expos':529C 'fail':169C,219C,455C 'fetch':170C,444C 'fix':336C,412C 'generic':367C 'get':231C 'h3':507C,510C 'hackeron':545C 'happen':242C 'hide':462C,471C 'host':291C 'http':120C,263C,351C 'id':225C 'impact':190C 'includ':178C 'instanceof':503C 'instead':399C 'intern':290C 'json':164C,360C 'let':200C 'life':406C 'line':265C 'longer':376C 'make':216C 'mark':342C 'may':397C,439C,526C 'meant':307C 'messag':147C,287C,303C,368C,379C 'middlewar':112C 'name':288C 'new':386C 'nitro':459C 'nitroapp':490C 'nitroapp.hooks.hook':491C 'nuxt':122C,160C,338C,340C,400C,415C,467C 'object.assign':512C 'one':138C 'page':108C,131C,158C,209C,358C,374C 'part':254C,323C 'patch':335C 'payload':159C,257C,326C 'plugin':109C,460C 'product':145C,389C 'program':546C 'queri':180C 'rais':141C 'reach':206C,403C 'receiv':230C,362C,410C 'regardless':243C 'reject':229C 'render':128C,198C,249C,279C 'report':540C 'request':177C,218C,223C,429C,454C 'respons':166C,189C,239C,361C 'result':371C 'rethrow':431C 'return':312C 'rotat':532C 'rout':111C,330C 'secret':519C 'see':398C,440C 'sensit':204C 'sent':151C 'server':197C,278C,329C,427C 'server-rend':196C 'server-sid':426C 'server/api':331C 'server/plugins/hide-error-details.ts':486C 'servic':228C 'show':377C 'showerror':298C 'side':428C 'status':264C,364C 'statuscod':450C 'statusmessag':452C 'string':181C 'summari':105C 'thank':535C 'throw':113C,385C,390C,448C 'thrown':276C,344C,383C 'token':522C 'treat':136C 'tri':442C 'true':515C 'ts':441C,485C 'unchang':319C 'undefin':517C 'unhandl':354C,514C 'unknown':499C 'upgrad':413C 'upstream':187C,227C,238C,453C,524C 'url':174C,445C,525C 'useasyncdata':316C 'usefetch':314C 'users.internal':233C 'users.internal/users/1?token=...':232C 'valu':345C 'vercel':544C 'vue':212C 'way':531C 'whose':286C 'workaround':420C,422C 'wrap':123C,133C,468C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-02T20:59:31+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-96x6-fvhx-49hg","url":"https://github.com/nuxt/nuxt/security/advisories/GHSA-96x6-fvhx-49hg","severity":"medium","published_at":"2026-11-02T20:59:31+00:00","modified_at":"2026-11-02T20:59:31+00:00","first_seen_at":"2026-11-09T22:53:16+00:00","fetched_at":"2026-11-09T22:53:16+00:00","note":null}],"documents":[{"id":110430,"url":"https://github.com/nuxt/nuxt/security/advisories/GHSA-96x6-fvhx-49hg","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}