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
- Inspect the skill bundle's file list for absolute or '../' entries and report it to the skill author.
- If you maintain the skill, keep all file paths relative and contained within the skill folder.
- Do not disable this guard — it is a security control; instead fix the offending path.
- 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
- Treat this guard as a security control — never bypass it; fix the offending path instead.
- When authoring skills, run file paths through a normalizer that strips leading slashes and rejects '../'.
- Audit third-party skills before install by listing their file entries.
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
- Unsafe skill name: ${JSON.stringify(skillName)}
- Skill name "${skillName}" escapes the skills root
- no files
- query and libraryName are required
AI-assisted analysis of upstash/context7@ca15df0443 (2026-08-12).
Data as JSON: /api/errors/cc17720444a2bdfa.
Report an issue: GitHub.