can1357/oh-my-pi · error · ToolError

Absolute paths are not allowed in ${scheme}:// URLs: ${rawPa

Error message

Absolute paths are not allowed in ${scheme}:// URLs: ${rawPath}

What it means

Internal-URL paths are treated as relative to the scheme's registered root; absolute paths inside a `scheme://` URL are rejected by `resolveUnderRoot` because they would bypass root confinement. This is a sandbox security boundary: only paths relative to the mounted root are legal.

Source

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

	if (!root) {
		throw new ToolError(`Protocol paths are not supported by ${op}(): ${rawPath}`);
	}
	return resolveUnderRoot(scheme, root, match[2], rawPath);
}

/** 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);

View on GitHub (pinned to 9690622007)

Solutions

  1. Drop the leading slash: use `local://report.md` instead of `local:///report.md`.
  2. For genuine absolute filesystem locations, pass the absolute path directly (no scheme:// prefix) instead of an internal URL.
  3. Express the target relative to the scheme's mounted root directory.

Example fix

// before
await read("local:///etc/passwd");
// after
await read("/etc/passwd"); // or a root-relative: await read("local://reports/q3.md");
Defensive patterns

Strategy: validation

Validate before calling

const rel = url.replace(/^\w+:\/\//, "").replaceAll("\\", "/");
if (rel.startsWith("/") || /^[A-Za-z]:/.test(rel)) throw new Error("absolute path not allowed in scheme URL");

Try / catch

try {
	return await read(url);
} catch (err) {
	if (String(err?.message).startsWith("Absolute paths are not allowed")) {
		const rel = url.replace(/^\w+:\/\/+/, "");
		return await read(`${url.slice(0, url.indexOf("://"))}://${rel.replace(/^\/+/, "")}`);
	}
	throw err;
}

Prevention

When it happens

Trigger: Calling read()/write() with paths like `local:///etc/passwd` or `local://C:/data/file.txt` — the portion after `scheme://` begins with `/` (or is a Windows drive path) after decoding and backslash normalization.

Common situations: Prepending `/` out of habit when constructing scheme URLs; mixing ordinary absolute file paths with protocol syntax; generated code that joins scheme prefix with an absolute path variable.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/55615a805e4dba59. Report an issue: GitHub.