upstash/context7 · critical · Error

Skill file path "${file.path}" resolves outside the target d

Error message

Skill file path "${file.path}" resolves outside the target directory

What it means

A path-traversal guard inside installSkillFiles(). For each downloaded file, the resolved absolute path is checked to ensure it stays inside skillDir; if a file.path uses '../', an absolute path, or a Windows drive that escapes, the install aborts before any writeFile. This is a security boundary defending against malicious or compromised skill bundles.

Source

Thrown at packages/cli/src/utils/installer.ts:23

import { assertSkillNameInRoot } from "./skill-name.js";

export async function installSkillFiles(
  skillName: string,
  files: SkillFile[],
  skillsRoot: string
): Promise<void> {
  const skillDir = assertSkillNameInRoot(skillsRoot, skillName);

  for (const file of files) {
    const filePath = resolve(skillDir, file.path);

    // Prevent directory traversal — resolved path must stay within skillDir
    if (
      !filePath.startsWith(skillDir + "/") &&
      !filePath.startsWith(skillDir + "\\") &&
      filePath !== skillDir
    ) {
      throw new Error(`Skill file path "${file.path}" resolves outside the target directory`);
    }

    const fileDir = dirname(filePath);

    await mkdir(fileDir, { recursive: true });
    await writeFile(filePath, file.content);
  }
}

export async function symlinkSkill(
  skillName: string,
  sourcePath: string,
  skillsRoot: string
): Promise<void> {
  const targetPath = assertSkillNameInRoot(skillsRoot, skillName);

  try {
    const stats = await lstat(targetPath);

View on GitHub (pinned to ca15df0443)

Solutions

  1. Inspect the skill bundle's file list for absolute or '../' entries and report it to the skill author.
  2. If you maintain the skill, keep all file paths relative and contained within the skill folder.
  3. Do not disable this guard — it is a security control; instead fix the offending path.
  4. Confirm the skillsRoot and skillName resolve as expected (no unexpected symlink in the parent chain).

Example fix

// before — manifest contains an absolute path and trips the guard
files: [{ path: "/etc/evil", content: "..." }]

// after — keep every path relative under the skill folder
files: [{ path: "SKILL.md", content: "..." }, { path: "scripts/run.sh", content: "..." }]
Defensive patterns

Strategy: validation

Validate before calling

// Reject escaping paths before calling installSkillFiles.
import { resolve, relative } from "node:path";
function isContained(files: { path: string }[], skillDir: string): boolean {
  const root = resolve(skillDir);
  return files.every((f) => {
    const rel = relative(root, resolve(root, f.path));
    return (rel === "" || !rel.startsWith("..")) && !resolve(root, f.path).includes("\\0");
  });
}
if (!isContained(downloadData.files, skillDir)) {
  throw new Error("Refusing to install skill: one or more file paths escape the skill directory.");
}

Type guard

function isSafeRelativePath(skillDir: string, p: string): boolean {
  if (typeof p !== "string" || p.length === 0 || p.includes("\0")) return false;
  const rel = relative(resolve(skillDir), resolve(skillDir, p));
  return rel === "" || (!rel.startsWith("..") && !resolve(skillDir, p).startsWith("/"));
}

Try / catch

try {
  await installSkillFiles(name, files, skillDir);
} catch (e) {
  if (/resolves outside the target directory/i.test((e as Error).message)) {
    // Security-relevant: do NOT retry; report the offending bundle upstream.
    throw new Error(`Skill "${name}" contains an escaping path and was blocked. Report to the skill author.`);
  }
  throw e;
}

Prevention

When it happens

Trigger: A downloaded skill manifest contains a file entry like '../../.bashrc', an absolute '/etc/...', a Windows 'C:\\...' path, or a symlink-style relative escape; the check fires before mkdir/writeFile so nothing is written.

Common situations: Skill author included absolute paths by mistake; a third-party/compromised skill tries to write outside its directory; path separator confusion on Windows where startsWith(skillDir + '\\') is the matching branch; bundled skill packaged with a build tool that emitted rooted paths.

Related errors


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