{"record":{"id":"028ce783c8d58c72","repo":"upstash/context7","slug":"unsafe-skill-name-json-stringify-skillname","errorCode":null,"errorMessage":"Unsafe skill name: ${JSON.stringify(skillName)}","messagePattern":"Unsafe skill name: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/utils/skill-name.ts","lineNumber":16,"sourceCode":"import { resolve, dirname, basename } from \"path\";\n\nconst SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;\n\nexport function isSafeSkillName(name: string): boolean {\n  if (typeof name !== \"string\") return false;\n  if (name.length === 0 || name.length > 128) return false;\n  if (name === \".\" || name === \"..\") return false;\n  if (name.includes(\"\\0\")) return false;\n  if (!SAFE_NAME.test(name)) return false;\n  return true;\n}\n\nexport function assertSkillNameInRoot(skillsRoot: string, skillName: string): string {\n  if (!isSafeSkillName(skillName)) {\n    throw new Error(`Unsafe skill name: ${JSON.stringify(skillName)}`);\n  }\n  const root = resolve(skillsRoot);\n  const target = resolve(root, skillName);\n  if (dirname(target) !== root || basename(target) !== skillName) {\n    throw new Error(`Skill name \"${skillName}\" escapes the skills root`);\n  }\n  return target;\n}\n","sourceCodeStart":1,"sourceCodeEnd":25,"githubUrl":"https://github.com/upstash/context7/blob/4416fb855b8f752be735e34f943b5d0762701aad/packages/cli/src/utils/skill-name.ts#L1-L25","documentation":"Thrown by assertSkillNameInRoot when the requested skill name fails isSafeSkillName (SAFE_NAME regex check — names must be simple, safe identifier-like strings). This is the first line of defense against path traversal via the skill name itself; a second check afterwards also ensures the resolved target is a direct child of the skills root.","triggerScenarios":"Calling any skill operation whose skillName argument contains path separators, `..`, leading dots, spaces, or other characters rejected by SAFE_NAME — e.g. `install(\"../evil\")`, `install(\"my skill/v2\")`, or an empty/oddly-cased name.","commonSituations":"User-typed skill names from CLI arguments containing typos, slashes, or shell-expanded paths; scripted installs interpolating untrusted input into the name; names copied from URLs that include version paths.","solutions":["Use a plain skill name matching the allowed pattern (letters/digits/dashes, e.g. \"my-skill\") with no slashes, dots-prefixes, or spaces","If you have a path or URL, extract just the skill's basename before passing it","Sanitize or validate user input in scripts before passing it as skillName","Check what the SAFE_NAME regex in skill-name.ts accepts and conform the name to it"],"exampleFix":"// before\nawait install(\"../downloaded/skill-pack\");\n// after\nawait install(\"skill-pack\");","handlingStrategy":"validation","validationCode":"const SAFE_NAME = /^[\\w][\\w.-]*$/; // mirror the library's SAFE_NAME\nif (!SAFE_NAME.test(skillName)) throw new Error(`Refusing unsafe skill name: ${skillName}`);","typeGuard":"function isSafeSkillName(name: string): boolean {\n  return typeof name === \"string\" && name.length > 0 && !/[^\\w.-]/.test(name) && !name.startsWith(\".\");\n}","tryCatchPattern":"try {\n  await installSkill(skillsRoot, rawName);\n} catch (e) {\n  if (e.message.startsWith(\"Unsafe skill name\")) {\n    console.error(`Invalid skill name '${rawName}'. Use letters, digits, and dashes only.`);\n  }\n}","preventionTips":["Sanitize CLI/script inputs before using them as skill names","Extract bare basenames from URLs or paths before passing them","Document the allowed name pattern for your skill registry users"],"tags":["security","validation","path-traversal","cli"],"backgroundTag":"invalid-identifier-format","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"}