{"record":{"id":"9ba1596d0bd120df","repo":"can1357/oh-my-pi","slug":"invalid-url-encoding-in-scheme-path-rawpa","errorCode":null,"errorMessage":"Invalid URL encoding in ${scheme}:// path: ${rawPath}","messagePattern":"Invalid URL encoding in (.+?):// path: (.+?)","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/eval/js/shared/helpers.ts","lineNumber":128,"sourceCode":"function resolveHelperPath(ctx: HelperContext, rawPath: string, op: \"read\" | \"write\"): string {\n\tconst match = INTERNAL_URL_RE.exec(rawPath);\n\tif (!match) return resolvePath(ctx, rawPath);\n\tconst scheme = match[1].toLowerCase();\n\tconst root = ctx.localRoots()[scheme];\n\tif (!root) {\n\t\tthrow new ToolError(`Protocol paths are not supported by ${op}(): ${rawPath}`);\n\t}\n\treturn resolveUnderRoot(scheme, root, match[2], rawPath);\n}\n\n/** Resolve an internal-URL relative path under its root, mirroring the host\n *  local-protocol handler: decode, reject absolute/traversal, confine to root. */\nfunction resolveUnderRoot(scheme: string, root: string, rawRelative: string, rawPath: string): string {\n\tlet relative: string;\n\ttry {\n\t\trelative = decodeURIComponent(rawRelative.replaceAll(\"\\\\\", \"/\"));\n\t} catch {\n\t\tthrow new ToolError(`Invalid URL encoding in ${scheme}:// path: ${rawPath}`);\n\t}\n\tconst rootPath = path.resolve(root);\n\tif (relative === \"\") return rootPath;\n\tif (path.isAbsolute(relative)) {\n\t\tthrow new ToolError(`Absolute paths are not allowed in ${scheme}:// URLs: ${rawPath}`);\n\t}\n\tconst normalized = path.normalize(relative);\n\tif (normalized.startsWith(\"..\") || normalized.includes(\"/../\") || normalized.includes(\"/..\")) {\n\t\tthrow new ToolError(`Path traversal (..) is not allowed in ${scheme}:// URLs: ${rawPath}`);\n\t}\n\tconst resolved = path.resolve(rootPath, normalized);\n\tif (resolved !== rootPath && !resolved.startsWith(`${rootPath}${path.sep}`)) {\n\t\tthrow new ToolError(`${scheme}:// path escapes its root: ${rawPath}`);\n\t}\n\treturn resolved;\n}\n\nasync function resolveRegularFile(","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/js/shared/helpers.ts#L110-L146","documentation":"When resolving an internal-URL path (e.g. `local://a%20b.md`) under its registered root, `resolveUnderRoot` decodes percent-escapes via decodeURIComponent. Malformed escapes (a bare `%` or truncated `%zz` sequence) make decoding throw, and the resolver surfaces this ToolError instead of a raw URIError.","triggerScenarios":"Calling read()/write() with an internal-URL path containing invalid percent-encoding, such as `local://100%.md` or `local://a%2.md`.","commonSituations":"Interpolating raw user/filename content into a scheme:// URL without encodeURIComponent; copy-pasting URLs that were truncated; template strings containing literal `%` characters.","solutions":["Percent-encode the path segment: `write(`local://${encodeURIComponent(name)}`, data)`.","Remove or escape stray `%` characters in the path.","If building URLs from file names, encodeURIComponent each segment rather than the whole string with unsafe characters."],"exampleFix":"// before\nawait write(`local://${name}.md`, data); // name = \"100% done\"\n// after\nawait write(`local://${encodeURIComponent(name)}.md`, data);","handlingStrategy":"validation","validationCode":"function safeSegment(name: string): string {\n\tconst enc = encodeURIComponent(name);\n\tdecodeURIComponent(enc); // throws on impossible cases; encodeURIComponent output is always valid\n\treturn enc;\n}","typeGuard":null,"tryCatchPattern":"try {\n\treturn await read(url);\n} catch (err) {\n\tif (String(err?.message).includes(\"Invalid URL encoding\")) {\n\t\tconst [scheme, rest] = url.split(\"://\");\n\t\treturn await read(`${scheme}://${encodeURIComponent(decodeURI(rest))}`);\n\t}\n\tthrow err;\n}","preventionTips":["encodeURIComponent path segments interpolated into scheme:// URLs.","Never interpolate raw filenames containing `%` unescaped.","Encode per segment, not the whole URL (keep `://` intact)."],"tags":["url-encoding","path-resolution","sandbox","validation"],"backgroundTag":"malformed-percent-encoding","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}