{"record":{"id":"71d7807dbd8127e1","repo":"nexu-io/open-design","slug":"invalid-zip-missing-central-directory","errorCode":null,"errorMessage":"invalid zip: missing central directory","messagePattern":"invalid zip: missing central directory","errorType":"validation","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"apps/daemon/src/design/claude-design-import.ts","lineNumber":229,"sourceCode":"    entries.push({\n      name,\n      method,\n      compressedSize,\n      uncompressedSize,\n      localOffset,\n      isDirectory: name.endsWith('/'),\n    });\n    offset += 46 + nameLen + extraLen + commentLen;\n  }\n  return entries;\n}\n\nfunction findEndOfCentralDirectory(zip: Buffer): number {\n  const min = Math.max(0, zip.length - 0xffff - 22);\n  for (let i = zip.length - 22; i >= min; i -= 1) {\n    if (zip.readUInt32LE(i) === EOCD_SIG) return i;\n  }\n  throw new Error('invalid zip: missing central directory');\n}\n\nfunction readEntryBody(zip: Buffer, entry: ZipEntry): Buffer {\n  const offset = entry.localOffset;\n  if (zip.readUInt32LE(offset) !== LOCAL_SIG) {\n    throw new Error(`invalid zip local header: ${entry.name}`);\n  }\n  const nameLen = zip.readUInt16LE(offset + 26);\n  const extraLen = zip.readUInt16LE(offset + 28);\n  const bodyStart = offset + 30 + nameLen + extraLen;\n  const bodyEnd = bodyStart + entry.compressedSize;\n  if (bodyEnd > zip.length) throw new Error(`zip entry exceeds archive: ${entry.name}`);\n  const compressed = zip.slice(bodyStart, bodyEnd);\n  if (entry.method === 0) return Buffer.from(compressed);\n  // A genuinely empty deflate payload would still occupy at least the BFINAL\n  // marker; an entirely missing payload cannot be inflated, so treat it as\n  // empty rather than handing a zero-length buffer to zlib.\n  if (compressed.length === 0) return Buffer.alloc(0);","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/design/claude-design-import.ts#L211-L247","documentation":"findEndOfCentralDirectory scans backward from the end of the buffer (up to 65557 bytes, the max EOCD + comment) for the End-of-Central-Directory signature 0x06054b50 (EOCD_SIG). If no match is found, the buffer has no recognizable EOCD record and is rejected. Maps to HTTP 400.","triggerScenarios":"importClaudeDesignZip on a buffer that contains no EOCD signature: a non-zip file renamed to .zip, an empty file, a gzip/tar/rar, or a zip whose trailing comment exceeds 0xFFFF bytes and masks the EOCD outside the scan window.","commonSituations":"Wrong file type uploaded (html/json/png renamed .zip); a truncated upload (EOCD lives at the end); uploading a .gz or .tar by mistake; an empty file.","solutions":["Confirm the file is genuinely a zip (`file upload.zip` should report 'Zip archive data').","Re-export from Claude Design and re-upload.","Check the uploaded file size is non-zero and matches the source.","If the zip has a very large comment, re-create it without the comment."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Reject non-zip uploads before invoking the importer.\nimport { execFileSync } from 'node:child_process';\nfunction isZipArchive(file: string): boolean {\n  try {\n    const out = execFileSync('file', ['-b', file], { encoding: 'utf8' });\n    return /Zip archive/i.test(out);\n  } catch {\n    return false;\n  }\n}\nif (!isZipArchive(uploadPath)) return res.status(400).json({ error: 'expected a real zip' });","typeGuard":null,"tryCatchPattern":"try {\n  await importClaudeDesignZip(zipPath, projectDir);\n} catch (err) {\n  if (String(err).includes('missing central directory')) {\n    return res.status(400).json({ error: 'file is not a valid zip archive' });\n  }\n  throw err;\n}","preventionTips":["Gate uploads with a `file` / magic-byte check, not just the .zip extension.","Reject zero-byte uploads.","Confirm the upload completed before persisting."],"tags":["zip","parsing","eocd","import","claude-design"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}