stablyai/orca · critical
Path "${filePath}" resolves outside the worktree
Error message
Path "${filePath}" resolves outside the worktree What it means
discardChanges resolves the target path against the worktree, then calls isWithinWorktree; if the resolved target equals the worktree root or escapes it (relative is '', '..', or starts with '..<sep>'), it throws before touching git. Without this guard, git restore / git clean could be pointed at files outside the worktree, so it is a security boundary. Note that relative '' (target is the worktree itself) is rejected — you cannot discard the worktree root.
Source
Thrown at src/main/git/status.ts:2138
} finally {
invalidateGitReadCaches()
}
}
/**
* Discard working tree changes for a file.
*/
export async function discardChanges(
worktreePath: string,
filePath: string,
options: GitRuntimeOptions = {}
): Promise<void> {
invalidateGitReadCaches()
const resolvedWorktree = path.resolve(worktreePath)
const resolvedTarget = path.resolve(worktreePath, filePath)
try {
if (!isWithinWorktree(path, resolvedWorktree, resolvedTarget)) {
throw new Error(`Path "${filePath}" resolves outside the worktree`)
}
let tracked = false
try {
await gitExecFileAsync(
['ls-files', '--error-unmatch', '--', literalPathspec(filePath, options)],
{
...gitOptionsForWorktree(worktreePath, options)
}
)
tracked = true
} catch {
// File is not tracked by git
}
if (tracked) {
await gitExecFileAsync(
['restore', '--worktree', '--source=HEAD', '--', literalPathspec(filePath, options)],View on GitHub (pinned to 1136503c6a)
Solutions
- Send file paths relative to the worktree, as git reports them, and ensure they are non-empty.
- Reject '', '.', and '..' at the renderer before issuing a discard IPC.
- If symlinks inside the worktree point outside, treat the discard as unsupported for those entries rather than letting resolve() escape.
- Use isWithinWorktree (exported) at the caller to pre-screen bulk paths.
Example fix
// before
discardChanges(worktreePath, rawPath)
// after: pre-screen at the caller
import { isWithinWorktree } from './status'
import path from 'node:path'
const resolvedTarget = path.resolve(worktreePath, rawPath)
if (!rawPath || !isWithinWorktree(path, path.resolve(worktreePath), resolvedTarget)) {
throw new Error('Refuse to discard a path outside the worktree.')
}
discardChanges(worktreePath, rawPath) Defensive patterns
Strategy: validation
Validate before calling
import path from 'node:path'
import { isWithinWorktree } from './status'
function isSafeDiscardTarget(worktreePath: string, filePath: string): boolean {
if (!filePath) return false
const root = path.resolve(worktreePath)
const target = path.resolve(root, filePath)
return isWithinWorktree(path, root, target)
}
if (!isSafeDiscardTarget(worktreePath, filePath)) throw new Error('Refuse to discard a path outside the worktree.') Type guard
function isDiscardOutsideWorktree(error: unknown): boolean {
return error instanceof Error && /^Path ".+" resolves outside the worktree$/.test(error.message)
} Try / catch
if (!isSafeDiscardTarget(worktreePath, filePath)) {
showStatus('Cannot discard: path is outside the worktree.')
return
}
try { await discardChanges(worktreePath, filePath, options) }
catch (error) { if (isDiscardOutsideWorktree(error)) { showStatus('Cannot discard: path is outside the worktree.'); return } throw error } Prevention
- Send file paths to discard as git reports them (relative to the worktree), never absolute.
- Reject '', '.', and '..' at the renderer before issuing the discard IPC.
- Pre-screen with the exported isWithinWorktree helper at the caller.
When it happens
Trigger: Calling discardChanges(worktreePath, filePath) where filePath is '', '.', '..', an absolute path outside the worktree, or a relative path that climbs above the worktree root (e.g. '../../etc/passwd'). Also when filePath resolves to the worktree directory itself.
Common situations: A UI discard action that sent an empty or stale file path; a path that was pre-resolved to absolute by the renderer; adversarial IPC input; symlinks inside the worktree pointing outside (resolve() follows them); race where the worktree moved between path capture and the discard call.
Related errors
- Clone path must be inside the destination directory
- Access denied: submodule path escapes the selected worktree
- Access denied: invalid submodule path
- Access denied: invalid git file path
- Access denied: git file path escapes the selected worktree
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/c0fc0acd5c7f76fb.
Report an issue: GitHub.