{"record":{"id":"cc17720444a2bdfa","repo":"upstash/context7","slug":"skill-file-path-file-path-resolves-outside-th","errorCode":null,"errorMessage":"Skill file path \"${file.path}\" resolves outside the target directory","messagePattern":"Skill file path \"(.+?)\" resolves outside the target directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/utils/installer.ts","lineNumber":23,"sourceCode":"import { assertSkillNameInRoot } from \"./skill-name.js\";\n\nexport async function installSkillFiles(\n  skillName: string,\n  files: SkillFile[],\n  skillsRoot: string\n): Promise<void> {\n  const skillDir = assertSkillNameInRoot(skillsRoot, skillName);\n\n  for (const file of files) {\n    const filePath = resolve(skillDir, file.path);\n\n    // Prevent directory traversal — resolved path must stay within skillDir\n    if (\n      !filePath.startsWith(skillDir + \"/\") &&\n      !filePath.startsWith(skillDir + \"\\\\\") &&\n      filePath !== skillDir\n    ) {\n      throw new Error(`Skill file path \"${file.path}\" resolves outside the target directory`);\n    }\n\n    const fileDir = dirname(filePath);\n\n    await mkdir(fileDir, { recursive: true });\n    await writeFile(filePath, file.content);\n  }\n}\n\nexport async function symlinkSkill(\n  skillName: string,\n  sourcePath: string,\n  skillsRoot: string\n): Promise<void> {\n  const targetPath = assertSkillNameInRoot(skillsRoot, skillName);\n\n  try {\n    const stats = await lstat(targetPath);","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/upstash/context7/blob/4416fb855b8f752be735e34f943b5d0762701aad/packages/cli/src/utils/installer.ts#L5-L41","documentation":"Thrown by installSkillFiles as a directory-traversal guard: after resolving a skill file's target path, the resolved path must stay inside skillDir (equal to it or nested under it with either separator). If a file entry's declared path escapes the skill directory — via `..` segments, absolute paths, or symlinks resolved earlier — installation aborts to prevent writing arbitrary files outside the skill target.","triggerScenarios":"Installing a skill whose file manifest contains entries like path: \"../../../etc/cron.d/evil\", an absolute path like \"/Users/x/.zshrc\", or any path that, after resolution, is not under skillDir.","commonSituations":"Downloading community skills from untrusted sources, hand-written skill manifests with typos in relative paths (e.g. too many `..`), or tools generating manifests with absolute output paths.","solutions":["Fix the `path` fields in the skill manifest to be relative paths staying inside the skill directory (e.g. \"scripts/run.sh\")","Remove `..` segments and absolute paths from file entries","Only install skills from trusted sources — this error may indicate a malicious or corrupted manifest","If the file legitimately belongs elsewhere, it should be installed by a different mechanism, not via the skill installer"],"exampleFix":"// before (manifest entry escapes target dir)\n{ \"path\": \"../../shared/helper.ts\", \"content\": \"...\" }\n// after\n{ \"path\": \"lib/helper.ts\", \"content\": \"...\" }","handlingStrategy":"validation","validationCode":"import { resolve, sep } from \"path\";\nfunction isInsideSkillDir(skillDir: string, relPath: string): boolean {\n  if (relPath.startsWith(\"/\") || /^[A-Za-z]:/.test(relPath)) return false;\n  const filePath = resolve(skillDir, relPath);\n  return filePath === skillDir || filePath.startsWith(skillDir + sep);\n}\nfiles.filter(f => !isInsideSkillDir(skillDir, f.path)); // reject before install","typeGuard":"function isSafeManifestPath(p: string): boolean {\n  return !p.startsWith(\"/\") && !p.split(\"/\").includes(\"..\") && !p.includes(\"\\\\\");\n}","tryCatchPattern":"try {\n  await installSkillFiles(skillDir, files);\n} catch (e) {\n  if (e.message.includes(\"resolves outside the target directory\")) {\n    console.error(`Skill manifest rejected (path traversal guard): ${e.message}`);\n    // do not retry; inspect the skill source for tampering\n  }\n}","preventionTips":["Only install skills from trusted sources","Keep manifest paths relative and free of `..` segments","Lint skill manifests before distribution"],"tags":["security","path-traversal","filesystem","validation"],"backgroundTag":"path-traversal-blocked","analyzedSha":"4416fb855b8f752be735e34f943b5d0762701aad","analyzedAt":"2026-09-16T20:28:07.148Z","contentChangedAt":"2026-09-16T20:28:07.148Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}