stablyai/orca · critical
Access denied: path resolves outside allowed directories. If
Error message
Access denied: path resolves outside allowed directories. If this blocks a legitimate workflow, please file a GitHub issue.
What it means
Primary filesystem-auth boundary in `resolveAuthorizedPath` (default, non-symlink-preserving branch): after `resolve(targetPath)`, the resolved target is not inside any allowed directory, registered worktree, or canonical allowed/registered root (checked via `isPathAllowedIncludingRegisteredWorktrees`). The message includes guidance to file an issue because the check is deliberately conservative.
Source
Thrown at src/main/ipc/filesystem-auth.ts:296
error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT'
)
}
export type ResolveAuthorizedPathOptions = {
/**
* Canonicalize the parent but preserve the leaf so delete/rename target the symlink itself, not its destination (which may live outside allowed roots).
*/
preserveSymlink?: boolean
}
export async function resolveAuthorizedPath(
targetPath: string,
store: Store,
options: ResolveAuthorizedPathOptions = {}
): Promise<string> {
const resolvedTarget = resolve(targetPath)
if (!(await isPathAllowedIncludingRegisteredWorktrees(resolvedTarget, store))) {
throw new Error(PATH_ACCESS_DENIED_MESSAGE)
}
if (options.preserveSymlink) {
// Canonicalize the parent so ancestor symlinks can't redirect outside allowed roots, but keep the leaf so delete/rename act on the link itself.
let realParent: string
try {
realParent = await realpath(dirname(resolvedTarget))
} catch (error) {
if (isENOENT(error)) {
return resolveAuthorizedMissingPath(resolvedTarget, store)
}
throw error
}
const candidateTarget = resolve(realParent, basename(resolvedTarget))
if (
!(await isPathAllowedIncludingRegisteredWorktrees(candidateTarget, store, {
canonicalSourcePath: resolvedTarget
}))View on GitHub (pinned to 1136503c6a)
Solutions
- Confirm the path is inside a registered repo/folder-workspace root or a registered git worktree.
- If the path is legitimately external and user-authorized, call `authorizeExternalPath(path)` before resolving.
- Call `invalidateAuthorizedRootsCache()` after repo/worktree registration changes, then retry.
Example fix
// before
const p = await resolveAuthorizedPath('/etc/hosts', store)
// after
const p = await resolveAuthorizedPath(join(repoPath, 'README.md'), store)
// or, for a legitimately external user-chosen path:
authorizeExternalPath(userChosenExternalPath)
const p = await resolveAuthorizedPath(userChosenExternalPath, store) Defensive patterns
Strategy: validation
Validate before calling
// Before resolving, confirm the path is inside an allowed root or pre-authorize it.
import { isPathInsideOrEqual } from '../../shared/cross-platform-path'
const resolved = resolve(targetPath)
const allowedRoots = getAllowedRoots(store) // repo/folder-workspace/project-group roots
if (!allowedRoots.some((root) => isPathInsideOrEqual(resolved, root))) {
authorizeExternalPath(resolved) // only if the user explicitly approved this external path
}
await resolveAuthorizedPath(targetPath, store) Type guard
function isInsideAnyRoot(p: string, roots: string[]): boolean {
return roots.some((root) => isPathInsideOrEqual(p, root))
} Try / catch
try {
return await resolveAuthorizedPath(targetPath, store)
} catch (e) {
if (e instanceof Error && e.message.startsWith('Access denied: path resolves outside')) {
// surface to user; only authorize external path on explicit consent
throw new PermissionDeniedError(e.message)
}
throw e
} Prevention
- Always derive paths from registered repo/folder-workspace roots rather than user-typed strings.
- Call invalidateAuthorizedRootsCache() after registration changes so new roots are honored.
- Use authorizeExternalPath only for paths the user explicitly chose via a native picker.
When it happens
Trigger: Calling `resolveAuthorizedPath(path, store)` with a path whose resolved form is outside every repo root, folder-workspace root, project-group subtree, registered worktree, and externally-authorized path.
Common situations: Renderer passes an absolute path from outside the workspace (e.g. `/etc/hosts`); a symlink inside the allowed root points outside it; path computed against the wrong workspace; a newly added repo whose roots cache has not been refreshed.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Access denied: git file path escapes the selected worktree
- Access denied: unknown repository or worktree path
- Access denied: invalid git file path
- Path escaped upload root during staging: '${displayPath}'
- Clone path must be inside the destination directory
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/b7a3b36b66c71904.
Report an issue: GitHub.