{"record":{"id":"694f38506197b394","repo":"yamadashy/repomix","slug":"skill-name-cannot-contain-path-separators-or-null","errorCode":null,"errorMessage":"Skill name cannot contain path separators or null bytes","messagePattern":"Skill name cannot contain path separators or null bytes","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/core/skill/skillUtils.ts","lineNumber":29,"sourceCode":"export const toKebabCase = (str: string): string => {\n  return str\n    .replace(/([a-z])([A-Z])/g, '$1-$2') // Handle PascalCase/camelCase\n    .replace(/[\\s_]+/g, '-') // Replace spaces and underscores with hyphens\n    .replace(/[^a-z0-9-]/gi, '') // Remove invalid characters\n    .toLowerCase()\n    .replace(/-+/g, '-') // Collapse multiple hyphens\n    .replace(/^-|-$/g, ''); // Trim leading/trailing hyphens\n};\n\n/**\n * Validates and normalizes a skill name.\n * Converts to kebab-case and truncates to 64 characters.\n * Also rejects path traversal attempts.\n */\nexport const validateSkillName = (name: string): string => {\n  // Reject path separators and null bytes to prevent path traversal\n  if (name.includes('/') || name.includes('\\\\') || name.includes('\\0')) {\n    throw new Error('Skill name cannot contain path separators or null bytes');\n  }\n\n  // Reject dot-only names (., .., ...)\n  if (/^\\.+$/.test(name)) {\n    throw new Error('Skill name cannot consist only of dots');\n  }\n\n  const kebabName = toKebabCase(name);\n\n  if (kebabName.length === 0) {\n    throw new Error('Skill name cannot be empty after normalization');\n  }\n\n  return kebabName.substring(0, SKILL_NAME_MAX_LENGTH);\n};\n\n/**\n * Converts a string to Title Case.","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/yamadashy/repomix/blob/f465ad909315a22120636baf03fa5e28701a50cb/src/core/skill/skillUtils.ts#L11-L47","documentation":"validateSkillName rejects skill names containing '/', '\\\\', or null bytes before the name is used to build a skill directory path, preventing path traversal (e.g. '../../etc'). It throws a plain Error because the input is attacker- or user-controlled and must never reach the filesystem.","triggerScenarios":"Calling validateSkillName (via generateDefaultSkillName or generateDefaultSkillNameFromUrl) with a name containing a path separator or NUL — e.g. deriving a skill name from a URL path segment that still contains slashes, or raw user input passed through unmodified.","commonSituations":"Auto-deriving a skill name from a GitHub URL whose path segments weren't fully sanitized; users typing names like 'my/skill' in prompts; interpolating untrusted input into skill names in scripts.","solutions":["Strip or replace '/' and '\\\\' from the name before validation (generateDefaultSkillNameFromUrl should sanitize URL segments first).","Let the generator derive the name (toKebabCase) instead of passing raw user input.","If intentional, encode separators (e.g. 'my-skill') manually."],"exampleFix":"// before\nconst name = validateSkillName(url.pathname); // may contain '/'\n// after\nconst name = validateSkillName(url.pathname.replaceAll('/', '-'));","handlingStrategy":"validation","validationCode":"export const sanitizeSkillNameInput = (raw: string): string =>\n  raw.replaceAll(/[\\\\/\\0]/g, '-');\nconst name = validateSkillName(sanitizeSkillNameInput(input));","typeGuard":"const isSafeSkillName = (s: string): boolean =>\n  !s.includes('/') && !s.includes('\\\\') && !s.includes('\\0');","tryCatchPattern":"try {\n  const name = validateSkillName(input);\n} catch (e) {\n  if (e.message.includes('path separators or null bytes')) {\n    const name = validateSkillName(input.replaceAll(/[\\\\/\\0]/g, '-'));\n  } else throw e;\n}","preventionTips":["Never pass raw URL paths or user input directly as skill names.","Sanitize separators (to '-') before validation.","Treat skill names as filesystem identifiers: whitelist-allowed characters only.","Sanitize at the boundary where URL-derived segments are created."],"tags":["security","path-traversal","validation"],"backgroundTag":"path-traversal","analyzedSha":"f465ad909315a22120636baf03fa5e28701a50cb","analyzedAt":"2026-08-29T01:27:42.024Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}