{"id":"CVE-2026-45669","aliases":["GHSA-fx6j-w5w5-h468"],"url":"https://o3.security/vulnerability/CVE-2026-45669","summary":"Nuxt: Reflected XSS in `navigateTo()` external redirect","details":"### Summary\n`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.\n\nThis 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 `>`.\n\n### Impact\nApplications 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.\n\n### Details\nIn `packages/nuxt/src/app/composables/router.ts`, the SSR redirect path builds an HTML response body with only `\"` percent-encoded in the destination URL:\n\n```ts\nconst encodedLoc = location.replace(/\"/g, '%22')\nnuxtApp.ssrContext!['~renderResponse'] = {\nstatus: sanitizeStatusCode(options?.redirectCode || 302, 302),\nbody: `<!DOCTYPE html><html><head><meta http-equiv=\"refresh\" content=\"0; url=${encodedLoc}\"></head></html>`,\nheaders: { location: encodeURL(location, isExternalHost) },\n}\n```\n\nThe `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.\n\n### Proof of concept\n\nGlobal middleware that forwards a query parameter to `navigateTo`:\n\n```ts\n// middleware/redirect.global.ts\nexport default defineNuxtRouteMiddleware((to) => {\nconst next = to.query.next as string | undefined\nif (next) {\n return navigateTo(next, { external: true })\n}\n})\n```\n\nRequest:\n\n```\nGET /?next=https://evil.example/x><img src=x onerror=alert(document.domain)>\n```\n\nResponse body:\n\n```html\n<!DOCTYPE html><html><head><meta http-equiv=\"refresh\" content=\"0; url=https://evil.example/x><img src=x onerror=alert(document.domain)>\"></head></html>\n```\n\nThe `>` after `evil.example/x` terminates the `content=\"…\"` attribute, and the `<img onerror>` tag executes JavaScript in the application's origin before any redirect\noccurs.\n\n### Patches\nFixed in `nuxt@4.4.6` and `nuxt@3.21.6` by [#35052](https://github.com/nuxt/nuxt/pull/35052). The fix percent-encodes the full set of HTML-attribute-significant characters (`&`, `\"`, `'`, `<`, `>`) before interpolating the URL into the meta-refresh body\n\n### Workarounds\nIf 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).","published":"2026-06-12T12:51:42.640Z","modified":"2026-08-12T03:51:13.619414387Z","cvss":null,"epss":{"score":0.00177,"percentile":0.0744,"asOf":"2026-08-15"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"nuxt","fixedVersion":"3.21.6"},{"ecosystem":"npm","name":"nuxt","fixedVersion":"4.4.6"}],"fix":{"url":"https://github.com/nuxt/nuxt/pull/35052","label":"nuxt/nuxt#35052"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/45xxx/CVE-2026-45669.json"},{"type":"ADVISORY","url":"https://github.com/nuxt/nuxt/security/advisories/GHSA-fx6j-w5w5-h468"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-45669"},{"type":"FIX","url":"https://github.com/nuxt/nuxt/pull/35052"},{"type":"PACKAGE","url":"https://github.com/nuxt/nuxt"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:13.619414387Z"}}