{"record":{"id":"e3e83ce5642478a1","repo":"stablyai/orca","slug":"pet-json-exceeded-the-manifest-size-limit","errorCode":null,"errorMessage":"pet.json exceeded the manifest size limit.","messagePattern":"pet\\.json exceeded the manifest size limit\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ipc/pet.ts","lineNumber":276,"sourceCode":"    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.\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)","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ipc/pet.ts#L258-L294","documentation":"Thrown by the pet:importPetBundle handler after readFile(manifestPath) succeeds, when Buffer.byteLength(raw, 'utf8') exceeds MAX_MANIFEST_BYTES (64 KiB). This is the TOCTOU defense explicitly called out in the source comment: the file may have grown between the stat() size check (line 264) and this read, so the byte length is re-checked on the actual content. The comment names the race the guard closes.","triggerScenarios":"Between the stat at line 260 and the readFile at line 273, another process (or the same bundle's own generator) appended data to pet.json so its byte length crosses 64 KiB. Equivalent to error 1237 but caught on content rather than on the earlier stat snapshot.","commonSituations":"A bundle-generation tool was still writing pet.json when the import started and flushed a large payload mid-import; a malicious bundle races the validator; or antivirus/backup rewrote the file between stat and read.","solutions":["Finish writing pet.json before triggering the import — do not import while the manifest is still being generated.","Reduce pet.json below 64 KiB by referencing external spritesheetPath instead of embedding data (same fix as 1237).","Re-export the bundle from its generator so the manifest is a stable, complete file, then import.","If this recurs, check whether a sync/backup tool is rewriting pet.json under the bundle folder."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import { readFile, stat } from 'node:fs/promises'\nimport { join } from 'node:path'\nconst MAX_MANIFEST_BYTES = 64 * 1024\n\nconst path = join(bundleDir, 'pet.json')\nconst raw = await readFile(path, 'utf8')\nif (Buffer.byteLength(raw, 'utf8') > MAX_MANIFEST_BYTES) {\n  notify('pet.json is larger than 64 KiB — reduce it before importing.')\n  return\n}\n// also: only import once the manifest writer has finished\nconst sizeNow = (await stat(path)).size\nif (sizeNow !== raw.length) {\n  notify('pet.json changed during read. Re-export and retry.')\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 exceeded the manifest size limit.') {\n    notify('pet.json grew past 64 KiB during import. Finish writing it, then retry.')\n  } else throw e\n}","preventionTips":["Finish generating pet.json before triggering import — do not import mid-write.","Reference external spritesheetPath instead of embedding data to keep the manifest small.","If it recurs, check whether sync/backup tools rewrite pet.json under the bundle folder."],"tags":["ipc","pet-overlay","filesystem","toctou","size-limit","bundle-import","manifest"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}