stablyai/orca · error · Error

Access denied: invalid worktree path

Error message

Access denied: invalid worktree path

What it means

Thrown inside normalizeRemoteHostedReviewPath when the supplied remote worktree path is falsy (empty/null) or contains a NUL byte (\0). The NUL-byte check is a path-traversal / injection guard: NUL bytes can terminate or manipulate paths in lower-level systems. This runs before any authorization comparison, so it rejects malformed input early.

Source

Thrown at src/main/ipc/hosted-review.ts:73

      throw new Error('Access denied: worktree does not belong to repository')
    }
    return remoteWorktreePath
  }
  const resolvedWorktreePath = await resolveRegisteredWorktreePath(worktreePath, store)
  const localGitOptions = getLocalProjectWorktreeGitOptions(store, repo)
  const repoWorktrees =
    Object.keys(localGitOptions).length > 0
      ? await listRepoWorktrees(repo, localGitOptions)
      : await listRepoWorktrees(repo)
  if (!repoWorktrees.some((worktree) => resolve(worktree.path) === resolvedWorktreePath)) {
    throw new Error('Access denied: worktree does not belong to repository')
  }
  return resolvedWorktreePath
}

function normalizeRemoteHostedReviewPath(remotePath: string): string {
  if (!remotePath || remotePath.includes('\0')) {
    throw new Error('Access denied: invalid worktree path')
  }
  // Why: SSH worktree paths belong to the remote POSIX host. Local path.resolve
  // rewrites them on Windows and cannot authorize remote-only paths.
  const normalized = posix.normalize(remotePath)
  return normalized.length > 1 ? normalized.replace(/\/+$/, '') : normalized
}

export function registerHostedReviewHandlers(store: Store, stats: StatsCollector): void {
  ipcMain.handle('hostedReview:forBranch', async (_event, args: HostedReviewForBranchArgs) => {
    const repo = assertRegisteredRepo(args.repoPath, store, args.repoId)
    const localGitOptions = getLocalProjectWorktreeGitOptions(store, repo)
    const review = await getHostedReviewForBranch({
      repoPath: repo.path,
      connectionId: repo.connectionId,
      branch: args.branch,
      linkedGitHubPR: args.linkedGitHubPR ?? null,
      fallbackGitHubPR: args.linkedGitHubPR == null ? (args.fallbackGitHubPR ?? null) : null,
      linkedGitLabMR: args.linkedGitLabMR ?? null,

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Ensure worktreePath is a non-empty string with no NUL bytes before calling the IPC method.
  2. Treat an empty worktreePath explicitly: pass the repo default path or skip the call rather than forwarding an empty value.
  3. Sanitize or reject input containing control characters at the renderer boundary.

Example fix

// before
const path = maybeWorktree?.path ?? ''
await call(worktreePath: path)

// after
if (!worktreePath || worktreePath.includes('\0')) {
  throw new Error('A valid worktree path is required')
}
await call(worktreePath)
Defensive patterns

Strategy: validation

Validate before calling

if (!worktreePath || typeof worktreePath !== 'string' || worktreePath.includes('\0')) {
  throw new Error('A valid non-empty worktree path is required')
}

Type guard

function isValidRemoteWorktreePath(value: unknown): value is string {
  return typeof value === 'string' && value.length > 0 && !value.includes('\0')
}

Prevention

When it happens

Trigger: Passing an empty string, null, or undefined as worktreePath to a remote hosted-review call. Embedding a \0 byte in the path string (e.g. via crafted or corrupted input). Any code path where worktreePath arrives unvalidated from untrusted source over IPC.

Common situations: Renderer sends an empty worktreePath when no worktree is selected but the field is not optional. Deserialized/cached state containing a corrupted string. Adversarial IPC input attempting path injection.

Understand the failure class

Related errors


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