can1357/oh-my-pi · error · ToolError

message (from validateRelativePath)

Error message

message (from validateRelativePath)

What it means

After decoding, the relative path is passed to validateRelativePath, which rejects unsafe forms (absolute paths, backslashes, traversal-prone segments, etc.). Whatever message that validator throws is re-wrapped as this ToolError, so the surfaced message describes the specific path-rule violated.

Source

Thrown at packages/coding-agent/src/tools/bash-skill-urls.ts:98

	// Combine any colon suffix (line range like ":1-5") with the path segment
	const rawPath = (parsed[2] ?? "") + (suffix ? `/${suffix}` : "");
	const hasRelativePath = rawPath !== "" && rawPath !== "/";

	if (!hasRelativePath) {
		return path.resolve(skill.baseDir);
	}

	let relativePath: string;
	try {
		relativePath = decodeURIComponent(rawPath.slice(1));
	} catch {
		throw new ToolError(`Invalid skill:// URL path encoding: ${url}`);
	}
	try {
		validateRelativePath(relativePath);
	} catch (err) {
		const message = err instanceof Error ? err.message : String(err);
		throw new ToolError(message);
	}

	const targetPath = path.join(skill.baseDir, relativePath);
	const resolvedPath = path.resolve(targetPath);
	const resolvedBaseDir = path.resolve(skill.baseDir);
	if (!resolvedPath.startsWith(resolvedBaseDir + path.sep) && resolvedPath !== resolvedBaseDir) {
		throw new ToolError("Path traversal is not allowed in skill:// URLs");
	}
	// Agent Plugin skills (§4.1): the resource must canonically resolve within
	// the plugin root. Fail closed: a dangling or unresolvable path is rejected
	// rather than handed to bash, where writing through it could create the
	// outside target. Symlinks may target other files inside the same package.
	if (skill.containRoot) {
		const contained = resolveContainedPathSync(skill.containRoot, resolvedPath);
		if (contained.status === "outside") {
			throw new ToolError(`skill:// path resolves outside the plugin root: ${url}`);
		}
		if (contained.status === "missing") {

View on GitHub (pinned to 9690622007)

Solutions

  1. Use a plain relative path rooted at the skill directory, e.g. skill://name/SKILL.md or skill://name/scripts/foo.py.
  2. Remove leading slashes, drive letters, backslashes, and ".." segments from the path.
  3. Reference other files through their own resource scheme instead of escaping the skill root.

Example fix

// before
resolveSkillUrlToPath("skill://my-skill/../../../etc/hosts", skills);

// after
resolveSkillUrlToPath("skill://my-skill/SKILL.md", skills);
Defensive patterns

Strategy: validation

Validate before calling

const rel = decodeURIComponent(rawPath.slice(1));
if (rel.startsWith("/") || rel.includes("\\") || rel.split("/").includes("..")) {
  throw new Error(`unsafe skill path: ${rel}`);
}

Try / catch

try {
  return resolveSkillUrlToPath(url, skills);
} catch (e) {
  if (e instanceof ToolError && /absolute|traversal|invalid path/i.test(e.message)) {
    // normalize the path (strip ../, leading /) and retry or reject
  } else throw e;
}

Prevention

When it happens

Trigger: A skill:// URL whose decoded path fails validation — e.g. "skill://name//etc/passwd", "skill://name/C:\\x", "skill://name/./../secret", or a path containing null bytes / illegal characters per validateRelativePath.

Common situations: A model attempts to reach files outside the skill directory via clever paths; Windows-style paths pasted into URLs; empty or "/"-only path segments built by naive concatenation.

Related errors


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