Nuxt: route rules skipped when the path changes case
Nuxt's page router matches paths case-insensitively by default, but its server-side route rules matched case-sensitively. Changing the case of a single letter in the URL rendered the page with its redirect, security headers, proxy, and SSR rules all skipped.
- Package
- nuxt, @nuxt/nitro-server (npm)
- Affected
- nuxt 3.0.0 to < 4.6.2, @nuxt/nitro-server < 4.6.2
- Fixed in
- 4.6.2 (Nuxt 3 is end of life, no fix)
- Weakness
- CWE-178 Case sensitivity, CWE-863 Incorrect authorization
- Severity
- Moderate
- Advisory
- GHSA-vjh5-4x84-xrqp
What was the issue
Nuxt is a full-stack framework built on Vue. With the default router.options.sensitive: false, its page router resolves routes case-insensitively, so /Admin renders the same page as /admin. On the server, though, route rules were resolved by Nitro's matcher, which compares keys case-sensitively. A request whose path differed from a rule key only in letter case got the page with none of that rule applied.
export default defineNuxtConfig({
routeRules: {
'/admin/**': { redirect: '/login' },
},
})
With this configuration, /admin/secret redirects to /login, but /Admin/secret rendered app/pages/admin/secret.vue.
Two earlier advisories (GHSA-mm7m-92g8-7m47 and GHSA-hxvh-4h3w-prp9) had case-folded the rules the Nuxt app itself reads. This one covers the rules applied on the server.
Impact
- redirect: a rule meant to send visitors away from a page was skipped, and the page and its server-fetched data were rendered instead.
- headers: per-route response headers, including Content-Security-Policy and X-Frame-Options, were not sent.
- proxy: the request was not proxied.
- ssr: false and noScripts: the page was server-rendered, or shipped scripts, despite the rule.
getRouteRules(event)andmatchRouteRulesreturned no rules for the case variant.
Apps with router.options.sensitive: true in nuxt.config were not affected. Access checks in server middleware, route middleware attached with definePageMeta, or a page's data fetching were not affected either. Only protections that depended on a server-side route rule matching the request path were bypassed.
The fix
Nuxt now resolves server-side route rules itself, decoding and case-folding both the rule keys and the request path unless sensitive is set. The result feeds Nitro's redirect, headers, and proxy handling, the renderer, and the getRouteRules and matchRouteRules helpers.
Response caching (cache and swr) is still matched case-sensitively by Nitro's router, so a case variant gets its rules applied but is not cached. Rules a deployment preset compiles into platform config, such as Vercel's config.json or Netlify's _redirects, are matched by the platform.
What to do
Upgrade to [email protected] or later. Nuxt 3 reached end of life on 31 July 2026 and won't get a fix. If you can't upgrade, set router: { options: { sensitive: true } } in nuxt.config, enforce access control in middleware rather than with a redirect rule, and send security headers on every response rather than per route.
Disclosure timeline
Reported privately to the Nuxt maintainers. Also credited: Aeg1sx and jkrshnmenon.
Advisory published with the fix in nuxt 4.6.2.
The takeaway. When two layers decide whether a path matches, they have to agree on what a path is. Normalize case and encoding once, and make every layer use the same rules.