{"record":{"id":"88fbf6706581cf51","repo":"can1357/oh-my-pi","slug":"message-from-validaterelativepath","errorCode":null,"errorMessage":"message (from validateRelativePath)","messagePattern":"message \\(from validateRelativePath\\)","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/bash-skill-urls.ts","lineNumber":98,"sourceCode":"\t// Combine any colon suffix (line range like \":1-5\") with the path segment\n\tconst rawPath = (parsed[2] ?? \"\") + (suffix ? `/${suffix}` : \"\");\n\tconst hasRelativePath = rawPath !== \"\" && rawPath !== \"/\";\n\n\tif (!hasRelativePath) {\n\t\treturn path.resolve(skill.baseDir);\n\t}\n\n\tlet relativePath: string;\n\ttry {\n\t\trelativePath = decodeURIComponent(rawPath.slice(1));\n\t} catch {\n\t\tthrow new ToolError(`Invalid skill:// URL path encoding: ${url}`);\n\t}\n\ttry {\n\t\tvalidateRelativePath(relativePath);\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\tthrow new ToolError(message);\n\t}\n\n\tconst targetPath = path.join(skill.baseDir, relativePath);\n\tconst resolvedPath = path.resolve(targetPath);\n\tconst resolvedBaseDir = path.resolve(skill.baseDir);\n\tif (!resolvedPath.startsWith(resolvedBaseDir + path.sep) && resolvedPath !== resolvedBaseDir) {\n\t\tthrow new ToolError(\"Path traversal is not allowed in skill:// URLs\");\n\t}\n\t// Agent Plugin skills (§4.1): the resource must canonically resolve within\n\t// the plugin root. Fail closed: a dangling or unresolvable path is rejected\n\t// rather than handed to bash, where writing through it could create the\n\t// outside target. Symlinks may target other files inside the same package.\n\tif (skill.containRoot) {\n\t\tconst contained = resolveContainedPathSync(skill.containRoot, resolvedPath);\n\t\tif (contained.status === \"outside\") {\n\t\t\tthrow new ToolError(`skill:// path resolves outside the plugin root: ${url}`);\n\t\t}\n\t\tif (contained.status === \"missing\") {","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/bash-skill-urls.ts#L80-L116","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nresolveSkillUrlToPath(\"skill://my-skill/../../../etc/hosts\", skills);\n\n// after\nresolveSkillUrlToPath(\"skill://my-skill/SKILL.md\", skills);","handlingStrategy":"validation","validationCode":"const rel = decodeURIComponent(rawPath.slice(1));\nif (rel.startsWith(\"/\") || rel.includes(\"\\\\\") || rel.split(\"/\").includes(\"..\")) {\n  throw new Error(`unsafe skill path: ${rel}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  return resolveSkillUrlToPath(url, skills);\n} catch (e) {\n  if (e instanceof ToolError && /absolute|traversal|invalid path/i.test(e.message)) {\n    // normalize the path (strip ../, leading /) and retry or reject\n  } else throw e;\n}","preventionTips":["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."],"tags":["path-validation","security","skills"],"backgroundTag":"invalid-path-format","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}