different-ai/openwork · error · Error

ZIP uncompressed size mismatch for ${entry.name}.

Error message

ZIP uncompressed size mismatch for ${entry.name}.

What it means

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.

Source

Thrown at apps/server/src/opencode-plugins/openwork-office-attachments.ts:329

  const cursor = entry.localOffset;
  if (cursor + 30 > buffer.byteLength || buffer.readUInt32LE(cursor) !== ZIP_LOCAL_FILE_HEADER) throw new Error(`Invalid local ZIP header for ${entry.name}.`);
  const localFlags = buffer.readUInt16LE(cursor + 6);
  const localMethod = buffer.readUInt16LE(cursor + 8);
  const localCompressedSize = buffer.readUInt32LE(cursor + 18);
  const localUncompressedSize = buffer.readUInt32LE(cursor + 22);
  const nameLength = buffer.readUInt16LE(cursor + 26);
  const extraLength = buffer.readUInt16LE(cursor + 28);
  rejectUnsafeZipFlags(localFlags, entry.name);
  if (localMethod !== entry.method) throw new Error(`ZIP method mismatch for ${entry.name}.`);
  if (localCompressedSize !== entry.compressedSize || localUncompressedSize !== entry.uncompressedSize) throw new Error(`ZIP size mismatch for ${entry.name}.`);
  if (cursor + 30 + nameLength + extraLength > buffer.byteLength) throw new Error(`ZIP local header for ${entry.name} is out of bounds.`);
  const localName = buffer.toString("utf8", cursor + 30, cursor + 30 + nameLength);
  if (localName !== entry.name) throw new Error(`ZIP local header name mismatch for ${entry.name}.`);
  const dataStart = cursor + 30 + nameLength + extraLength;
  if (dataStart + entry.compressedSize > buffer.byteLength) throw new Error(`ZIP data for ${entry.name} is out of bounds.`);
  const compressed = buffer.subarray(dataStart, dataStart + entry.compressedSize);
  const data = entry.method === ZIP_STORED ? compressed : inflateRawSync(compressed);
  if (data.byteLength !== entry.uncompressedSize) throw new Error(`ZIP uncompressed size mismatch for ${entry.name}.`);
  return data;
}

function relevantXmlEntry(kind: OfficeKind, name: string): boolean {
  if (!name.endsWith(".xml")) return false;
  if (kind === "docx") {
    return name === "word/document.xml"
      || /^word\/header\d+\.xml$/.test(name)
      || /^word\/footer\d+\.xml$/.test(name)
      || name === "word/footnotes.xml"
      || name === "word/endnotes.xml"
      || name === "word/comments.xml";
  }
  return /^ppt\/slides\/slide\d+\.xml$/.test(name) || /^ppt\/notesSlides\/notesSlide\d+\.xml$/.test(name);
}

function compareEntryName(left: ZipEntry, right: ZipEntry): number {
  return left.name.localeCompare(right.name, undefined, { numeric: true, sensitivity: "base" });

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Obtain a fresh copy of the Office file and retry.
  2. Open the file in a standard zip tool; if it also reports corruption, the file is bad — replace it.
  3. Verify the zip was produced by a mainstream tool (Office, zip CLI) rather than custom code.
  4. If this arrives from untrusted uploaders, treat it as malformed input and reject the attachment with a user-facing message.

Example fix

// before: trusting a corrupted upload
const text = extractOfficeText(kind, uploadedBytes);
// after: pre-validate the archive
if (!isValidZip(uploadedBytes)) throw new ApiError(400, "invalid_attachment", "Attachment archive is corrupted");
const text = extractOfficeText(kind, uploadedBytes);
Defensive patterns

Strategy: try-catch

Validate before calling

import { inflateRawSync } from "node:zlib";
function entryInflatesTo(compressed: Buffer, expected: number): boolean {
  try { return inflateRawSync(compressed).byteLength === expected; } catch { return false; }
}

Type guard

function isSaneEntry(entry: { compressedSize: number; uncompressedSize: number }): boolean {
  return Number.isSafeInteger(entry.uncompressedSize) && entry.uncompressedSize >= 0 && entry.compressedSize >= 0;
}

Try / catch

try {
  const text = extractOfficeText(kind, bytes);
} catch (err) {
  if (err instanceof Error && err.message.includes("uncompressed size mismatch")) {
    throw new ApiError(400, "corrupt_attachment", "Attachment archive does not match its manifest; please re-upload.");
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: Files edited/re-zipped by buggy tools that write wrong sizes in the central directory; maliciously crafted attachments (fuzzed corpora); downloads corrupted in transit.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/741eb9dec5b88e85. Report an issue: GitHub.