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
- 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.
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
- 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.
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
- ZIP data for ${entry.name} is out of bounds.
- ZIP central directory entry is out of bounds.
- latest-mac.yml is missing artifact path/url.
- latest-mac.yml is missing sha512.
- invalid_json
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/741eb9dec5b88e85.
Report an issue: GitHub.