{"record":{"id":"681369a7a512d33b","repo":"nexu-io/open-design","slug":"zip-contains-too-many-files","errorCode":null,"errorMessage":"zip contains too many files","messagePattern":"zip contains too many files","errorType":"validation","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"apps/daemon/src/design/claude-design-import.ts","lineNumber":33,"sourceCode":"  name: string;\n  method: number;\n  compressedSize: number;\n  uncompressedSize: number;\n  localOffset: number;\n  isDirectory: boolean;\n};\n\ntype ImportedFile = { path: string; body: Buffer };\n\nexport async function importClaudeDesignZip(zipPath: string, projectDir: string) {\n  const zip = await readFile(zipPath);\n  const entries = readCentralDirectory(zip);\n  const files: ImportedFile[] = [];\n  let totalBytes = 0;\n\n  for (const entry of entries) {\n    if (entry.isDirectory) continue;\n    if (files.length >= MAX_FILES) throw new Error('zip contains too many files');\n    const relPath = sanitizeZipPath(entry.name);\n    if (entry.uncompressedSize > MAX_FILE_BYTES) {\n      throw new Error(`zip file too large: ${relPath}`);\n    }\n\n    // Decode first; the central directory's uncompressedSize is unreliable for\n    // streaming/data-descriptor zips (it can read 0 even when the payload\n    // carries real data). The inflate cap and the post-decode size checks below\n    // are authoritative.\n    const body = readEntryBody(zip, entry);\n    if (body.length > MAX_FILE_BYTES) {\n      throw new Error(`zip file too large: ${relPath}`);\n    }\n    if (entry.uncompressedSize > 0 && body.length !== entry.uncompressedSize) {\n      throw new Error(`zip entry size mismatch: ${relPath}`);\n    }\n    totalBytes += body.length;\n    if (totalBytes > MAX_TOTAL_BYTES) throw new Error('zip is too large');","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/design/claude-design-import.ts#L15-L51","documentation":"Thrown by `importClaudeDesignZip` when the number of decoded non-directory entries reaches `MAX_FILES` (5000). The check runs at the top of the per-entry loop before adding the next file, capping the total entry count to protect the daemon from zip bombs by file count.","triggerScenarios":"Uploading a Claude Design zip archive that declares 5000 or more file entries. A zip bomb or a maliciously crafted archive intended to exhaust file handles or disk.","commonSituations":"A legitimate but very large export (rare for Claude Design HTML exports). A malicious archive. An archive that includes `node_modules` or other bulky directory trees by accident.","solutions":["Reduce the archive to fewer than 5000 file entries by removing unnecessary files (e.g. `node_modules`, build output).","If exporting from a tool, configure it to emit only the HTML and required assets.","Reject the upload at the request layer if the declared entry count exceeds 5000."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const MAX_FILES = 5000;\n\nasync function zipEntryCountDoesNotExceed(zipPath: string): Promise<boolean> {\n  const zip = await readFile(zipPath);\n  const entries = readCentralDirectory(zip); // reuse the importer's reader\n  return entries.filter((e) => !e.isDirectory).length <= MAX_FILES;\n}\n\nif (!await zipEntryCountDoesNotExceed(zipPath)) {\n  throw new Error('zip exceeds the 5000-file cap');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await importClaudeDesignZip(zipPath, projectDir);\n} catch (err) {\n  if (err instanceof Error && err.message === 'zip contains too many files') {\n    // trim the archive and re-upload\n  }\n  throw err;\n}","preventionTips":["Exclude `node_modules`, build output, and other bulky trees before zipping.","Reject uploads whose declared entry count exceeds 5000 at the request layer.","Pre-validate archives before importing."],"tags":["zip","claude-design","size-limit","import","zip-bomb"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}