{"id":"GHSA-ggxq-hp9w-j794","summary":"Astro's middleware authentication checks based on url.pathname can be bypassed via url encoded values","details":"A mismatch exists between how Astro normalizes request paths for routing/rendering and how the application’s middleware reads the path for validation checks. Astro internally applies `decodeURI()` to determine which route to render, while the middleware uses `context.url.pathname` without applying the same normalization (decodeURI).\n\nThis discrepancy may allow attackers to reach protected routes (e.g., /admin) using encoded path variants that pass routing but bypass validation checks.\n\nhttps://github.com/withastro/astro/blob/ebc4b1cde82c76076d5d673b5b70f94be2c066f3/packages/astro/src/vite-plugin-astro-server/request.ts#L40-L44\n\n```js\n/** The main logic to route dev server requests to pages in Astro. */\nexport async function handleRequest({\n    pipeline,\n    routesList,\n    controller,\n    incomingRequest,\n    incomingResponse,\n}: HandleRequest) {\n    const { config, loader } = pipeline;\n    const origin = `${loader.isHttps() ? 'https' : 'http'}://${\n        incomingRequest.headers[':authority'] ?? incomingRequest.headers.host\n    }`;\n\n    const url = new URL(origin + incomingRequest.url);\n    let pathname: string;\n    if (config.trailingSlash === 'never' && !incomingRequest.url) {\n        pathname = '';\n    } else {\n        // We already have a middleware that checks if there's an incoming URL that has invalid URI, so it's safe\n        // to not handle the error: packages/astro/src/vite-plugin-astro-server/base.ts\n        pathname = decodeURI(url.pathname); // here this url is for routing/rendering\n    }\n\n    // Add config.base back to url before passing it to SSR\n    url.pathname = removeTrailingForwardSlash(config.base) + url.pathname; // this is used for middleware context\n```\n\nConsider an application having the following middleware code:\n\n```js\nimport { defineMiddleware } from \"astro/middleware\";\n\nexport const onRequest = defineMiddleware(async (context, next) =\u003e {\n  const isAuthed = false;  // simulate no auth\n  if (context.url.pathname === \"/admin\" && !isAuthed) {\n    return context.redirect(\"/\");\n  }\n  return next();\n});\n```\n\n`context.url.pathname` is validated , if it's equal to `/admin` the `isAuthed` property must be true for the next() method to be called. The same example can be found in the official docs https://docs.astro.build/en/guides/authentication/\n\n\n`context.url.pathname` returns the raw version which is `/%61admin` while pathname which is used for routing/rendering `/admin`, this creates a path normalization mismatch.\n\nBy sending the following request, it's possible to bypass the middleware check\n\n```\nGET /%61dmin HTTP/1.1\nHost: localhost:3000\n```\n\n\u003cimg width=\"1920\" height=\"1025\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7e0eeecd-607a-4c73-b12e-5977a30c9bc4\" /\u003e\n\n\n**Remediation**\n\nEnsure middleware context has  the same normalized pathname value that Astro uses internally, because any difference could allow it to bypass such checks. In short maybe something like this\n\n```diff\n        pathname = decodeURI(url.pathname);\n    }\n\n    // Add config.base back to url before passing it to SSR\n-    url.pathname = removeTrailingForwardSlash(config.base) + url.pathname;\n+    url.pathname = removeTrailingForwardSlash(config.base) + decodeURI(url.pathname);\n```\n\nThank you, let @Sudistark know if any more info is needed. Happy to help :)","aliases":["CVE-2025-64765"],"modified":"2026-01-30T02:45:34.702078Z","published":"2025-11-19T20:03:21Z","related":["CVE-2025-66202"],"database_specific":{"cwe_ids":["CWE-22"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2025-11-19T20:03:21Z","nvd_published_at":"2025-11-19T17:15:52Z"},"references":[{"type":"WEB","url":"https://github.com/withastro/astro/security/advisories/GHSA-ggxq-hp9w-j794"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-64765"},{"type":"WEB","url":"https://github.com/withastro/astro/commit/6f800813516b07bbe12c666a92937525fddb58ce"},{"type":"PACKAGE","url":"https://github.com/withastro/astro"}],"affected":[{"package":{"name":"astro","ecosystem":"npm","purl":"pkg:npm/astro"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"5.15.8"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2025/11/GHSA-ggxq-hp9w-j794/GHSA-ggxq-hp9w-j794.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N"}]}