{"record":{"id":"97b1768ed4cb7c34","repo":"mastra-ai/mastra","slug":"path-traversal-detected-relativepath-escapes","errorCode":null,"errorMessage":"Path traversal detected: \"${relativePath}\" escapes skill directory","messagePattern":"Path traversal detected: \"(.+?)\" escapes skill directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/storage/filesystem-db.ts","lineNumber":217,"sourceCode":"   * 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.\n   */\n  listSkillFiles(skillName: string): string[] {\n    const dir = this.skillDir(skillName);\n    if (!existsSync(dir)) return [];\n    return walkDir(dir).map(abs => relative(dir, abs).split(sep).join('/'));\n  }\n\n  /**\n   * Read a file from a skill's directory.\n   */\n  readSkillFile(skillName: string, relativePath: string): Buffer | null {\n    const filePath = this.safeSkillPath(skillName, relativePath);","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/storage/filesystem-db.ts#L199-L235","documentation":"safeSkillPath() resolves a relative path within a skill's directory (after skillDir's own check) and throws if the result escapes that skill directory. It prevents file operations targeting other skills or arbitrary paths via `../` in the relative path.","triggerScenarios":"Calling filePath (which delegates to safeSkillPath) with a relativePath containing `..`, absolute paths, or leading separators that resolve outside the skill directory.","commonSituations":"Skill file lists from untrusted archives/registries containing entries like '../../package.json'; user-specified file names within a skill editor; ZIP-slip style extracted entries.","solutions":["Sanitize/normalize the relative path: reject absolute paths and any `..` segment before calling.","Iterate only over file lists produced by the library (listSkillFiles) rather than untrusted manifests.","Validate entries against /^[a-zA-Z0-9-_/.]+$/ and require the normalized path to stay within the skill dir.","When importing skills, validate the archive contents before writing (reject entries escaping the skill root)."],"exampleFix":"// before\nconst p = db.filePath(skillName, entry.name); // entry may be '../../x'\n\n// after\nconst safeRel = entry.name.replace(/^(\\.\\.?(\\/|\\\\))+/, '').replace(/[^a-zA-Z0-9-_.\\/]/g, '');\nif (safeRel.includes('..')) throw new Error('unsafe skill file path');\nconst p = db.filePath(skillName, safeRel);","handlingStrategy":"validation","validationCode":"const path = require('path');\nfunction isSafeSkillRelPath(rel) {\n  if (typeof rel !== 'string' || rel.length === 0) return false;\n  if (path.isAbsolute(rel)) return false;\n  const norm = path.normalize(rel);\n  return !norm.split(path.sep).includes('..');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const p = db.filePath(skillName, rel);\n} catch (e) {\n  if (e.message.startsWith('Path traversal detected')) {\n    throw new Error(`Rejected skill file path: ${rel}`);\n  }\n  throw e;\n}","preventionTips":["Only trust file lists produced by the library's own listing APIs.","Validate entries in untrusted skill archives (zip-slip protection) before use.","Reject absolute paths and `..` segments in relative paths.","Keep skill content imports sandboxed to the skill directory."],"tags":["security","path-traversal","skills","zip-slip"],"backgroundTag":"path-traversal-detected","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}