{"record":{"id":"4e092c931b1a1820","repo":"upstash/context7","slug":"skipping-file-with-unsafe-path-item-path","errorCode":null,"errorMessage":"Skipping file with unsafe path: ${item.path}","messagePattern":"Skipping file with unsafe path: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/cli/src/utils/github.ts","lineNumber":295,"sourceCode":"    return { files: [], error: `No files found in ${skillPath}` };\n  }\n\n  const files: SkillFile[] = [];\n  for (const item of skillFiles) {\n    const rawUrl = `${GITHUB_RAW}/${owner}/${repo}/${branch}/${item.path}`;\n    const fileResponse = await fetch(rawUrl, { headers: ghHeaders });\n\n    if (!fileResponse.ok) {\n      console.warn(`Failed to fetch ${item.path}: ${fileResponse.status}`);\n      continue;\n    }\n\n    const content = await fileResponse.text();\n    const relativePath = item.path.slice(skillPath.length + 1);\n\n    // Reject paths that attempt directory traversal\n    if (relativePath.includes(\"..\")) {\n      console.warn(`Skipping file with unsafe path: ${item.path}`);\n      continue;\n    }\n\n    files.push({\n      path: relativePath,\n      content,\n    });\n  }\n\n  return { files };\n}\n\nasync function downloadSingleSkillFile(\n  skillUrl: string,\n  ghHeaders: Record<string, string>\n): Promise<SkillFile[] | null> {\n  let fileName: string;\n  try {","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/upstash/context7/blob/4416fb855b8f752be735e34f943b5d0762701aad/packages/cli/src/utils/github.ts#L277-L313","documentation":"downloadSkillTree strips the skill path prefix from each file and rejects any relative path containing \"..\", which would allow directory traversal outside the intended skill directory. The file is skipped with this warning instead of being written. This is a deliberate security guard, not a bug in the library.","triggerScenarios":"A GitHub tree entry whose path, after slicing off the skillPath prefix, contains \"..\" — i.e. the remote repo contains a file whose path escapes the skill directory (malicious repo, symlink-like naming, or a skillPath that doesn't actually prefix item.path).","commonSituations":"Downloading skills from an untrusted third-party repo; passing a skillPath that doesn't match the tree paths so slice() produces odd strings; hostile repos crafted to overwrite local files.","solutions":["Inspect the reported item.path in the source repo; remove or rename the offending file.","Verify the skillPath argument exactly matches the directory prefix used in the GitHub tree.","Only download skill trees from trusted repositories.","If the file is legitimate, restructure it so it lives inside the skill directory instead of using ../ escapes."],"exampleFix":"// before\nawait downloadSkillTree({ repo: 'untrusted-skills', skillPath: 'skills/foo' });\n// after\nawait downloadSkillTree({ repo: 'trusted-skills', skillPath: 'skills/foo' }); // and ensure skillPath matches tree paths","handlingStrategy":"validation","validationCode":"function isSafeRelativePath(p) { return !p.split('/').includes('..') && !p.startsWith('/') && p.length > 0; }\nif (!isSafeRelativePath(relativePath)) skip(item);","typeGuard":"const isSafeSkillPath = (p: string): p is string => !p.includes('..');","tryCatchPattern":null,"preventionTips":["Only download skills from trusted repositories.","Ensure skillPath exactly matches the tree directory prefix.","Normalize and validate paths before writing files to disk.","Review the source repo if this warning appears repeatedly."],"tags":["security","path-traversal","github"],"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"}