stablyai/orca · error · Error
spritesheetPath escapes the bundle.
Error message
spritesheetPath escapes the bundle.
What it means
Thrown at pet.ts:301-302 when the resolved spritesheet path does not start with the bundle root (bundleResolved + sep). This is the primary path-traversal/escape guard: even though Zod rejects a literal '..' at pet.ts:88, the resolve+prefix check is defense-in-depth against encoded separators, mixed separators, and other tricks that bypass the string check. The comparison is case-insensitive on Windows (pet.ts:300) because NTFS volumes are case-insensitive.
Source
Thrown at src/main/ipc/pet.ts:302
const normalizedSpritePath = manifest.spritesheetPath.replace(/[\\/]+/g, sep)
if (
isAbsolute(manifest.spritesheetPath) ||
isAbsolute(normalizedSpritePath) ||
/^[a-zA-Z]:/.test(manifest.spritesheetPath)
) {
throw new Error('spritesheetPath must be relative to the bundle.')
}
// Why: bundles exported on Windows may be imported on macOS/Linux; normalize separators before resolving.
const sheetSrc = resolve(bundleDir, normalizedSpritePath)
const bundleResolved = resolve(bundleDir)
if (sheetSrc === bundleResolved) {
throw new Error('spritesheetPath must point to a file, not the bundle root.')
}
const bundleRoot = bundleResolved + sep
// Why: Windows volumes are case-insensitive; lowercase the prefix compare so case differences can't bypass the escape check.
const cmp = process.platform === 'win32' ? (s: string) => s.toLowerCase() : (s: string) => s
if (!cmp(sheetSrc + sep).startsWith(cmp(bundleRoot))) {
throw new Error('spritesheetPath escapes the bundle.')
}
if (await isSymlink(sheetSrc)) {
throw new Error('spritesheet must not be a symlink.')
}
const sheetClass = classifyFile(sheetSrc)
if (!sheetClass || sheetClass.ext === '.svg') {
// SVG can't be used as a sprite sheet (no pixel grid).
throw new Error('Spritesheet must be a PNG, APNG, JPG, GIF, or WebP.')
}
let sheetStat: Awaited<ReturnType<typeof stat>>
try {
sheetStat = await stat(sheetSrc)
} catch {
throw new Error('Spritesheet file not found.')
}
if (!sheetStat.isFile()) {
throw new Error('Spritesheet path is not a file.')
}View on GitHub (pinned to 1136503c6a)
Solutions
- Make spritesheetPath a plain relative filename inside the bundle directory.
- Remove any '..' segments or absolute components.
- Re-export the bundle from a clean directory containing only the manifest and the spritesheet.
Example fix
// before
{ "spritesheetPath": "../../../../etc/passwd" }
// after
{ "spritesheetPath": "spritesheet.png" } Defensive patterns
Strategy: validation
Validate before calling
import { resolve, sep } from 'node:path'
function assertSheetInsideBundle(bundleDir: string, spritePath: string) {
const sheetSrc = resolve(bundleDir, spritePath.replace(/[\\/]+/g, sep))
const bundleRoot = resolve(bundleDir) + sep
const cmp = process.platform === 'win32' ? (s: string) => s.toLowerCase() : (s: string) => s
if (!cmp(sheetSrc + sep).startsWith(cmp(bundleRoot))) {
throw new Error('spritesheetPath escapes the bundle')
}
} Type guard
function staysInBundle(spritePath: string): boolean {
return !spritePath.includes('..') && !spritePath.startsWith('/') && !spritePath.startsWith('\\')
} Try / catch
try { await importPetBundle(p) }
catch (e) { if (e instanceof Error && e.message === 'spritesheetPath escapes the bundle.') { /* reject bundle as unsafe */ } else throw e } Prevention
- Treat this error as a possible malicious bundle; do not silently retry.
- Keep spritesheetPath a single relative filename with no parent-directory segments.
When it happens
Trigger: spritesheetPath uses '../../etc/passwd' (if it slipped past the Zod refine), a value with mixed forward/back slashes that resolves outside the bundle, or on Windows a same-volume path that differs only in case from the bundle root to bypass a naive prefix check.
Common situations: Deliberately malicious bundle; a bundle author who used a symlinked relative path that points outside; cross-platform separator confusion after copying a bundle between Windows and POSIX.
Related errors
- spritesheetPath must be relative to the bundle.
- Invalid renderer output path: ${String(outputPath)}
- Access denied: invalid submodule path
- Invalid worktree path
- Refusing to place the disposable validation root inside the
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/f51399922d53b9a0.
Report an issue: GitHub.