{"record":{"id":"fdbd329e86fad09d","repo":"vercel-labs/skills","slug":"unsafe-subpath-subpath-contains-path-travers","errorCode":null,"errorMessage":"Unsafe subpath: \"${subpath}\" contains path traversal segments. Subpaths must not contain \"..\" components.","messagePattern":"Unsafe subpath: \"(.+?)\" contains path traversal segments\\. Subpaths must not contain \"\\.\\.\" components\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/source-parser.ts","lineNumber":114,"sourceCode":"    // On error, return null to indicate we couldn't determine\n    return null;\n  }\n}\n\n/**\n * Sanitizes a subpath to prevent path traversal attacks.\n * Rejects subpaths containing \"..\" segments that could escape the repository root.\n * Returns the sanitized subpath, or throws if the subpath is unsafe.\n */\nexport function sanitizeSubpath(subpath: string): string {\n  // Normalize to forward slashes for consistent handling\n  const normalized = subpath.replace(/\\\\/g, '/');\n\n  // Check each segment for \"..\"\n  const segments = normalized.split('/');\n  for (const segment of segments) {\n    if (segment === '..') {\n      throw new Error(\n        `Unsafe subpath: \"${subpath}\" contains path traversal segments. ` +\n          `Subpaths must not contain \"..\" components.`\n      );\n    }\n  }\n\n  return subpath;\n}\n\n/**\n * Check if a string represents a local file system path\n */\nfunction isLocalPath(input: string): boolean {\n  return (\n    isAbsolute(input) ||\n    input.startsWith('./') ||\n    input.startsWith('../') ||\n    input === '.' ||","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/vercel-labs/skills/blob/435076e78988e1e6ec40d00b0b1d76bdbbc5419a/src/source-parser.ts#L96-L132","documentation":"sanitizeSubpath() normalizes backslashes to slashes and rejects the whole subpath if any single '..' segment appears. It is the front-line guard in source parsing, so hostile or malformed source specs fail before any filesystem or network operation.","triggerScenarios":"parseSource() receiving a source string whose subpath component contains '..' — e.g. 'owner/repo/..', 'github.com/a/b/../../c', or Windows-style 'a\\\\..\\\\b'. Any '..' segment, even one that would stay inside, is rejected.","commonSituations":"Users pasting traversal-style relative paths; automation building source strings by concatenation without sanitizing; inputs mixing Windows backslash separators.","solutions":["Remove '..' segments (or the whole subpath) from the source string before parsing","Use explicit, clean subpaths: 'owner/repo/path/to/skill'","If traversal was unintentional, simplify to the direct path to the skill directory","In SDKs, validate subpaths against /^[A-Za-z0-9._\\/-]+$/ with no '..' component"],"exampleFix":"# before\nskills add github.com/org/repo/skills/../skills/python\n# after\nskills add github.com/org/repo/skills/python","handlingStrategy":"validation","validationCode":"function isSafeSubpath(sub: string): boolean {\n  const n = sub.replace(/\\\\/g, '/');\n  return !n.split('/').includes('..');\n}\nif (subpath && !isSafeSubpath(subpath)) throw new Error(`Unsafe subpath: ${subpath}`);","typeGuard":"function isUnsafeSubpath(e: unknown): e is Error {\n  return e instanceof Error && /Unsafe subpath.*\\.\\./.test(e.message);\n}","tryCatchPattern":"try { const src = parseSource(input); }\ncatch (e) {\n  if (isUnsafeSubpath(e)) {\n    const cleaned = input.replace(/\\.{2}(?:\\/[\\\\/]*)?/g, '').replace(/\\\\/g, '/');\n    return parseSource(cleaned); // retry with sanitized input\n  }\n  throw e;\n}","preventionTips":["Normalize separators and strip '..' segments from user-supplied source strings","Validate subpaths with a whitelist regex before parsing","Return clear user-facing errors for traversal input rather than forwarding to APIs"],"tags":["security","path-traversal","subpath","source-parsing"],"backgroundTag":"path-traversal-validation","analyzedSha":"435076e78988e1e6ec40d00b0b1d76bdbbc5419a","analyzedAt":"2026-08-28T17:47:53.369Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}