{"record":{"id":"5cc2e847209c7703","repo":"can1357/oh-my-pi","slug":"path-traversal-is-not-allowed-in-skill-urls","errorCode":null,"errorMessage":"Path traversal is not allowed in skill:// URLs","messagePattern":"Path traversal is not allowed in skill:// URLs","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/bash-skill-urls.ts","lineNumber":105,"sourceCode":"\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\") {\n\t\t\tthrow new ToolError(`skill:// path does not exist: ${url}`);\n\t\t}\n\t\treturn contained.realPath;\n\t}\n\n\treturn resolvedPath;\n}","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/bash-skill-urls.ts#L87-L123","documentation":"Even with a well-formed relative path, the resolved absolute target must stay inside the skill's base directory. The function resolves both paths and rejects any result that escapes the base (the classic \"..\" symlink-or-path traversal defense), throwing this ToolError on escape.","triggerScenarios":"Any skill:// URL whose final resolved path (after path.join/resolve) lands outside skill.baseDir — typically \"skill://name/../../outside.txt\" or a baseDir-relative escape via symlinks not covered by containRoot.","commonSituations":"Prompt-injection or a wandering agent tries to read files outside a skill package; a legitimately relocated skill directory makes previously valid relative paths resolve oddly; hardcoded traversal in generated commands.","solutions":["Remove \"..\" traversal from the URL path; target only files inside the skill directory.","If you need a file outside the skill, use the appropriate tool (read tool / plain path) with the proper permissions.","Check that the skill's baseDir is where you expect; the URL is interpreted relative to it."],"exampleFix":"// before\nresolveSkillUrlToPath(\"skill://my-skill/../../secrets.env\", skills);\n\n// after\nresolveSkillUrlToPath(\"skill://my-skill/config.env\", skills);","handlingStrategy":"validation","validationCode":"import * as path from \"node:path\";\nconst rel = decodeURIComponent(new URL(url).pathname.slice(1));\nconst resolved = path.resolve(skill.baseDir, rel);\nif (!resolved.startsWith(path.resolve(skill.baseDir) + path.sep)) {\n  throw new Error(`refusing: ${url} escapes skill directory`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  return resolveSkillUrlToPath(url, skills);\n} catch (e) {\n  if (e instanceof ToolError && e.message.includes(\"Path traversal\")) {\n    // do not retry; treat as a security rejection and report to the caller\n  } else throw e;\n}","preventionTips":["Never construct skill URLs containing \"..\" segments, even for 'convenience' access.","Sanitize any user/model-supplied path with a allowlist of safe segments.","Treat traversal attempts as a security signal, not a recoverable input error."],"tags":["security","path-traversal","skills"],"backgroundTag":"path-traversal-blocked","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}