{"record":{"id":"ebb38621367d5367","repo":"Hmbown/CodeWhale","slug":"refusing-unsafe-remote-path-json-stringify-p","errorCode":null,"errorMessage":"refusing unsafe remote path: ${JSON.stringify(p)}","messagePattern":"refusing unsafe remote path: (.+?)","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/transport.mjs","lineNumber":41,"sourceCode":"export function closeAppSession({ releaseOnly = false } = {}) {\n  if (!usedApp || appSessionClosed) return Promise.resolve();\n  return appSessionRequest({ tool: releaseOnly ? \"release_session_input\" : \"close_session\", sessionId: SESSION_ID }, { timeoutMs: 2_500, signal: null }).then((reply) => {\n    if (!reply?.ok) throw Object.assign(new ExecError(reply?.error?.message ?? \"Computer input cleanup failed\"), { code: reply?.error?.code ?? \"input_release_failed\" });\n    if (!releaseOnly) appSessionClosed = true;\n  });\n}\n\nexport function b64(obj) {\n  return Buffer.from(JSON.stringify(obj), \"utf8\").toString(\"base64\");\n}\n\n/**\n * Validate a remote-side filesystem path we construct ourselves.\n * Blocks shell metacharacters and traversal outside the agent dir.\n */\nexport function safeRemotePath(p) {\n  if (typeof p !== \"string\" || !/^[A-Za-z0-9.][A-Za-z0-9/._-]{0,511}$/.test(p) || p.includes(\"..\")) {\n    throw new ExecError(`refusing unsafe remote path: ${JSON.stringify(p)}`);\n  }\n  return p;\n}\n\n/**\n * Local executor bound to a platform backend name.\n * All backends receive this shape.\n */\nexport function localExec() {\n  return {\n    kind: \"local\",\n    run,\n    runOk,\n    runInputLease,\n    async readFile(p) { return fs.promises.readFile(p); },\n    async writeFile(p, data) { return fs.promises.writeFile(p, data); },\n    tmpFile(prefix) {\n      return path.join(fs.mkdtempSync(path.join(os.tmpdir(), prefix)), \"out\");","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/transport.mjs#L23-L59","documentation":"Thrown by safeRemotePath() when a remote-side filesystem path does not match the strict allow-list (alphanumeric start, only [A-Za-z0-9/._-], max 512 chars, no \"..\"). The library constructs these paths itself; rejecting here blocks shell metacharacter injection and traversal outside the agent directory before anything reaches a remote shell.","triggerScenarios":"Calling a transport/remote API with a user- or model-supplied path argument that contains characters like spaces, quotes, `$`, leading `/`, a `..` segment, or exceeds 512 characters, so the regex test or the `p.includes(\"..\")` check fails.","commonSituations":"Paths copied from Windows (`C:\\\\...` or backslashes), absolute paths (`/tmp/foo`), spaces in filenames, `../` traversal from a model/tool output, or empty string arguments.","solutions":["Sanitize the path to the allowed charset: strip shell metacharacters, backslashes, and absolute prefixes.","Resolve any `..` segments beforehand and pass only paths inside the agent directory.","Rename files with spaces/special characters, or refer to them via a safe generated name.","If you control the flow, generate remote filenames yourself (e.g. a fixed name plus random hex) instead of echoing user input."],"exampleFix":"// before\nawait transport.upload({ path: input }); // input = \"../../etc/passwd\" or \"my file.txt\"\n// after\nconst safe = input.replaceAll(/[^A-Za-z0-9/._-]/g, \"_\").replaceAll(/\\.\\./g, \"_\");\nawait transport.upload({ path: safeRemotePath(safe) });","handlingStrategy":"validation","validationCode":"function looksLikeSafeRemotePath(p) {\n  return typeof p === \"string\" &&\n    /^[A-Za-z0-9.][A-Za-z0-9/._-]{0,511}$/.test(p) &&\n    !p.includes(\"..\");\n}","typeGuard":"function isSafeRemotePath(p) {\n  return typeof p === \"string\" &&\n    /^[A-Za-z0-9.][A-Za-z0-9/._-]{0,511}$/.test(p) &&\n    !p.includes(\"..\");\n}","tryCatchPattern":"try {\n  await transport.upload({ path: userPath });\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"refusing unsafe remote path\")) {\n    // sanitize the path (charset whitelist, no '..', no metacharacters) and retry\n  }\n}","preventionTips":["Never pass raw user/model strings as remote paths; whitelist-sanitize first.","Resolve `..` segments and restrict to the agent directory before calling.","Generate remote filenames programmatically instead of echoing input.","Convert Windows-style paths (backslashes, drive letters) before use."],"tags":["security","path-validation","injection"],"backgroundTag":"path-traversal-blocked","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}