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
- Reject/normalize the skill name before calling the installer: strip '/', '\\', '..' and leading dots
- Audit SKILL.md frontmatter of third-party skills before installing (name should be a simple slug)
- If you hit this with a legitimately-named skill, file a bug — sanitizeName should have handled it first
- 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
- Treat SKILL.md frontmatter names as untrusted input
- Slugify names before install: lowercase, dashes only
- Never bypass or catch-and-continue past a traversal error
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
- Archive contains unsafe path: ${path}
- Archive contains unsafe path: ${entryPath}
- Unsafe archive path: ${path}
- Invalid subpath: "${subpath}" resolves outside the repositor
- Unsafe subpath: "${subpath}" contains path traversal segment
AI-assisted analysis of vercel-labs/skills@435076e789 (2026-08-28).
Data as JSON: /api/errors/326af395adc6053a.
Report an issue: GitHub.