stablyai/orca · error · Error
spritesheetPath must be relative to the bundle.
Error message
spritesheetPath must be relative to the bundle.
What it means
Thrown at pet.ts:284-290 when the manifest's spritesheetPath is absolute on POSIX, absolute after backslash-to-sep normalization, or matches a Windows drive-letter prefix (e.g. C:\). This is a hard security gate: spritesheetPath is attacker-controlled (it comes from the bundle's pet.json) and is resolved relative to the bundle directory, so an absolute value would let a bundle read an arbitrary file outside it.
Source
Thrown at src/main/ipc/pet.ts:290
try {
const raw = await readFile(manifestPath, 'utf8')
// Why: defend against TOCTOU — the file may have grown between the stat check and this read.
if (Buffer.byteLength(raw, 'utf8') > MAX_MANIFEST_BYTES) {
throw new Error('pet.json exceeded the manifest size limit.')
}
manifest = applyCodexPetDefaults(PetManifestSchema.parse(JSON.parse(raw)))
} catch (error) {
throw new Error(`Invalid pet.json: ${error instanceof Error ? error.message : 'parse error'}`)
}
// Why: spritesheetPath is bundle-relative and attacker-controlled — reject absolute/escaping paths (and symlinks) so a bundle can't reach outside.
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') {View on GitHub (pinned to 1136503c6a)
Solutions
- Set spritesheetPath to a path relative to the bundle directory, e.g. "spritesheet.png" or "assets/cat.png".
- Remove any leading '/', '\\', or drive-letter prefix from the value in pet.json.
- Re-export the bundle so the path is relative, then re-import.
Example fix
// before (pet.json)
{ "spritesheetPath": "/home/user/cat.png" }
// after
{ "spritesheetPath": "cat.png" } Defensive patterns
Strategy: validation
Validate before calling
import { isAbsolute } from 'node:path'
function assertRelativeSheetPath(p: string | undefined) {
if (p === undefined) return
const normalized = p.replace(/[\\/]+/g, '/')
if (isAbsolute(p) || isAbsolute(normalized) || /^[a-zA-Z]:/.test(p)) {
throw new Error(`spritesheetPath must be relative: ${p}`)
}
} Type guard
function isRelativeBundlePath(p: string): boolean {
return !p.includes('\0') && !p.startsWith('/') && !p.startsWith('\\') && !/^[a-zA-Z]:/.test(p)
} Try / catch
try { await importPetBundle(p) }
catch (e) { if (e instanceof Error && e.message === 'spritesheetPath must be relative to the bundle.') { /* prompt user to fix manifest */ } else throw e } Prevention
- Always emit spritesheetPath as a bare relative filename from your bundle generator.
- Never hardcode absolute paths from your authoring machine into a portable bundle.
When it happens
Trigger: pet.json sets spritesheetPath to '/etc/passwd', 'C:\Windows\secret.png', '\\?\C:\x.png', or any value that isAbsolute() flags (after replacing backslash runs with sep). The check at lines 285-289 runs immediately after PetManifestSchema.parse succeeds.
Common situations: A bundle author hard-codes an absolute path from their own machine; a Windows-exported bundle carries a drive-letter path; a malicious bundle crafted to exfiltrate a known file.
Related errors
- spritesheetPath escapes 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/d940b05826150041.
Report an issue: GitHub.