{"id":"CVE-2025-64765","aliases":["GHSA-ggxq-hp9w-j794"],"url":"https://o3.security/vulnerability/CVE-2025-64765","summary":"Astro 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) => {\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<img width=\"1920\" height=\"1025\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7e0eeecd-607a-4c73-b12e-5977a30c9bc4\" />\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 :)","published":"2025-11-19T16:41:19.022Z","modified":"2026-10-28T02:31:01.166561690Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"astro","fixedVersion":"5.15.8"}],"fix":{"url":"https://github.com/withastro/astro/commit/6f800813516b07bbe12c666a92937525fddb58ce","label":"withastro/astro@6f80081"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2025/64xxx/CVE-2025-64765.json"},{"type":"ADVISORY","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":"FIX","url":"https://github.com/withastro/astro/commit/6f800813516b07bbe12c666a92937525fddb58ce"},{"type":"PACKAGE","url":"https://github.com/withastro/astro"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-28T02:31:01.166561690Z"}}