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
- Use a plain relative path rooted at the skill directory, e.g. skill://name/SKILL.md or skill://name/scripts/foo.py.
- Remove leading slashes, drive letters, backslashes, and ".." segments from the path.
- 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
- Treat skill:// paths as strictly relative to the skill root — no .., no leading /, no drive letters.
- Sanitize model-generated paths before building the URL.
- Prefer enumerating skill files (via list) over guessing paths.
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
- Absolute paths are not allowed in omp:// URLs
- Codex Security bundle locations must be repository-relative:
- Path traversal is not allowed in skill:// URLs
- skill:// path resolves outside the plugin root: ${url}
- Invalid ASAR member path '${formatArchivePathForError(rawPat
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/88fbf6706581cf51.
Report an issue: GitHub.