{"record":{"id":"e6840efd02a80bc9","repo":"mastra-ai/mastra","slug":"path-traversal-detected-skill-name-skillname","errorCode":null,"errorMessage":"Path traversal detected: skill name \"${skillName}\" escapes skills directory","messagePattern":"Path traversal detected: skill name \"(.+?)\" escapes skills directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/storage/filesystem-db.ts","lineNumber":205,"sourceCode":"    const data = this.readDomain(filename);\n    if (id in data) {\n      delete data[id];\n      this.writeDomain(filename, data);\n    }\n  }\n\n  // =========================================================================\n  // Skills directory operations (real file tree, not JSON)\n  // =========================================================================\n\n  /**\n   * Get the path to a skill's directory.\n   */\n  skillDir(skillName: string): string {\n    const skillsBase = join(this.dir, 'skills');\n    const dir = resolve(skillsBase, skillName);\n    if (!dir.startsWith(skillsBase + sep) && dir !== skillsBase) {\n      throw new Error(`Path traversal detected: skill name \"${skillName}\" escapes skills directory`);\n    }\n    return dir;\n  }\n\n  /**\n   * Resolve a file path within a skill directory, throwing if it escapes.\n   */\n  private safeSkillPath(skillName: string, relativePath: string): string {\n    const base = this.skillDir(skillName);\n    const resolved = resolve(base, relativePath);\n    if (!resolved.startsWith(base + sep) && resolved !== base) {\n      throw new Error(`Path traversal detected: \"${relativePath}\" escapes skill directory`);\n    }\n    return resolved;\n  }\n\n  /**\n   * List all files in a skill's directory, returning relative paths.","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/storage/filesystem-db.ts#L187-L223","documentation":"skillDir() resolves a skill name under `<storageDir>/skills` and throws if the resolved directory escapes that skills base directory. Skill names become directory names, so traversal sequences in a skill name are rejected.","triggerScenarios":"Calling skillDir (directly or via base/dir helpers) with a skill name containing `..`, `/`, `\\`, or an absolute path.","commonSituations":"Skill names derived from user input, git repo/branch names, or external registries containing slashes (e.g. 'owner/repo') used unmodified as a directory name.","solutions":["Sanitize the skill name: replace path separators and reject `..` segments before using it.","Slugify external names (e.g. 'owner/repo' -> 'owner-repo') before storage.","Validate against a pattern like /^[a-zA-Z0-9-_]+$/ before calling skillDir.","Check where the skill name originates (user upload, registry) and enforce naming rules at ingestion."],"exampleFix":"// before\nconst dir = fsDb.skillDir('../../malicious'); // throws\n\n// after\nconst safeName = skillName.replace(/[^a-zA-Z0-9-_]/g, '-');\nconst dir = fsDb.skillDir(safeName);","handlingStrategy":"validation","validationCode":"function isSafeSkillName(name) {\n  return typeof name === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9-_]*$/.test(name);\n}\nif (!isSafeSkillName(skillName)) throw new Error('invalid skill name');","typeGuard":null,"tryCatchPattern":"try {\n  const dir = db.skillDir(skillName);\n} catch (e) {\n  if (e.message.startsWith('Path traversal detected')) {\n    throw new Error(`Skill name not allowed: ${skillName}`);\n  }\n  throw e;\n}","preventionTips":["Slugify names from external sources (registries, git refs) before storage.","Enforce skill-name validation at ingestion/upload time.","Reject names containing '/', '\\\\', or '..' early.","Add unit tests covering traversal attempts against skillDir."],"tags":["security","path-traversal","skills","filesystem"],"backgroundTag":"path-traversal-detected","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}