{"record":{"id":"741eb9dec5b88e85","repo":"different-ai/openwork","slug":"zip-uncompressed-size-mismatch-for-entry-name","errorCode":null,"errorMessage":"ZIP uncompressed size mismatch for ${entry.name}.","messagePattern":"ZIP uncompressed size mismatch for (.+?)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/server/src/opencode-plugins/openwork-office-attachments.ts","lineNumber":329,"sourceCode":"  const cursor = entry.localOffset;\n  if (cursor + 30 > buffer.byteLength || buffer.readUInt32LE(cursor) !== ZIP_LOCAL_FILE_HEADER) throw new Error(`Invalid local ZIP header for ${entry.name}.`);\n  const localFlags = buffer.readUInt16LE(cursor + 6);\n  const localMethod = buffer.readUInt16LE(cursor + 8);\n  const localCompressedSize = buffer.readUInt32LE(cursor + 18);\n  const localUncompressedSize = buffer.readUInt32LE(cursor + 22);\n  const nameLength = buffer.readUInt16LE(cursor + 26);\n  const extraLength = buffer.readUInt16LE(cursor + 28);\n  rejectUnsafeZipFlags(localFlags, entry.name);\n  if (localMethod !== entry.method) throw new Error(`ZIP method mismatch for ${entry.name}.`);\n  if (localCompressedSize !== entry.compressedSize || localUncompressedSize !== entry.uncompressedSize) throw new Error(`ZIP size mismatch for ${entry.name}.`);\n  if (cursor + 30 + nameLength + extraLength > buffer.byteLength) throw new Error(`ZIP local header for ${entry.name} is out of bounds.`);\n  const localName = buffer.toString(\"utf8\", cursor + 30, cursor + 30 + nameLength);\n  if (localName !== entry.name) throw new Error(`ZIP local header name mismatch for ${entry.name}.`);\n  const dataStart = cursor + 30 + nameLength + extraLength;\n  if (dataStart + entry.compressedSize > buffer.byteLength) throw new Error(`ZIP data for ${entry.name} is out of bounds.`);\n  const compressed = buffer.subarray(dataStart, dataStart + entry.compressedSize);\n  const data = entry.method === ZIP_STORED ? compressed : inflateRawSync(compressed);\n  if (data.byteLength !== entry.uncompressedSize) throw new Error(`ZIP uncompressed size mismatch for ${entry.name}.`);\n  return data;\n}\n\nfunction relevantXmlEntry(kind: OfficeKind, name: string): boolean {\n  if (!name.endsWith(\".xml\")) return false;\n  if (kind === \"docx\") {\n    return name === \"word/document.xml\"\n      || /^word\\/header\\d+\\.xml$/.test(name)\n      || /^word\\/footer\\d+\\.xml$/.test(name)\n      || name === \"word/footnotes.xml\"\n      || name === \"word/endnotes.xml\"\n      || name === \"word/comments.xml\";\n  }\n  return /^ppt\\/slides\\/slide\\d+\\.xml$/.test(name) || /^ppt\\/notesSlides\\/notesSlide\\d+\\.xml$/.test(name);\n}\n\nfunction compareEntryName(left: ZipEntry, right: ZipEntry): number {\n  return left.name.localeCompare(right.name, undefined, { numeric: true, sensitivity: \"base\" });","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/opencode-plugins/openwork-office-attachments.ts#L311-L347","documentation":"After inflating (or reading, for stored entries) an Office ZIP entry's data, the resulting byte length does not match the uncompressedSize declared in the ZIP central directory. This guards against tampered or malformed archives where the inflate output doesn't match the manifest.","triggerScenarios":"extractOfficeText/readZipEntryData on an Office attachment whose entry inflates to a size different from entry.uncompressedSize — corrupted archive, manipulated central directory, or an inflateRawSync that silently produced partial output.","commonSituations":"Files edited/re-zipped by buggy tools that write wrong sizes in the central directory; maliciously crafted attachments (fuzzed corpora); downloads corrupted in transit.","solutions":["Obtain a fresh copy of the Office file and retry.","Open the file in a standard zip tool; if it also reports corruption, the file is bad — replace it.","Verify the zip was produced by a mainstream tool (Office, zip CLI) rather than custom code.","If this arrives from untrusted uploaders, treat it as malformed input and reject the attachment with a user-facing message."],"exampleFix":"// before: trusting a corrupted upload\nconst text = extractOfficeText(kind, uploadedBytes);\n// after: pre-validate the archive\nif (!isValidZip(uploadedBytes)) throw new ApiError(400, \"invalid_attachment\", \"Attachment archive is corrupted\");\nconst text = extractOfficeText(kind, uploadedBytes);","handlingStrategy":"try-catch","validationCode":"import { inflateRawSync } from \"node:zlib\";\nfunction entryInflatesTo(compressed: Buffer, expected: number): boolean {\n  try { return inflateRawSync(compressed).byteLength === expected; } catch { return false; }\n}","typeGuard":"function isSaneEntry(entry: { compressedSize: number; uncompressedSize: number }): boolean {\n  return Number.isSafeInteger(entry.uncompressedSize) && entry.uncompressedSize >= 0 && entry.compressedSize >= 0;\n}","tryCatchPattern":"try {\n  const text = extractOfficeText(kind, bytes);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"uncompressed size mismatch\")) {\n    throw new ApiError(400, \"corrupt_attachment\", \"Attachment archive does not match its manifest; please re-upload.\");\n  }\n  throw err;\n}","preventionTips":["Re-zip with standard tools (Office, Info-ZIP) if you generate files programmatically.","Test generated archives with unzip -t before serving.","Treat mismatch as hostile input from untrusted uploaders — reject, don't retry.","Keep checksums of original files to detect in-transit corruption."],"tags":["zip","corrupt-file","parsing"],"backgroundTag":"zip-entry-out-of-bounds","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}