vercel-labs/skills · critical
Invalid subpath: "${subpath}" resolves outside the repositor
Error message
Invalid subpath: "${subpath}" resolves outside the repository directory. Subpath must not contain ".." segments that escape the base path. What it means
discoverSkills() validates any provided subpath with isSubpathSafe(basePath, subpath) before searching; if the subpath resolves outside basePath (contains '..' segments that escape), it throws this traversal error. This guards skill discovery inside cloned repos against path injection via the source spec's subpath component.
Source
Thrown at src/skills.ts:189
const normalizedTarget = normalize(resolve(join(basePath, subpath)));
return normalizedTarget.startsWith(normalizedBase + sep) || normalizedTarget === normalizedBase;
}
export async function discoverSkills(
basePath: string,
subpath?: string,
options?: DiscoverSkillsOptions
): Promise<Skill[]> {
const skills: Skill[] = [];
const seenNames = new Set<string>();
const parsedSkillPaths = new Set<string>();
const localLock = await readLocalLock(basePath);
const lockedSkillNames = new Set(Object.keys(localLock.skills).map(normalizeSkillName));
// Validate subpath doesn't escape basePath (prevent path traversal)
if (subpath && !isSubpathSafe(basePath, subpath)) {
throw new Error(
`Invalid subpath: "${subpath}" resolves outside the repository directory. Subpath must not contain ".." segments that escape the base path.`
);
}
const searchPath = subpath ? join(basePath, subpath) : basePath;
// Get plugin groupings to map skills to their parent plugin
// We search for plugin definitions from the base search path
const pluginGroupings = await getPluginGroupings(searchPath);
// Helper to assign plugin name if available
const enhanceSkill = (skill: Skill) => {
const resolvedPath = resolve(skill.path);
if (pluginGroupings.has(resolvedPath)) {
skill.pluginName = pluginGroupings.get(resolvedPath);
}
return skill;
};View on GitHub (pinned to 435076e789)
Solutions
- Strip '..' segments from the subpath before calling discoverSkills (or sanitizeSubpath from source-parser)
- Validate user input: allow only [A-Za-z0-9._/-] and reject any '..' component
- Pass an absolute directory instead of a traversal-prone relative subpath
- Fail fast in your CLI/SDK on suspicious path input rather than forwarding it
Example fix
// before
discoverSkills(repoDir, userInput); // userInput = '../../secrets'
// after
if (userInput.split(/[\\/]/).includes('..')) throw new Error('bad subpath');
discoverSkills(repoDir, userInput); Defensive patterns
Strategy: validation
Validate before calling
import { resolve, relative, isAbsolute } from 'node:path';
function isSubpathSafe(base: string, sub: string): boolean {
if (sub.includes('..')) return false;
const target = resolve(base, sub);
const rel = relative(resolve(base), target);
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
}
if (subpath && !isSubpathSafe(basePath, subpath)) throw new Error('Unsafe subpath rejected'); Type guard
function isSubpathTraversal(e: unknown): e is Error {
return e instanceof Error && /Invalid subpath.*outside the repository directory/.test(e.message);
} Try / catch
try { await discoverSkills(basePath, subpath); }
catch (e) {
if (isSubpathTraversal(e)) throw new Error(`Rejected unsafe subpath: ${JSON.stringify(subpath)}`);
throw e;
} Prevention
- Never forward raw user input as a subpath — whitelist characters
- Reject any subpath containing '..' before calling discovery APIs
- Prefer absolute validated paths over composed relative subpaths
When it happens
Trigger: Calling discoverSkills(basePath, subpath) (ultimately from 'skills add owner/repo/sub..') with a subpath like '../../', 'a/../../..', or one that normalizes outside the repo root.
Common situations: User-supplied subpaths from CLI args or SDK strings like 'skills/../../etc'; URL parsing quirks that keep '..' segments; programmatic callers joining unvalidated user input into the subpath parameter.
Related errors
- Unsafe subpath: "${subpath}" contains path traversal segment
- Archive contains unsafe path: ${path}
- Archive contains unsafe path: ${entryPath}
- Invalid skill name: potential path traversal detected
- Unsafe archive path: ${path}
AI-assisted analysis of vercel-labs/skills@435076e789 (2026-08-28).
Data as JSON: /api/errors/9a32308ce164789d.
Report an issue: GitHub.