{"record":{"id":"d22396ad770e1ca2","repo":"actualbudget/actual","slug":"zip-too-large","errorCode":"zip-too-large","errorMessage":"zip-too-large","messagePattern":"zip-too-large","errorType":"error_code","errorClass":"FileDownloadError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/cloud-storage.ts","lineNumber":218,"sourceCode":"\n  const availableMemory = memory.getAvailableMemory();\n  if (\n    availableMemory != null &&\n    entries['db.sqlite'].length > availableMemory\n  ) {\n    warnings.push('may-exceed-available-memory');\n  }\n\n  return { data: Buffer.from(zipped), warnings };\n}\n\nexport async function importBuffer(fileData, buffer) {\n  let entries;\n  try {\n    entries = safeUnzip(buffer);\n  } catch (e) {\n    if (e instanceof UnsafeZipError) {\n      throw FileDownloadError('zip-too-large', e.meta);\n    }\n    throw FileDownloadError('not-zip-file');\n  }\n  const entryNames = Object.keys(entries);\n  const dbDirs = entryNames\n    .filter(name => name === 'db.sqlite' || name.endsWith('/db.sqlite'))\n    .map(name => name.slice(0, -'db.sqlite'.length));\n  const metaDirs = entryNames\n    .filter(name => name === 'metadata.json' || name.endsWith('/metadata.json'))\n    .map(name => name.slice(0, -'metadata.json'.length));\n\n  // Both files must come from the same directory: prefer the archive root,\n  // otherwise there must be exactly one directory containing both.\n  const sharedDirs = dbDirs.filter(dir => metaDirs.includes(dir));\n  const dir = sharedDirs.includes('')\n    ? ''\n    : sharedDirs.length === 1\n      ? sharedDirs[0]","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/cloud-storage.ts#L200-L236","documentation":"importBuffer unzips downloaded/imported budget files using safeUnzip, which enforces size limits to prevent zip bombs. When safeUnzip raises UnsafeZipError (archive too large), importBuffer rethrows a FileDownloadError with code 'zip-too-large' plus metadata.","triggerScenarios":"Importing a budget file (download from cloud storage or importActual of a .zip export) whose uncompressed contents exceed the safe size limit — oversized db.sqlite inside the zip, or a malicious/accidental zip bomb.","commonSituations":"Very old, bloated budgets with large unused sqlite pages; importing a corrupted/renamed archive; test fixtures with inflated zips; hosting limits truncating archives.","solutions":["Reduce the archive's uncompressed size (e.g. delete and re-export the budget so sqlite is vacuumed).","Import a fresh export of the budget rather than an old bloated zip.","Check e.meta on the error for the size limits involved and confirm the file isn't truncated (re-download)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const fs = require('fs');\nconst stat = fs.statSync(zipPath);\nif (stat.size > 100 * 1024 * 1024) throw new Error('Archive suspiciously large');","typeGuard":null,"tryCatchPattern":"try {\n  await actual.importActual(zipPath);\n} catch (e) {\n  if (e.code === 'zip-too-large') {\n    console.error('Archive too large:', e.meta);\n  } else throw e;\n}","preventionTips":["Re-export fresh budgets instead of importing old bloated zips","Check archive size before import","Treat huge zips from unknown sources as suspicious"],"tags":["file-import","zip","size-limit","security"],"backgroundTag":"zip-bomb-detected","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}