upstash/context7 · error · Error
Skill name "${skillName}" escapes the skills root
Error message
Skill name "${skillName}" escapes the skills root What it means
A second, defense-in-depth guard in assertSkillNameInRoot(), run only after isSafeSkillName() already passed. It resolves(root, skillName) and requires dirname(target)===root AND basename(target)===skillName. Because the SAFE_NAME regex already forbids '/' and '\\', under normal conditions this branch is effectively unreachable; firing it signals a platform-specific path resolution anomaly (e.g. drive letters, normalized UNC components) that slipped past the regex.
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 ca15df0443)
Solutions
- Log the exact skillName, root, resolved target, and platform when this fires — the regex should have caught it.
- Simplify the name to plain ASCII alphanumerics and re-test.
- If reproducible, file a bug: either SAFE_NAME is too permissive for that platform or resolve() is doing something unexpected.
- Avoid reserved filenames and any path-like characters entirely.
Example fix
// before — name that round-trips abnormally on the host OS // (no clean user fix; this guard is defense-in-depth) // after — use a plain name that the regex already permits and that resolves cleanly assertSkillNameInRoot(root, "my-skill");
Defensive patterns
Strategy: validation
Validate before calling
// Round-trip check that mirrors the internal guard, so you can fail fast with context.
import { resolve, dirname, basename } from "node:path";
import { isSafeSkillName } from "../utils/skill-name";
function safeResolve(skillsRoot: string, name: string): string {
if (!isSafeSkillName(name)) throw new Error(`Unsafe name: ${name}`);
const root = resolve(skillsRoot);
const target = resolve(root, name);
if (dirname(target) !== root || basename(target) !== name) {
throw new Error(`Name "${name}" resolves unexpectedly on ${process.platform}; use a plainer name.`);
}
return target;
} Type guard
function resolvesInsideRoot(skillsRoot: string, name: string): boolean {
if (!isSafeSkillName(name)) return false;
const root = resolve(skillsRoot);
const target = resolve(root, name);
return dirname(target) === root && basename(target) === name;
} Try / catch
try {
assertSkillNameInRoot(skillsRoot, candidate);
} catch (e) {
const msg = (e as Error).message;
if (msg.includes("escapes the skills root")) {
// Should be near-unreachable; log platform + inputs and use a plain name.
console.error({ platform: process.platform, skillsRoot, candidate });
return fallbackToPlainName();
}
throw e;
} Prevention
- Use plain ASCII alphanumeric names plus only ._- to make this guard a non-event.
- If it ever fires, capture platform + root + name — it indicates path-normalization drift worth a bug report.
- Avoid reserved filenames and path-like characters entirely.
When it happens
Trigger: A skill name that the OS path resolver normalizes differently than the lexical regex — e.g. a Windows drive-qualified or UNC-ish component, a reserved name, or a normalization quirk where dirname/basename no longer round-trip to the original name.
Common situations: Cross-platform bug where a name passes on POSIX but resolves oddly on Windows; corrupted/templated name input; essentially never seen in practice — treat as a canary for unexpected path-normalization behavior.
Related errors
- Skill file path "${file.path}" resolves outside the target d
- Unsafe skill name: ${JSON.stringify(skillName)}
- no files
- query and libraryName are required
AI-assisted analysis of upstash/context7@ca15df0443 (2026-08-12).
Data as JSON: /api/errors/10fa15d5366bb808.
Report an issue: GitHub.