Hmbown/CodeWhale · error · ExecError
refusing unsafe remote path
Error message
refusing unsafe remote path: ${JSON.stringify(p)} What it means
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.
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.
Example fix
// before
await transport.upload({ path: input }); // input = "../../etc/passwd" or "my file.txt"
// after
const safe = input.replaceAll(/[^A-Za-z0-9/._-]/g, "_").replaceAll(/\.\./g, "_");
await transport.upload({ path: safeRemotePath(safe) }); Defensive patterns
Strategy: validation
Validate before calling
function looksLikeSafeRemotePath(p) {
return typeof p === "string" &&
/^[A-Za-z0-9.][A-Za-z0-9/._-]{0,511}$/.test(p) &&
!p.includes("..");
} Type guard
function isSafeRemotePath(p) {
return typeof p === "string" &&
/^[A-Za-z0-9.][A-Za-z0-9/._-]{0,511}$/.test(p) &&
!p.includes("..");
} Try / catch
try {
await transport.upload({ path: userPath });
} catch (e) {
if (e instanceof Error && e.message.startsWith("refusing unsafe remote path")) {
// sanitize the path (charset whitelist, no '..', no metacharacters) and retry
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Paths copied from Windows (`C:\\...` or backslashes), absolute paths (`/tmp/foo`), spaces in filenames, `../` traversal from a model/tool output, or empty string arguments.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- audited skill path does not match owned package
- bundle path escapes the config directory via a symlink…
- bundle path is absolute; only paths inside the config…
- bundle path contains a NUL byte; refused
- external credential path must be absolute
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/ebb38621367d5367.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/transport.mjs:41
export function closeAppSession({ releaseOnly = false } = {}) {
if (!usedApp || appSessionClosed) return Promise.resolve();
return appSessionRequest({ tool: releaseOnly ? "release_session_input" : "close_session", sessionId: SESSION_ID }, { timeoutMs: 2_500, signal: null }).then((reply) => {
if (!reply?.ok) throw Object.assign(new ExecError(reply?.error?.message ?? "Computer input cleanup failed"), { code: reply?.error?.code ?? "input_release_failed" });
if (!releaseOnly) appSessionClosed = true;
});
}
export function b64(obj) {
return Buffer.from(JSON.stringify(obj), "utf8").toString("base64");
}
/**
* Validate a remote-side filesystem path we construct ourselves.
* Blocks shell metacharacters and traversal outside the agent dir.
*/
export function safeRemotePath(p) {
if (typeof p !== "string" || !/^[A-Za-z0-9.][A-Za-z0-9/._-]{0,511}$/.test(p) || p.includes("..")) {
throw new ExecError(`refusing unsafe remote path: ${JSON.stringify(p)}`);
}
return p;
}
/**
* Local executor bound to a platform backend name.
* All backends receive this shape.
*/
export function localExec() {
return {
kind: "local",
run,
runOk,
runInputLease,
async readFile(p) { return fs.promises.readFile(p); },
async writeFile(p, data) { return fs.promises.writeFile(p, data); },
tmpFile(prefix) {
return path.join(fs.mkdtempSync(path.join(os.tmpdir(), prefix)), "out");View on GitHub (pinned to 73e0f67d83)