can1357/oh-my-pi · error

Path traversal is not allowed

Error message

Path traversal is not allowed

What it means

This is a second, post-join defense in resolve(): even after validateRelativePath() passes, the joined path is resolved with path.resolve() and checked to still start with the skill's resolved baseDir. It catches edge cases the segment check misses (e.g. symlinks within baseDir pointing out, unusual normalization). If the resolved absolute path escapes skill.baseDir, resolve() refuses with this generic traversal error.

Source

Thrown at packages/coding-agent/src/internal-urls/skill-protocol.ts:78

		if (!skill) {
			const available = skills.map(s => s.name);
			const availableStr = available.length > 0 ? available.join(", ") : "none";
			throw new Error(`Unknown skill: ${skillName}\nAvailable: ${availableStr}`);
		}

		let targetPath: string;
		const urlPath = url.pathname;
		const hasRelativePath = urlPath && urlPath !== "/" && urlPath !== "";

		if (hasRelativePath) {
			const relativePath = decodeURIComponent(urlPath.slice(1));
			validateRelativePath(relativePath);
			targetPath = path.join(skill.baseDir, relativePath);

			const resolvedPath = path.resolve(targetPath);
			const resolvedBaseDir = path.resolve(skill.baseDir);
			if (!resolvedPath.startsWith(resolvedBaseDir + path.sep) && resolvedPath !== resolvedBaseDir) {
				throw new Error("Path traversal is not allowed");
			}
			// Agent Plugin skills (§4.1): the resource must canonically resolve
			// within the plugin root; a dangling or unresolvable path fails closed.
			// Symlinks may target other files inside the same package.
			if (skill.containRoot) {
				const contained = await resolveContainedPath(skill.containRoot, resolvedPath);
				if (contained.status === "outside") {
					throw new Error(`skill:// path resolves outside the plugin root: ${url.href}`);
				}
				if (contained.status === "missing") {
					throw new Error(`File not found: ${resolvedPath}`);
				}
				targetPath = contained.realPath;
			}
		} else {
			targetPath = context?.pathOnly === true ? skill.baseDir : skill.filePath;
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Reference real files inside the skill directory, not symlinks pointing outside it
  2. Inspect where skill.baseDir and the target actually resolve (path.resolve, fs.realpath) to find the escape
  3. If you legitimately need external content, expose it as its own skill or resource rather than symlinking into a skill
  4. Move the external file into the skill's directory

Example fix

// before
// skills/my-skill/data -> symlink to /etc  ;  resolve('skill://my-skill/data/passwd')
// after
// copy the needed files physically into the skill directory and resolve them there
Defensive patterns

Strategy: validation

Validate before calling

const resolved = path.resolve(path.join(skill.baseDir, rel));
const base = path.resolve(skill.baseDir);
if (!resolved.startsWith(base + path.sep) && resolved !== base) {
  throw new Error(`skill path escapes baseDir: ${resolved}`);
}

Try / catch

try {
  return await handler.resolve(url, ctx);
} catch (err) {
  if (err instanceof Error && err.message === 'Path traversal is not allowed') {
    // audit the skill directory for symlinks pointing outside
  }
  throw err;
}

Prevention

When it happens

Trigger: Resolving skill://<name>/<path> where path.join(skill.baseDir, relativePath), after path.resolve(), lands outside path.resolve(skill.baseDir) — e.g. via a symlinked directory inside the skill whose target is external (when the skill has no containRoot), or platform-specific normalization quirks.

Common situations: A symlink inside the skill directory pointing to an external file; baseDir itself being a symlink whose resolution interacts with the relative path; paths crafted with separators that evade the '..' segment check but still escape after resolution (e.g. on Windows with mixed separators).

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/ad5467e43b4778ac. Report an issue: GitHub.