stablyai/orca · error · Error
Upload source escapes selected directory: ${candidatePath}
Error message
Upload source escapes selected directory: ${candidatePath} What it means
Thrown by assertLocalUploadPathInsideRoot() during recursive SSH directory upload. For each visited path it resolves realpath(candidate) and computes its path relative to rootRealPath; if that relative path is '..', starts with '../', or is absolute, the candidate resolves outside the upload root and the import is rejected. This is a security guard against symlink-mediated path traversal: a child entry whose realpath target escapes the selected directory.
Source
Thrown at src/main/ipc/filesystem-import-ssh-directory.ts:104
left: number | bigint | undefined,
right: number | bigint | undefined
): boolean {
const leftKnown = left !== undefined && left !== 0 && left !== 0n
const rightKnown = right !== undefined && right !== 0 && right !== 0n
return leftKnown && rightKnown && left !== right
}
async function assertLocalUploadPathInsideRoot(
rootRealPath: string,
candidatePath: string
): Promise<void> {
const candidateRealPath = await realpath(candidatePath)
const relativeToRoot = relative(rootRealPath, candidateRealPath)
if (
relativeToRoot !== '' &&
(relativeToRoot === '..' || relativeToRoot.startsWith(`..${sep}`) || isAbsolute(relativeToRoot))
) {
throw new Error(`Upload source escapes selected directory: ${candidatePath}`)
}
}
View on GitHub (pinned to 1136503c6a)
Solutions
- Remove or un-symlink the escaping entry from the source tree before importing.
- Restructure so external targets are copied inside the upload root rather than symlinked out.
- If the escape is intentional, copy the referenced content into the tree instead of importing the symlink.
Defensive patterns
Strategy: validation
Validate before calling
// Pre-scan for escaping symlinks before calling uploadSshImportDirectory
import { realpath, readdir } from 'node:fs/promises'
import { relative, sep } from 'node:path'
async function hasEscapingSymlink(rootReal: string, dir: string): Promise<boolean> {
for (const e of await readdir(dir, { withFileTypes: true })) {
const child = joinPath(dir, e.name)
if (e.isSymbolicLink()) {
const rel = relative(rootReal, await realpath(child))
if (rel === '..' || rel.startsWith('..' + sep)) return true
}
if (e.isDirectory() && await hasEscapingSymlink(rootReal, child)) return true
}
return false
} Try / catch
try {
await uploadSshImportDirectory(provider, session, localDir, remoteDir, root, flavor, assert)
} catch (e) {
if (e instanceof Error && /escapes selected directory/.test(e.message)) {
// report which path escaped and abort the import for this source
} else throw e
} Prevention
- Run a realpath-based escape pre-scan before upload and reject escaping sources in the UI.
- Teach users that symlinked children pointing outside the dropped root are not uploadable.
- Copy external symlink targets into the tree rather than linking them.
When it happens
Trigger: uploadSshImportDirectory walks a directory that contains a symlinked child (or a child reachable through a symlinked intermediate) whose realpath resolves outside rootRealPath. Because realpath follows links, any entry pointing out of the root trips the check, even before the later symlink skip.
Common situations: Dropped/imported directory contains symlinks to /etc, a sibling project, or an absolute outside path; a node_modules-style symlinked package whose real target lives outside the dropped root; a directory whose parent component is itself a symlink escaping the root.
Related errors
- Managed Claude auth child file is not owned by Orca.
- Path escaped upload root during staging: '${displayPath}'
- Invalid renderer output path: ${String(outputPath)}
- [verify-packaged-plugin-resources] bundled plugin path escap
- Clone path must be inside the destination directory
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/bc67c8f937003d60.
Report an issue: GitHub.