can1357/oh-my-pi · error · ToolError

Path traversal (..) is not allowed in ${scheme}:// URLs: ${r

Error message

Path traversal (..) is not allowed in ${scheme}:// URLs: ${rawPath}

What it means

`resolveUnderRoot` normalizes the decoded relative path and rejects any result containing `..` segments (leading `..`, `/../`, or trailing `/..`). This blocks parent-directory traversal inside `scheme://` URLs, keeping sandbox file access confined to the mounted root — a deliberate security guard, not a bug.

Source

Thrown at packages/coding-agent/src/eval/js/shared/helpers.ts:137

}

/** Resolve an internal-URL relative path under its root, mirroring the host
 *  local-protocol handler: decode, reject absolute/traversal, confine to root. */
function resolveUnderRoot(scheme: string, root: string, rawRelative: string, rawPath: string): string {
	let relative: string;
	try {
		relative = decodeURIComponent(rawRelative.replaceAll("\\", "/"));
	} catch {
		throw new ToolError(`Invalid URL encoding in ${scheme}:// path: ${rawPath}`);
	}
	const rootPath = path.resolve(root);
	if (relative === "") return rootPath;
	if (path.isAbsolute(relative)) {
		throw new ToolError(`Absolute paths are not allowed in ${scheme}:// URLs: ${rawPath}`);
	}
	const normalized = path.normalize(relative);
	if (normalized.startsWith("..") || normalized.includes("/../") || normalized.includes("/..")) {
		throw new ToolError(`Path traversal (..) is not allowed in ${scheme}:// URLs: ${rawPath}`);
	}
	const resolved = path.resolve(rootPath, normalized);
	if (resolved !== rootPath && !resolved.startsWith(`${rootPath}${path.sep}`)) {
		throw new ToolError(`${scheme}:// path escapes its root: ${rawPath}`);
	}
	return resolved;
}

async function resolveRegularFile(
	ctx: HelperContext,
	rawPath: string,
): Promise<{ filePath: string; file: Bun.BunFile; size: number }> {
	const filePath = resolveHelperPath(ctx, rawPath, "read");
	const file = Bun.file(filePath);
	const stat = await file.stat();
	if (stat.isDirectory()) {
		throw new ToolError(`Directory paths are not supported by read(): ${filePath}`);
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Reference files within the mounted root using paths with no `..` segments.
  2. Ask the host to mount the directory you actually need as an additional localRoot instead of traversing up.
  3. Use absolute plain paths only if the environment permits them and drop the scheme:// form.

Example fix

// before
const s = await read("local://../project/config.json");
// after
const s = await read("/abs/path/project/config.json"); // or mount project as a root and use local://project/config.json
Defensive patterns

Strategy: validation

Validate before calling

const rel = decodeURIComponent(url.replace(/^\w+:\/\//, "").replaceAll("\\", "/"));
if (rel.split("/").includes("..")) throw new Error("traversal not allowed");

Try / catch

try {
	return await read(url);
} catch (err) {
	if (String(err?.message).includes("Path traversal")) {
		// do not sanitize-and-retry silently: log and surface to the user
		throw new Error(`blocked traversal attempt: ${url}`);
	}
	throw err;
}

Prevention

When it happens

Trigger: Calling read()/write() with traversal paths such as `local://../secrets.txt`, `local://a/../../etc/passwd`, or `local://dir/..%2f..%2fx` (decodes to `..` segments).

Common situations: LLM-generated eval code attempting to reach files outside the artifacts root; constructing paths by concatenating untrusted input; attempting to walk up from the session's mounted directory.

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 can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/50b84f11743d443f. Report an issue: GitHub.