{"record":{"id":"fab0077810260081","repo":"stablyai/orca","slug":"spritesheetpath-must-point-to-a-file-not-the-bund","errorCode":null,"errorMessage":"spritesheetPath must point to a file, not the bundle root.","messagePattern":"spritesheetPath must point to a file, not the bundle root\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ipc/pet.ts","lineNumber":296,"sourceCode":"      manifest = applyCodexPetDefaults(PetManifestSchema.parse(JSON.parse(raw)))\n    } catch (error) {\n      throw new Error(`Invalid pet.json: ${error instanceof Error ? error.message : 'parse error'}`)\n    }\n\n    // Why: spritesheetPath is bundle-relative and attacker-controlled — reject absolute/escaping paths (and symlinks) so a bundle can't reach outside.\n    const normalizedSpritePath = manifest.spritesheetPath.replace(/[\\\\/]+/g, sep)\n    if (\n      isAbsolute(manifest.spritesheetPath) ||\n      isAbsolute(normalizedSpritePath) ||\n      /^[a-zA-Z]:/.test(manifest.spritesheetPath)\n    ) {\n      throw new Error('spritesheetPath must be relative to the bundle.')\n    }\n    // Why: bundles exported on Windows may be imported on macOS/Linux; normalize separators before resolving.\n    const sheetSrc = resolve(bundleDir, normalizedSpritePath)\n    const bundleResolved = resolve(bundleDir)\n    if (sheetSrc === bundleResolved) {\n      throw new Error('spritesheetPath must point to a file, not the bundle root.')\n    }\n    const bundleRoot = bundleResolved + sep\n    // Why: Windows volumes are case-insensitive; lowercase the prefix compare so case differences can't bypass the escape check.\n    const cmp = process.platform === 'win32' ? (s: string) => s.toLowerCase() : (s: string) => s\n    if (!cmp(sheetSrc + sep).startsWith(cmp(bundleRoot))) {\n      throw new Error('spritesheetPath escapes the bundle.')\n    }\n    if (await isSymlink(sheetSrc)) {\n      throw new Error('spritesheet must not be a symlink.')\n    }\n    const sheetClass = classifyFile(sheetSrc)\n    if (!sheetClass || sheetClass.ext === '.svg') {\n      // SVG can't be used as a sprite sheet (no pixel grid).\n      throw new Error('Spritesheet must be a PNG, APNG, JPG, GIF, or WebP.')\n    }\n    let sheetStat: Awaited<ReturnType<typeof stat>>\n    try {\n      sheetStat = await stat(sheetSrc)","sourceCodeStart":278,"sourceCodeEnd":314,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ipc/pet.ts#L278-L314","documentation":"Thrown at pet.ts:295-296 when resolve(bundleDir, normalizedSpritePath) equals resolve(bundleDir) — meaning spritesheetPath resolves to the bundle directory itself (empty or all-separator value), not a file. This guards the later directory-walking and copy logic from being pointed at a directory root.","triggerScenarios":"pet.json sets spritesheetPath to '.', '', '.\\', or a value that normalizes away to nothing so that resolve collapses it to bundleDir. Also reachable if the path is all slashes.","commonSituations":"Bundle generator emits an empty spritesheetPath default; a hand-edit sets '.' by mistake; the field was trimmed to empty.","solutions":["Set spritesheetPath to the actual image filename inside the bundle, e.g. \"spritesheet.png\".","Ensure the value is neither empty nor '.' and points to a real file inside the bundle directory."],"exampleFix":"// before\n{ \"spritesheetPath\": \".\" }\n// after\n{ \"spritesheetPath\": \"spritesheet.png\" }","handlingStrategy":"validation","validationCode":"import { resolve } from 'node:path'\nfunction assertSheetIsNotBundleRoot(bundleDir: string, spritePath: string) {\n  const normalized = spritePath.replace(/[\\\\/]+/g, '/')\n  if (resolve(bundleDir, normalized) === resolve(bundleDir)) {\n    throw new Error('spritesheetPath resolves to the bundle root')\n  }\n}","typeGuard":null,"tryCatchPattern":"try { await importPetBundle(p) }\ncatch (e) { if (e instanceof Error && e.message === 'spritesheetPath must point to a file, not the bundle root.') { /* fix manifest */ } else throw e }","preventionTips":["Ensure spritesheetPath is a non-empty filename, never '.' or a bare separator.","Validate the manifest in your export tool before shipping the bundle."],"tags":["pet-bundle","validation","path-resolution"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}