{"record":{"id":"416fae3071779d8a","repo":"withastro/astro","slug":"invalid-url-encoding","errorCode":null,"errorMessage":"Invalid URL encoding","messagePattern":"Invalid URL encoding","errorType":"http","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"packages/astro/src/core/util/pathname.ts","lineNumber":44,"sourceCode":" * encoded several times ends up as a single, final path. This stops someone\n * from sneaking a path like `/admin` past middleware by encoding it multiple\n * times — middleware always sees the real, decoded path.\n *\n * @param pathname - The path to decode\n * @returns The final, fully decoded path\n * @throws Error if the path has broken encoding that can't be decoded at all\n *   (for example a lone `%` that isn't followed by two hex digits)\n * @throws MultiLevelEncodingError if the path is still changing after\n *   {@link MAX_DECODE_ITERATIONS} tries (it was encoded too many times).\n *   Handing back a half-decoded path here would bring back the security hole\n *   this function exists to close.\n */\nexport function validateAndDecodePathname(pathname: string): string {\n\tlet decoded: string;\n\ttry {\n\t\tdecoded = decodeURI(pathname);\n\t} catch (_e) {\n\t\tthrow new Error('Invalid URL encoding');\n\t}\n\t// Keep decoding until the path stops changing. A path can be encoded more\n\t// than once (for example %2561 → %61 → a), and we want the final decoded\n\t// path so the rest of Astro — especially middleware security checks —\n\t// always sees the same real path, no matter how many times it was encoded.\n\tlet iterations = 0;\n\twhile (decoded !== pathname) {\n\t\t// The path is still changing after the maximum number of tries, so it\n\t\t// was encoded too many times for us to fully decode. Stop and reject\n\t\t// it: handing back a half-decoded path could let middleware check one\n\t\t// path while a later decode (during rewrite routing) turns it into a\n\t\t// different, possibly protected, path.\n\t\tif (iterations >= MAX_DECODE_ITERATIONS) {\n\t\t\tthrow new MultiLevelEncodingError();\n\t\t}\n\t\tpathname = decoded;\n\t\ttry {\n\t\t\tdecoded = decodeURI(pathname);","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/withastro/astro/blob/d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca/packages/astro/src/core/util/pathname.ts#L26-L62","documentation":"validateAndDecodePathname decodes the request path so middleware always sees the real path. If the very first decodeURI throws (the path has broken percent-encoding, e.g. a lone '%' not followed by two hex digits), it throws a plain Error 'Invalid URL encoding'. Note: a separate MultiLevelEncodingError exists for paths encoded too many times (more than MAX_DECODE_ITERATIONS = 10).","triggerScenarios":"An incoming URL containing a malformed percent-escape such as '/foo%zz', '/search?q=%', '/a%2', or '/bar%'. decodeURI cannot parse these and throws.","commonSituations":"Hand-built links with a stray '%'; a proxy/CDN rewriting URLs incorrectly; user input containing '%' passed unencoded into a link; double-processing that leaves a dangling escape.","solutions":["Fix the source link/redirect to properly percent-encode the value (e.g. encodeURIComponent).","Ensure upstream proxies pass the path through without corrupting escapes.","Validate/normalize incoming URLs at the edge before they reach Astro."],"exampleFix":"// before\n<a href={`/search?q=${raw}`}>search</a>  // raw may contain '%'\n// after\n<a href={`/search?q=${encodeURIComponent(raw)}`}>search</a>","handlingStrategy":"try-catch","validationCode":"function isDecodable(pathname) {\n  try { decodeURI(pathname); return true; } catch { return false; }\n}\nif (!isDecodable(req.url.pathname)) {\n  return new Response('Invalid URL encoding', { status: 400 });\n}","typeGuard":"const isDecodablePath = (p: string): boolean => {\n  try { decodeURI(p); return true; } catch { return false; }\n};","tryCatchPattern":"try {\n  validateAndDecodePathname(pathname);\n} catch (e) {\n  return new Response('Bad Request', { status: 400 });\n}","preventionTips":["Always encodeURIComponent dynamic URL segments when building links.","Validate/normalize paths at the edge or in middleware before routing.","Reject malformed escapes early with a 400 instead of letting them reach routing."],"tags":["routing","url","security","request"],"backgroundTag":null,"analyzedSha":"d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca","analyzedAt":"2026-08-12T13:37:29.035Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}