can1357/oh-my-pi · error

Absolute paths are not allowed in skill:// URLs

Error message

Absolute paths are not allowed in skill:// URLs

What it means

validateRelativePath() guards skill:// URL path components against unsafe paths. Any path that Node's path.isAbsolute() accepts (e.g. '/etc/passwd', 'C:\\data') is rejected because skill:// paths must stay relative to the skill's base directory. This is a security boundary preventing absolute-path escapes when the path is later joined with skill.baseDir.

Source

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

import * as path from "node:path";
import { isEnoent } from "@oh-my-pi/pi-utils";
import { resolveContainedPath } from "../discovery/contained-path";
import { getActiveSkills } from "../extensibility/skills";
import { isMarkdownPath } from "../utils/lang-from-path";
import { buildDirectoryResource } from "./filesystem-resource";
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, UrlCompletion } from "./types";

function getContentType(filePath: string): InternalResource["contentType"] {
	if (isMarkdownPath(filePath)) return "text/markdown";
	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";

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass a path relative to the skill's base directory, e.g. skill://my-skill/examples/foo.md instead of skill://my-skill//abs/path
  2. Strip the skill.baseDir prefix from your absolute path (path.relative) before embedding it in the URL
  3. If the file lives outside the skill directory, access it through file:// rather than skill://

Example fix

// before
resolve(`skill://my-skill${path.resolve('/home/me/skills/my-skill/notes.md')}`)
// after
const rel = path.relative(skill.baseDir, '/home/me/skills/my-skill/notes.md');
resolve(`skill://my-skill/${rel}`)
Defensive patterns

Strategy: validation

Validate before calling

import * as path from 'node:path';
export function toSkillUrl(skillName: string, baseDir: string, absolutePath: string): string {
  const rel = path.relative(baseDir, absolutePath);
  if (path.isAbsolute(rel) || rel.startsWith('..')) {
    throw new Error(`${absolutePath} is not inside skill ${skillName}`);
  }
  return `skill://${skillName}/${rel}`;
}

Type guard

function isSafeRelative(p: string): boolean {
  return !path.isAbsolute(p) && !p.split(/[\\/]/).includes('..');
}

Try / catch

try {
  return await handler.resolve(url, ctx);
} catch (err) {
  if (err instanceof Error && err.message.includes('Absolute paths are not allowed')) {
    // convert to a relative path or fall back to file://
  }
  throw err;
}

Prevention

When it happens

Trigger: resolve() on skill://<name>/... where the pathname (after decodeURIComponent) starts with '/', 'X:', or '\\\\server\\share'; also direct calls to validateRelativePath(), extractRelativePath(), splitMemoryGlobPattern(), resolveMemoryUrlToPath(), decodeVaultPath(), or validateQueryPath() with an absolute path.

Common situations: Concatenating a filesystem absolute path into a skill:// URL instead of a relative one; URL-encoding an absolute path that decodes to a leading slash; building URLs from user-supplied file paths without stripping the skill directory prefix; Windows drive-letter paths leaking into URLs.

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/4d535b2facdc5372. Report an issue: GitHub.