stablyai/orca · critical
Access denied: invalid submodule path
Error message
Access denied: invalid submodule path
What it means
resolveSubmoduleWorktreePath builds a submodule's own worktree path from a parent worktree plus a relative submodule path. It throws this message when submodulePath is empty, contains a NUL byte, or is an absolute path — none of those can be a valid relative submodule entry, and accepting them would let a caller target arbitrary filesystem locations. This is the input-shape guard; the escape guard at :508 is the second layer.
Source
Thrown at src/main/git/status.ts:503
timeout: GIT_BRANCH_LINE_TOTAL_TIMEOUT_MS
}).then((result) => result.stdout),
...(options.signal ? { signal: options.signal } : {})
})
}
}
function getStatusLineStatsCacheKey(worktreePath: string, options: GitRuntimeOptions = {}): string {
// Why: identical paths can map to different WSL-distro filesystems, so key stats by Git's execution host.
return `${options.wslDistro ?? 'native'}\0${worktreePath}`
}
/**
* Resolve a submodule's own worktree path from a parent worktree + relative
* submodule path, rejecting anything that escapes the parent.
*/
export function resolveSubmoduleWorktreePath(worktreePath: string, submodulePath: string): string {
if (!submodulePath || submodulePath.includes('\0') || path.isAbsolute(submodulePath)) {
throw new Error('Access denied: invalid submodule path')
}
const resolved = path.resolve(worktreePath, submodulePath)
const rel = path.relative(worktreePath, resolved)
if (!rel || rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
throw new Error('Access denied: submodule path escapes the selected worktree')
}
return resolved
}
/**
* Run a plain status inside a submodule's own worktree (lazy "expand submodule"
* flow). Entry paths are relative to the submodule root; the renderer prefixes them.
*/
export async function getSubmoduleStatus(
worktreePath: string,
submodulePath: string,
options: GetStatusOptions & { staged?: boolean } = {}
): Promise<GitStatusResult> {View on GitHub (pinned to 1136503c6a)
Solutions
- Pass a non-empty, relative submodule path as it appears in .gitmodules / git submodule status.
- Reject empty or NUL-containing paths at the caller before invoking the function.
- If you have an absolute path, compute the relative path against the parent worktree first (path.relative).
- Do not feed user-typed arbitrary paths directly — source them from the submodule listing.
Example fix
// before
resolveSubmoduleWorktreePath(worktreePath, rawEntry)
// after: validate shape and relativize
if (!rawEntry || rawEntry.includes('\0') || path.isAbsolute(rawEntry)) {
throw new Error('Submodule path must be a non-empty relative path.')
}
resolveSubmoduleWorktreePath(worktreePath, rawEntry) Defensive patterns
Strategy: validation
Validate before calling
import path from 'node:path'
function isValidSubmodulePath(submodulePath: string): boolean {
return Boolean(submodulePath) && !submodulePath.includes('\0') && !path.isAbsolute(submodulePath)
}
if (!isValidSubmodulePath(submodulePath)) throw new Error('Submodule path must be a non-empty relative path.') Type guard
function isInvalidSubmodulePath(error: unknown): boolean {
return error instanceof Error && error.message === 'Access denied: invalid submodule path'
} Try / catch
if (!isValidSubmodulePath(submodulePath)) {
throw new Error('Refuse to load submodule: path is empty, absolute, or contains a NUL byte.')
}
try { return resolveSubmoduleWorktreePath(worktreePath, submodulePath) }
catch (error) { if (isInvalidSubmodulePath(error)) { showStatus('Cannot open submodule: invalid path.'); return null } throw error } Prevention
- Always pass submodule paths as git reports them (relative, from .gitmodules / git submodule status).
- Reject empty and NUL-containing paths at the caller boundary.
- If you hold an absolute path, relativize it against the parent worktree first.
When it happens
Trigger: Calling resolveSubmoduleWorktreePath(worktreePath, submodulePath) (directly, or via getSubmoduleStatus / loadDiff routing) with submodulePath = '', a path containing '\0', or an absolute path like '/etc' or 'C:\Windows'.
Common situations: A UI entry that fed an empty submodule path because the selection was not yet ready; a malformed .gitmodules entry that produced an empty path; adversarial or corrupted path input from an untrusted source; a path that was pre-resolved to absolute by a caller that did not realise the function expects a relative path.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Access denied: submodule path escapes the selected worktree
- Invalid renderer output path: ${String(outputPath)}
- Invalid repository name derived from URL
- Clone path must be inside the destination directory
- Path "${filePath}" resolves outside the worktree
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/4c9944a019086d7f.
Report an issue: GitHub.