vercel-labs/skills · critical

Invalid skill name: potential path traversal detected

Error message

Invalid skill name: potential path traversal detected

What it means

getInstallPath() sanitizes the skill name and then verifies with isPathSafe that join(targetBase, sanitized) stays inside the install base directory. If the sanitized name still escapes (traversal survives sanitization), it throws to prevent writing skill files outside the agent directory.

Source

Thrown at src/installer.ts:583

export function getInstallPath(
  skillName: string,
  agentType: AgentType,
  options: { global?: boolean; cwd?: string; eveSubagent?: string } = {}
): string {
  const agent = agents[agentType];
  const cwd = options.cwd || process.cwd();
  const sanitized = sanitizeName(skillName);

  const targetBase = getAgentBaseDir(
    agentType,
    options.global ?? false,
    options.cwd,
    options.eveSubagent
  );
  const installPath = join(targetBase, sanitized);

  if (!isPathSafe(targetBase, installPath)) {
    throw new Error('Invalid skill name: potential path traversal detected');
  }

  return installPath;
}

/**
 * Gets the canonical .agents/skills/<skill> path
 */
export function getCanonicalPath(
  skillName: string,
  options: { global?: boolean; cwd?: string; agent?: AgentType; eveSubagent?: string } = {}
): string {
  const sanitized = sanitizeName(skillName);
  const canonicalBase =
    options.agent === 'eve'
      ? getAgentBaseDir('eve', options.global ?? false, options.cwd, options.eveSubagent)
      : getCanonicalSkillsDir(options.global ?? false, options.cwd);
  const canonicalPath = join(canonicalBase, sanitized);

View on GitHub (pinned to 435076e789)

Solutions

  1. Reject/normalize the skill name before calling the installer: strip '/', '\\', '..' and leading dots
  2. Audit SKILL.md frontmatter of third-party skills before installing (name should be a simple slug)
  3. If you hit this with a legitimately-named skill, file a bug — sanitizeName should have handled it first
  4. Never pass user-supplied strings directly as skill names

Example fix

// before
installSkill({ name: '../../evil', ... });
// after
const name = rawName.replace(/[^a-z0-9-]/gi, '-').replace(/^-+|-+$/g, '');
installSkill({ name, ... });
Defensive patterns

Strategy: validation

Validate before calling

function isSafeSkillName(name: string): boolean {
  return /^[a-z0-9][a-z0-9-_.]{0,63}$/i.test(name) && !name.includes('..');
}
if (!isSafeSkillName(skill.name)) throw new Error(`Rejecting unsafe skill name: ${skill.name}`);

Type guard

function isTraversalError(e: unknown): e is Error {
  return e instanceof Error && /path traversal/i.test(e.message);
}

Try / catch

try { const p = getInstallPath(opts); }
catch (e) {
  if (isTraversalError(e)) throw new Error(`Refusing to install skill with unsafe name: ${opts.skillName}`);
  throw e;
}

Prevention

When it happens

Trigger: Calling install/getInstallPath with a skill name containing traversal payloads ('..', '../..', absolute paths, or Windows drive letters) that sanitizeName does not strip — i.e. a crafted SKILL.md name field or programmatic install call.

Common situations: Installing a hostile skill whose SKILL.md frontmatter name is '../../.ssh/authorized_keys'; names with backslashes on Windows; SDK users passing unsanitized external input as skillName.

Related errors


AI-assisted analysis of vercel-labs/skills@435076e789 (2026-08-28). Data as JSON: /api/errors/326af395adc6053a. Report an issue: GitHub.