{"record":{"id":"2a6a21b5142723e3","repo":"chatboxai/chatbox","slug":"duplicate-zip-entry-entry-name","errorCode":null,"errorMessage":"Duplicate ZIP entry: ${entry.name}","messagePattern":"Duplicate ZIP entry: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/renderer/packages/backup/zip.ts","lineNumber":224,"sourceCode":"\nexport async function readZipFileEntries(\n  file: File,\n  onEntry: (entry: ReadZipEntry) => Promise<void> | void,\n  options: ZipReadOptions = {}\n): Promise<void> {\n  await validateZipEndOfCentralDirectory(file)\n  const limits = { ...DEFAULT_ZIP_LIMITS, ...options.limits }\n  const seenPaths = new Set<string>()\n  const pendingHandlers: Promise<void>[] = []\n  let entryCount = 0\n  let totalUncompressedBytes = 0\n  let fatalError: unknown\n\n  const unzip = new Unzip((entry) => {\n    try {\n      throwIfAborted(options.signal)\n      assertSafeArchivePath(entry.name)\n      if (seenPaths.has(entry.name)) throw new Error(`Duplicate ZIP entry: ${entry.name}`)\n      seenPaths.add(entry.name)\n      entryCount++\n      if (entryCount > limits.maxEntries) throw new Error('ZIP contains too many entries')\n      const entryLimits = { ...limits, ...options.entryLimits?.(entry.name) }\n      if (entry.originalSize !== undefined && entry.originalSize > entryLimits.maxEntryUncompressedBytes) {\n        throw new Error(`ZIP entry is too large: ${entry.name}`)\n      }\n      if (\n        entry.size !== undefined &&\n        entry.originalSize !== undefined &&\n        entry.originalSize > 1024 * 1024 &&\n        entry.originalSize > Math.max(1, entry.size) * entryLimits.maxCompressionRatio\n      ) {\n        throw new Error(`ZIP entry compression ratio is unsafe: ${entry.name}`)\n      }\n\n      const chunks: Uint8Array[] = []\n      let entryBytes = 0","sourceCodeStart":206,"sourceCodeEnd":242,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/renderer/packages/backup/zip.ts#L206-L242","documentation":"Thrown inside the Unzip per-entry callback when an entry.name has already been added to the seenPaths Set. Duplicate entry names are treated as a defect because extraction logic that writes by path would silently overwrite or merge, a common path-collision attack vector against archive extractors.","triggerScenarios":"A zip deliberately crafted with two entries sharing the same name (zip-merge/overwrite attacks), archives produced by buggy mergers that duplicated entries, or zips that list a directory entry and a file entry with identical names.","commonSituations":"Importing a third-party or user-supplied backup, merging two archives with a naive zipper, or processing an archive generated by an exporter that emits redundant directory markers.","solutions":["Regenerate the archive from the original source with a correct zipper so names are unique.","Treat the file as untrusted and reject it; do not attempt to de-duplicate during extraction.","Audit the producer to confirm whether directory entries are double-counted with file entries."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Duplicate detection is streaming; a full pre-scan is costly. The library\n// already enforces uniqueness. Pre-validate only the trusted-source contract:\nfunction isTrustedArchiveSource(source: string): boolean {\n  return source === 'chatbox-backup-export' // only accept your own exporter\n}","typeGuard":null,"tryCatchPattern":"try {\n  await readZipFileEntries(file, onEntry, { signal })\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith('Duplicate ZIP entry')) {\n    reportToUser('The archive contains duplicate files and was rejected for safety.')\n  } else {\n    throw error\n  }\n}","preventionTips":["Only import archives from your own exporter; reject third-party/user-supplied zips for restore.","Never attempt to de-duplicate during extraction; treat duplicates as a corrupt/unsafe archive.","Log the duplicated path so the producer can be fixed."],"tags":["zip","backup","security","dedup","path-collision"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}