upstash/context7 · critical
Skill name " " escapes the skills root
Error message
Skill name "${skillName}" escapes the skills root What it means
assertSkillNameInRoot validates a skill name before resolving it under the skills root directory. After checking the name against isSafeSkillName, it resolves the joined path and verifies the resolved directory is still the skills root and the basename equals the original name. If a crafted name (e.g. containing '..' or separators) resolves outside the root, it throws to block path traversal.
Solutions
- Sanitize the skill name before calling: strip path separators and '..' segments, or reject names not matching /^[A-Za-z0-9._-]+$/
- Ensure the name is a plain single path segment (no '/' or '\\', not '..')
- If the intent was a nested path, pass only the final directory name and manage nesting inside the library
- Log/inspect the rejected name to find where unsanitized input enters the CLI
Example fix
// before
removeCommand(`../${userInput}`);
// after
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(userInput)) throw new Error('invalid skill name');
removeCommand(userInput); Defensive patterns
Strategy: validation
Validate before calling
const SAFE_SKILL_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
function isValidSkillName(name) { return typeof name === 'string' && SAFE_SKILL_NAME.test(name) && name !== '..' && name !== '.'; } Type guard
function isSafeSkillName(name: unknown): name is string {
return typeof name === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name);
} Try / catch
try {
const dir = skillDir(userInput);
} catch (e) {
if (e.message.includes('escapes the skills root') || e.message.startsWith('Unsafe skill name')) {
throw new UserFacingError(`Invalid skill name: ${userInput}`);
}
throw e;
} Prevention
- Always validate user-supplied names against a strict allowlist pattern before passing them to skill APIs
- Never build skill names from file paths, URLs, or user raw input without sanitization
- Treat path traversal rejections as a security signal and log the attempted input
- Keep the library's assertSkillNameInRoot call in place; do not bypass it
When it happens
Trigger: Calling skillDir/targetPath/removeCommand with a skill name like '../other', 'a/b', an absolute path, or any string containing path separators or '..' segments, so that resolve(root, skillName) lands outside the skills root.
Common situations: User-supplied skill names passed to CLI remove/skill commands without sanitization; automation scripts interpolating paths into skill names; names copied from URLs or file paths; locale/encoding oddities introducing separator characters.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Unsafe skill name
- Skill file path " " resolves outside the target directory
- Context7 base URL must not contain a query string or…
- Context7 base URL must not contain credentials
- Expertise description is required
AI-assisted analysis of upstash/context7@4416fb855b (2026-09-16).
Data as JSON: /api/errors/10fa15d5366bb808.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/utils/skill-name.ts:21
const SAFE_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
export function isSafeSkillName(name: string): boolean {
if (typeof name !== "string") return false;
if (name.length === 0 || name.length > 128) return false;
if (name === "." || name === "..") return false;
if (name.includes("\0")) return false;
if (!SAFE_NAME.test(name)) return false;
return true;
}
export function assertSkillNameInRoot(skillsRoot: string, skillName: string): string {
if (!isSafeSkillName(skillName)) {
throw new Error(`Unsafe skill name: ${JSON.stringify(skillName)}`);
}
const root = resolve(skillsRoot);
const target = resolve(root, skillName);
if (dirname(target) !== root || basename(target) !== skillName) {
throw new Error(`Skill name "${skillName}" escapes the skills root`);
}
return target;
}
View on GitHub (pinned to 4416fb855b)