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

  1. Log the exact skillName, root, resolved target, and platform when this fires — the regex should have caught it.
  2. Simplify the name to plain ASCII alphanumerics and re-test.
  3. If reproducible, file a bug: either SAFE_NAME is too permissive for that platform or resolve() is doing something unexpected.
  4. 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

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


AI-assisted analysis of upstash/context7@ca15df0443 (2026-08-12). Data as JSON: /api/errors/10fa15d5366bb808. Report an issue: GitHub.