{"record":{"id":"6d54aee592caaeb6","repo":"stablyai/orca","slug":"pet-json-is-invalid","errorCode":null,"errorMessage":"pet.json is invalid.","messagePattern":"pet\\.json is invalid\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/ipc/pet.ts","lineNumber":265,"sourceCode":"    }\n    const picked = result.filePaths[0]\n    let bundleDir: string\n    try {\n      const pickedStat = await stat(picked)\n      bundleDir = pickedStat.isDirectory() ? picked : dirname(picked)\n    } catch {\n      throw new Error('Could not read the selected path.')\n    }\n\n    const manifestPath = join(bundleDir, 'pet.json')\n    let manifestStat: Awaited<ReturnType<typeof stat>>\n    try {\n      manifestStat = await stat(manifestPath)\n    } catch {\n      throw new Error('Bundle is missing pet.json.')\n    }\n    if (!manifestStat.isFile() || manifestStat.size > MAX_MANIFEST_BYTES) {\n      throw new Error('pet.json is invalid.')\n    }\n    if (await isSymlink(manifestPath)) {\n      throw new Error('pet.json must not be a symlink.')\n    }\n\n    let manifest: ResolvedPetManifest<PetManifest>\n    try {\n      const raw = await readFile(manifestPath, 'utf8')\n      // Why: defend against TOCTOU — the file may have grown between the stat check and this read.\n      if (Buffer.byteLength(raw, 'utf8') > MAX_MANIFEST_BYTES) {\n        throw new Error('pet.json exceeded the manifest size limit.')\n      }\n      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.","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ipc/pet.ts#L247-L283","documentation":"Thrown by the pet:importPetBundle handler when stat(manifestPath) succeeds but either manifestStat.isFile() is false (pet.json is a directory/device) or manifestStat.size already exceeds MAX_MANIFEST_BYTES (64 KiB, defined as 64 * 1024). The size pre-check is an early guard; a second TOCTOU check at line 276 re-verifies after reading. pet.json is spec'd to be tiny, so an oversized manifest is treated as malicious/stuffed.","triggerScenarios":"pet.json is actually a directory (e.g. the user named a folder 'pet.json'), or pet.json is larger than 64 KiB due to embedded base64, a huge animations map, or padding/junk appended to the file.","commonSituations":"A bundle generator embedded a base64-encoded spritesheet inside pet.json instead of referencing spritesheetPath; a hand-edited manifest grew past 64 KiB; or the manifest was corrupted/padded.","solutions":["Keep pet.json small — reference spritesheetPath rather than embedding image bytes.","Confirm pet.json is a regular file, not a directory or device node.","Trim oversized animations maps and long description fields (description is capped at 2000 chars by schema anyway).","If you genuinely need a large manifest, that exceeds the format — restructure so binary assets live as separate files."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import { stat } from 'node:fs/promises'\nimport { join } from 'node:path'\nconst MAX_MANIFEST_BYTES = 64 * 1024\n\nconst s = await stat(join(bundleDir, 'pet.json'))\nif (!s.isFile() || s.size > MAX_MANIFEST_BYTES) {\n  notify('pet.json is invalid or too large (max 64 KiB).')\n  return\n}","typeGuard":null,"tryCatchPattern":"try {\n  await ipcRenderer.invoke('pet:importPetBundle')\n} catch (e) {\n  if (e instanceof Error && e.message === 'pet.json is invalid.') {\n    notify('pet.json is the wrong type or exceeds 64 KiB. Reference external spritesheetPath instead of embedding data.')\n  } else throw e\n}","preventionTips":["Keep pet.json tiny — never embed image bytes; use spritesheetPath.","Confirm pet.json is a regular file, not a directory.","Trim large animations maps before exporting the bundle."],"tags":["ipc","pet-overlay","filesystem","size-limit","bundle-import","manifest"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}