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

  1. Remove or un-symlink the escaping entry from the source tree before importing.
  2. Restructure so external targets are copied inside the upload root rather than symlinked out.
  3. 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

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


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/bc67c8f937003d60. Report an issue: GitHub.