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

  1. Sanitize the path to the allowed charset: strip shell metacharacters, backslashes, and absolute prefixes.
  2. Resolve any `..` segments beforehand and pass only paths inside the agent directory.
  3. Rename files with spaces/special characters, or refer to them via a safe generated name.
  4. 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

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


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)