mastra-ai/mastra · error

Path traversal detected: "${relativePath}" escapes skill dir

Error message

Path traversal detected: "${relativePath}" escapes skill directory

What it means

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.

Source

Thrown at packages/core/src/storage/filesystem-db.ts:217

   * Get the path to a skill's directory.
   */
  skillDir(skillName: string): string {
    const skillsBase = join(this.dir, 'skills');
    const dir = resolve(skillsBase, skillName);
    if (!dir.startsWith(skillsBase + sep) && dir !== skillsBase) {
      throw new Error(`Path traversal detected: skill name "${skillName}" escapes skills directory`);
    }
    return dir;
  }

  /**
   * Resolve a file path within a skill directory, throwing if it escapes.
   */
  private safeSkillPath(skillName: string, relativePath: string): string {
    const base = this.skillDir(skillName);
    const resolved = resolve(base, relativePath);
    if (!resolved.startsWith(base + sep) && resolved !== base) {
      throw new Error(`Path traversal detected: "${relativePath}" escapes skill directory`);
    }
    return resolved;
  }

  /**
   * List all files in a skill's directory, returning relative paths.
   */
  listSkillFiles(skillName: string): string[] {
    const dir = this.skillDir(skillName);
    if (!existsSync(dir)) return [];
    return walkDir(dir).map(abs => relative(dir, abs).split(sep).join('/'));
  }

  /**
   * Read a file from a skill's directory.
   */
  readSkillFile(skillName: string, relativePath: string): Buffer | null {
    const filePath = this.safeSkillPath(skillName, relativePath);

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Sanitize/normalize the relative path: reject absolute paths and any `..` segment before calling.
  2. Iterate only over file lists produced by the library (listSkillFiles) rather than untrusted manifests.
  3. Validate entries against /^[a-zA-Z0-9-_/.]+$/ and require the normalized path to stay within the skill dir.
  4. When importing skills, validate the archive contents before writing (reject entries escaping the skill root).

Example fix

// before
const p = db.filePath(skillName, entry.name); // entry may be '../../x'

// after
const safeRel = entry.name.replace(/^(\.\.?(\/|\\))+/, '').replace(/[^a-zA-Z0-9-_.\/]/g, '');
if (safeRel.includes('..')) throw new Error('unsafe skill file path');
const p = db.filePath(skillName, safeRel);
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');
function isSafeSkillRelPath(rel) {
  if (typeof rel !== 'string' || rel.length === 0) return false;
  if (path.isAbsolute(rel)) return false;
  const norm = path.normalize(rel);
  return !norm.split(path.sep).includes('..');
}

Try / catch

try {
  const p = db.filePath(skillName, rel);
} catch (e) {
  if (e.message.startsWith('Path traversal detected')) {
    throw new Error(`Rejected skill file path: ${rel}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling filePath (which delegates to safeSkillPath) with a relativePath containing `..`, absolute paths, or leading separators that resolve outside the skill directory.

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/97b1768ed4cb7c34. Report an issue: GitHub.