can1357/oh-my-pi · error
local:// URL escapes local root
Error message
local:// URL escapes local root
What it means
ensureWithinRoot enforces that any local:// target — after path resolution and realpath (symlink) expansion — still lies inside the session's local root directory. It throws when the resolved path is neither the root itself nor a descendant of it. This is a path-traversal / symlink-escape security guard.
Source
Thrown at packages/coding-agent/src/internal-urls/local-protocol.ts:23
import { AgentRegistry } from "../registry/agent-registry";
import { isMarkdownPath } from "../utils/lang-from-path";
import { buildDirectoryResource } from "./filesystem-resource";
import { parseInternalUrl } from "./parse";
import { validateRelativePath } from "./skill-protocol";
import type { InternalResource, InternalUrl, ProtocolHandler, ResolveContext, UrlCompletion } from "./types";
export interface LocalProtocolOptions {
getArtifactsDir?: () => string | null;
getSessionId?: () => string | null;
}
function parseLocalUrl(input: string): InternalUrl {
return parseInternalUrl(input);
}
function ensureWithinRoot(targetPath: string, rootPath: string): void {
if (targetPath !== rootPath && !targetPath.startsWith(`${rootPath}${path.sep}`)) {
throw new Error("local:// URL escapes local root");
}
}
function toLocalValidationError(error: unknown): Error {
const message = error instanceof Error ? error.message : String(error);
return new Error(message.replace("skill://", "local://"));
}
const WINDOWS_LOCAL_ROOT_MAX_CHARS = 180;
function safeSessionId(options: LocalProtocolOptions): string {
const raw = options.getSessionId?.() ?? "session";
const safe = raw.replace(/[^a-zA-Z0-9_.-]/g, "_");
return safe.length > 0 ? safe : "session";
}
function shortLocalRoot(options: LocalProtocolOptions): string {
// Derive the short root from the stable session id, never the artifact path,
// so `SessionManager.moveTo()` and the resume-after-move flow keep findingView on GitHub (pinned to 9690622007)
Solutions
- Remove or replace the symlink inside the local root that points outside it.
- Only reference paths genuinely under the session local root (visible via local:// listing).
- If you legitimately need external files, read them with the normal file tools, not local://.
Defensive patterns
Strategy: validation
Validate before calling
const rel = url.replace(/^local:\/\//, '');
if (rel.includes('..') || path.isAbsolute(rel)) throw new Error('local:// path must be relative and stay inside the session root'); Try / catch
try { resource = await handler.resolve(url, ctx); } catch (e) { if (e.message === 'local:// URL escapes local root') { /* treat as security rejection; log and refuse */ } else throw e; } Prevention
- Only reference paths shown in the local:// root listing.
- Never create symlinks inside the session local artifacts directory.
- Treat this error as a security signal — investigate the URL source rather than working around it.
When it happens
Trigger: Resolving local://../../etc/passwd (validateRelativePath usually catches ../ first, but realpath-expansion can still expose escapes); a symlink inside the local root pointing at a file outside it, e.g. local://link where link -> /etc/passwd; resolveLocalUrlToPath or resolveLocalTarget with a crafted InternalUrl.
Common situations: A user or LLM writes a symlink into the session's local artifacts dir; sessions whose artifacts dir was relocated without copying contents; hostile input embedded in model-generated local:// 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
- ${scheme}:// path escapes its root: ${rawPath}
- {scheme}:// path escapes its root: {path}
- #{scheme}:// path escapes its root: #{path}
- Path traversal is not allowed
- Archive symlink escapes extraction dir: ${link.path} -> ${li
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/141b02fd846565eb.
Report an issue: GitHub.