{"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":"Error","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/ca15df0443ee770506fc4eb270d1efc71d483933/packages/cli/src/utils/skill-name.ts#L1-L25","documentation":"Thrown by assertSkillNameInRoot() when isSafeSkillName() rejects the name. A safe name must be a string of length 1–128, not '.' or '..', contain no NUL byte, and match ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ (alphanumeric first char; only letters, digits, '.', '_', '-' afterwards). The error stringifies the name with JSON.stringify so control characters are visible.","triggerScenarios":"Empty string; name longer than 128 chars; name with '/', '\\\\', ':', space, or other punctuation; name starting with '.' or '-' or '_' (first char must be alphanumeric); name containing a NUL byte; non-string input (number/object).","commonSituations":"User typed a scoped registry id like '@org/skill' (contains '@' and '/'); copy-paste included a trailing slash or whitespace; name derived from a filesystem path with separators; very long auto-generated slug.","solutions":["Rename to start with an alphanumeric character and use only a-zA-Z0-9._- afterwards.","Strip leading/trailing whitespace and any '/', '\\\\', '@', ':' from the name.","Shorten to <= 128 characters.","For scoped skills, flatten '@org/skill' to 'org.skill' or 'org-skill'."],"exampleFix":"// before\nassertSkillNameInRoot(root, \"@upstash/context7\"); // throws: contains '@' and '/'\n\n// after\nassertSkillNameInRoot(root, \"upstash-context7\");","handlingStrategy":"validation","validationCode":"import { isSafeSkillName } from \"../utils/skill-name\";\nfunction normalizeSkillName(raw: string): string {\n  const trimmed = raw.trim().replace(/^[@]/, \"\").replace(/[\\\\/]+/g, \"-\");\n  return trimmed;\n}\nconst name = normalizeSkillName(input);\nif (!isSafeSkillName(name)) {\n  throw new Error(`\"${input}\" is not a valid skill name (use a-z0-9 and ._-, max 128 chars).`);\n}\nassertSkillNameInRoot(skillsRoot, name);","typeGuard":"// Direct reuse of the library predicate as a userland type guard.\nimport { isSafeSkillName } from \"../utils/skill-name\";\nfunction asSkillName(raw: unknown): string | null {\n  return typeof raw === \"string\" && isSafeSkillName(raw) ? raw : null;\n}","tryCatchPattern":"try {\n  assertSkillNameInRoot(skillsRoot, candidate);\n} catch (e) {\n  const msg = (e as Error).message;\n  if (msg.startsWith(\"Unsafe skill name\")) {\n    throw new Error(`Refusing to install: ${msg}. Use only a-z0-9 and ._- characters.`);\n  }\n  throw e;\n}","preventionTips":["Validate skill names at the input boundary with isSafeSkillName before any filesystem work.","Flatten registry-style ids ('@org/skill') to 'org-skill' before passing them in.","Trim whitespace and reject blank strings explicitly."],"tags":["validation","security","skill-install"],"backgroundTag":null,"analyzedSha":"ca15df0443ee770506fc4eb270d1efc71d483933","analyzedAt":"2026-08-12T13:31:48.440Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}