can1357/oh-my-pi · error
Path traversal (..) is not allowed in skill:// URLs
Error message
Path traversal (..) is not allowed in skill:// URLs
What it means
validateRelativePath() rejects any relative path containing '..' segments — as raw segments, after path.normalize(), or as prefixes/suffixes like '../x' or 'x/..'. This prevents directory traversal out of the skill's base directory when the path is joined and resolved. The check runs both on the split segments (before normalization) and the normalized string to defeat encoded or redundant-segment tricks.
Source
Thrown at packages/coding-agent/src/internal-urls/skill-protocol.ts:40
return "text/plain";
}
/**
* Validate that a path is safe (no traversal, no absolute paths).
*/
export function validateRelativePath(relativePath: string): void {
if (path.isAbsolute(relativePath)) {
throw new Error("Absolute paths are not allowed in skill:// URLs");
}
const normalized = path.normalize(relativePath);
if (
relativePath.split(/[\\/]/).includes("..") ||
normalized.startsWith("..") ||
normalized.includes("/../") ||
normalized.includes("/..")
) {
throw new Error("Path traversal (..) is not allowed in skill:// URLs");
}
}
/**
* Handler for skill:// URLs.
*/
export class SkillProtocolHandler implements ProtocolHandler {
readonly scheme = "skill";
readonly immutable = true;
async resolve(url: InternalUrl, context?: ResolveContext): Promise<InternalResource> {
const skills = context?.skills ?? getActiveSkills();
const skillName = url.rawHost || url.hostname;
if (!skillName) {
throw new Error("skill:// URL requires a skill name: skill://<name>");
}
View on GitHub (pinned to 9690622007)
Solutions
- Remove '..' segments — reference files only within the target skill's directory
- Resolve the desired file to a path relative to skill.baseDir with path.relative() and verify it contains no '..'
- For sibling or external files, resolve the other skill directly (skill://other-skill/...) or use file://
- Decode and sanitize user input before constructing the URL
Example fix
// before
resolve(`skill://my-skill/../shared/util.md`)
// after
resolve('skill://shared-skill/util.md') // resolve the owning skill directly Defensive patterns
Strategy: validation
Validate before calling
function assertNoTraversal(p: string): string {
const norm = path.normalize(decodeURIComponent(p));
if (norm.split(/[\\/]/).includes('..') || norm.includes('..')) {
throw new Error(`Traversal rejected: ${p}`);
}
return norm;
}
// run before building the skill:// URL from user input Type guard
function isTraversalFree(p: string): boolean {
return !path.normalize(p).split(/[\\/]/).includes('..');
} Try / catch
try {
return await handler.resolve(url, ctx);
} catch (err) {
if (err instanceof Error && err.message.includes('Path traversal')) {
logger.warn('Rejected traversal attempt', { url });
}
throw err;
} Prevention
- Sanitize user-supplied paths before URL construction, after percent-decoding
- Use path.relative(baseDir, resolvedTarget) and reject results starting with '..'
- Treat any '..' in a URL path as malicious input, never a navigation feature
When it happens
Trigger: Resolving skill://<name>/../other-skill/file.md, skill://<name>/a/../../etc/passwd, or any decoded pathname whose segments include '..'; calling the exported validators (extractRelativePath, splitMemoryGlobPattern, resolveMemoryUrlToPath, decodeVaultPath, validateQueryPath) with '..' in the path.
Common situations: Building a URL by naive string concatenation of user input containing '..'; double-dot segments introduced by URL percent-decoding (%2e%2e); joining a sibling skill's path; glob patterns like '../**/*.md' passed through splitMemoryGlobPattern.
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
- Absolute paths are not allowed in skill:// URLs
- vault:// URL escapes vault root
- Provider delete URL must not embed an account credential
- ${destination} returned an unsupported upload URL
- Destination paths cannot contain parent traversal or NUL byt
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/777129ebcdbb89ae.
Report an issue: GitHub.