{"record":{"id":"326af395adc6053a","repo":"vercel-labs/skills","slug":"invalid-skill-name-potential-path-traversal-detec","errorCode":null,"errorMessage":"Invalid skill name: potential path traversal detected","messagePattern":"Invalid skill name: potential path traversal detected","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/installer.ts","lineNumber":583,"sourceCode":"export function getInstallPath(\n  skillName: string,\n  agentType: AgentType,\n  options: { global?: boolean; cwd?: string; eveSubagent?: string } = {}\n): string {\n  const agent = agents[agentType];\n  const cwd = options.cwd || process.cwd();\n  const sanitized = sanitizeName(skillName);\n\n  const targetBase = getAgentBaseDir(\n    agentType,\n    options.global ?? false,\n    options.cwd,\n    options.eveSubagent\n  );\n  const installPath = join(targetBase, sanitized);\n\n  if (!isPathSafe(targetBase, installPath)) {\n    throw new Error('Invalid skill name: potential path traversal detected');\n  }\n\n  return installPath;\n}\n\n/**\n * Gets the canonical .agents/skills/<skill> path\n */\nexport function getCanonicalPath(\n  skillName: string,\n  options: { global?: boolean; cwd?: string; agent?: AgentType; eveSubagent?: string } = {}\n): string {\n  const sanitized = sanitizeName(skillName);\n  const canonicalBase =\n    options.agent === 'eve'\n      ? getAgentBaseDir('eve', options.global ?? false, options.cwd, options.eveSubagent)\n      : getCanonicalSkillsDir(options.global ?? false, options.cwd);\n  const canonicalPath = join(canonicalBase, sanitized);","sourceCodeStart":565,"sourceCodeEnd":601,"githubUrl":"https://github.com/vercel-labs/skills/blob/435076e78988e1e6ec40d00b0b1d76bdbbc5419a/src/installer.ts#L565-L601","documentation":"getInstallPath() sanitizes the skill name and then verifies with isPathSafe that join(targetBase, sanitized) stays inside the install base directory. If the sanitized name still escapes (traversal survives sanitization), it throws to prevent writing skill files outside the agent directory.","triggerScenarios":"Calling install/getInstallPath with a skill name containing traversal payloads ('..', '../..', absolute paths, or Windows drive letters) that sanitizeName does not strip — i.e. a crafted SKILL.md name field or programmatic install call.","commonSituations":"Installing a hostile skill whose SKILL.md frontmatter name is '../../.ssh/authorized_keys'; names with backslashes on Windows; SDK users passing unsanitized external input as skillName.","solutions":["Reject/normalize the skill name before calling the installer: strip '/', '\\\\', '..' and leading dots","Audit SKILL.md frontmatter of third-party skills before installing (name should be a simple slug)","If you hit this with a legitimately-named skill, file a bug — sanitizeName should have handled it first","Never pass user-supplied strings directly as skill names"],"exampleFix":"// before\ninstallSkill({ name: '../../evil', ... });\n// after\nconst name = rawName.replace(/[^a-z0-9-]/gi, '-').replace(/^-+|-+$/g, '');\ninstallSkill({ name, ... });","handlingStrategy":"validation","validationCode":"function isSafeSkillName(name: string): boolean {\n  return /^[a-z0-9][a-z0-9-_.]{0,63}$/i.test(name) && !name.includes('..');\n}\nif (!isSafeSkillName(skill.name)) throw new Error(`Rejecting unsafe skill name: ${skill.name}`);","typeGuard":"function isTraversalError(e: unknown): e is Error {\n  return e instanceof Error && /path traversal/i.test(e.message);\n}","tryCatchPattern":"try { const p = getInstallPath(opts); }\ncatch (e) {\n  if (isTraversalError(e)) throw new Error(`Refusing to install skill with unsafe name: ${opts.skillName}`);\n  throw e;\n}","preventionTips":["Treat SKILL.md frontmatter names as untrusted input","Slugify names before install: lowercase, dashes only","Never bypass or catch-and-continue past a traversal error"],"tags":["security","path-traversal","installer","skill-name"],"backgroundTag":"path-traversal-validation","analyzedSha":"435076e78988e1e6ec40d00b0b1d76bdbbc5419a","analyzedAt":"2026-08-28T17:47:53.369Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}