GHSA-9V7C-Q64Q-VV33

Vulnerability from github – Published: 2026-10-30 23:00 – Updated: 2026-10-30 23:00
VLAI
Summary
Nuxt: Dot segments in the request path bypass `server/middleware` path checks and server route rules on page routes
Details

Summary

Since 4.6.0, Nuxt resolves . and .. segments in the request path, including percent-encoded forms such as %2e, before rendering a page. Nitro's routing, Nitro's route rules and server/middleware see the path with those segments still in place. A request for /%2e/admin therefore renders the page at /admin, while server-side checks keyed on /admin do not apply.

Impact

An unauthenticated attacker can bypass server-side controls that match on the request path of a page route.

server/middleware that checks event.path (or event.node.req.url) is skipped:

export default defineEventHandler((event) => {
  if (event.path.startsWith('/admin') && !isAuthenticated(event)) {
    throw createError({ statusCode: 401 })
  }
})

With this middleware, /admin returns 401, but /%2e/admin, /.%2e/admin and /x/%2e%2e/admin reach the middleware with event.path set to /./admin, /../admin and /x/../admin, and render app/pages/admin.vue along with the data it fetches during server rendering.

Server-side redirect and headers route rules are skipped in the same way, so '/admin/**': { redirect: '/login' } does not redirect /%2e/admin/secret, and per-route security headers such as Content-Security-Policy are not sent.

Not affected:

  • API handlers in server/api and server/routes. Nitro's router does not resolve dot segments, so these requests do not reach the handler.
  • appMiddleware route rules and middleware declared with definePageMeta, which are matched against the resolved route.
  • Nuxt 3.x and 4.0.0 to 4.5.x, which pass the path to the page router unchanged, so these requests 404.

Browsers resolve dot segments before sending a request, so exploitation requires a client that sends the path as written.

Patches

Fixed in nuxt@4.6.3 and @nuxt/nitro-server@4.6.3. A page request whose path contains . or .. segments now receives a 404 instead of rendering the page the path resolves to, and route-rule keys and lookup paths have their dot segments resolved, including percent-encoded spellings, before they are compared.

Workarounds

Reject dot segments in a server middleware that runs before your other middleware (middleware runs in file name order):

// server/middleware/0.dot-segments.ts
export default defineEventHandler((event) => {
  if (/(?:^|\/)\.{1,2}(?:\/|$)/.test(event.path.split('?')[0])) {
    throw createError({ statusCode: 404 })
  }
})

Checking getRequestURL(event).pathname instead of event.path is not a sufficient workaround: it resolves dot segments, but keeps percent-encoding such as %61 that the page router decodes. Path checks in server middleware should also account for the page router matching paths case-insensitively unless router.options.sensitive is set.

A reverse proxy or CDN that normalises the request path before forwarding it also removes the mismatch.

Credits

Thanks to @vinzabe, @nikhilagrawalsf (Nikhil Agrawal, Mettle Labs), @0xjunwei and @kq5y, who reported this issue independently.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "nuxt"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.6.0"
            },
            {
              "fixed": "4.6.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@nuxt/nitro-server"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.6.0"
            },
            {
              "fixed": "4.6.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-119200"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-41",
      "CWE-436",
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-30T23:00:53Z",
    "nvd_published_at": "2026-10-29T21:17:18Z",
    "severity": "MODERATE"
  },
  "details": "### Summary\n\nSince `4.6.0`, Nuxt resolves `.` and `..` segments in the request path, including percent-encoded forms such as `%2e`, before rendering a page. Nitro\u0027s routing, Nitro\u0027s route rules and `server/middleware` see the path with those segments still in place. A request for `/%2e/admin` therefore renders the page at `/admin`, while server-side checks keyed on `/admin` do not apply.\n\n### Impact\n\nAn unauthenticated attacker can bypass server-side controls that match on the request path of a page route.\n\n`server/middleware` that checks `event.path` (or `event.node.req.url`) is skipped:\n\n```ts\nexport default defineEventHandler((event) =\u003e {\n  if (event.path.startsWith(\u0027/admin\u0027) \u0026\u0026 !isAuthenticated(event)) {\n    throw createError({ statusCode: 401 })\n  }\n})\n```\n\nWith this middleware, `/admin` returns 401, but `/%2e/admin`, `/.%2e/admin` and `/x/%2e%2e/admin` reach the middleware with `event.path` set to `/./admin`, `/../admin` and `/x/../admin`, and render `app/pages/admin.vue` along with the data it fetches during server rendering.\n\nServer-side `redirect` and `headers` route rules are skipped in the same way, so `\u0027/admin/**\u0027: { redirect: \u0027/login\u0027 }` does not redirect `/%2e/admin/secret`, and per-route security headers such as `Content-Security-Policy` are not sent.\n\nNot affected:\n\n- API handlers in `server/api` and `server/routes`. Nitro\u0027s router does not resolve dot segments, so these requests do not reach the handler.\n- `appMiddleware` route rules and middleware declared with `definePageMeta`, which are matched against the resolved route.\n- Nuxt `3.x` and `4.0.0` to `4.5.x`, which pass the path to the page router unchanged, so these requests 404.\n\nBrowsers resolve dot segments before sending a request, so exploitation requires a client that sends the path as written.\n\n### Patches\n\nFixed in `nuxt@4.6.3` and `@nuxt/nitro-server@4.6.3`. A page request whose path contains `.` or `..` segments now receives a 404 instead of rendering the page the path resolves to, and route-rule keys and lookup paths have their dot segments resolved, including percent-encoded spellings, before they are compared.\n\n### Workarounds\n\nReject dot segments in a server middleware that runs before your other middleware (middleware runs in file name order):\n\n```ts\n// server/middleware/0.dot-segments.ts\nexport default defineEventHandler((event) =\u003e {\n  if (/(?:^|\\/)\\.{1,2}(?:\\/|$)/.test(event.path.split(\u0027?\u0027)[0])) {\n    throw createError({ statusCode: 404 })\n  }\n})\n```\n\nChecking `getRequestURL(event).pathname` instead of `event.path` is not a sufficient workaround: it resolves dot segments, but keeps percent-encoding such as `%61` that the page router decodes. Path checks in server middleware should also account for the page router matching paths case-insensitively unless `router.options.sensitive` is set.\n\nA reverse proxy or CDN that normalises the request path before forwarding it also removes the mismatch.\n\n### Credits\n\nThanks to @vinzabe, @nikhilagrawalsf (Nikhil Agrawal, Mettle Labs), @0xjunwei and @kq5y, who reported this issue independently.",
  "id": "GHSA-9v7c-q64q-vv33",
  "modified": "2026-10-30T23:00:53Z",
  "published": "2026-10-30T23:00:53Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/nuxt/nuxt/security/advisories/GHSA-9v7c-q64q-vv33"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-119200"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nuxt/nuxt/commit/45463f8b58487e387149ebfb704d6a0834b3a450"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/nuxt/nuxt"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nuxt/nuxt/releases/tag/v4.6.3"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Nuxt: Dot segments in the request path bypass `server/middleware` path checks and server route rules on page routes"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
How durations are calculated

Following the VTEM framework (Black Hat USA 2015), time to patch starts when the vendor or a coordinator was informed, total time to patch at discovery, and disclosure to patch at public disclosure; vendor response time runs from the report to its acknowledgement. Time to exploit and time unpatched start at the earliest discovery or report, falling back to public disclosure. Unpatched time ends at the first recorded patch, or the calculation time when no patch is recorded. Exploitation to patch shows the interval between those two events; a published proof of concept is a public exploit, not exploitation.

Each result shows its source dates. A negative interval means the end event came first; when date-only inputs leave the order open, the result says so. Date-only inputs are approximate. The earliest evidence wins: KEV report dates and sightings are labelled as such. Records describing another vulnerability are listed but never calculated from, and a PoC alone does not establish exploitation. Missing patch evidence does not prove that no patch exists. Filters below change the event list only.

Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.