different-ai/openwork · error · Error
ZIP data for ${entry.name} is out of bounds.
Error message
ZIP data for ${entry.name} is out of bounds. What it means
Thrown while decompressing a ZIP local file entry inside an Office (docx/xlsx/pptx) attachment: the computed end of the entry's compressed data (dataStart + entry.compressedSize) exceeds the buffer's byte length. The parser validates every entry against the central directory, so this means the local record claims more data than the file physically contains — a truncated or corrupted ZIP.
Source
Thrown at apps/server/src/opencode-plugins/openwork-office-attachments.ts:326
}
function readZipEntryData(buffer: Buffer, entry: ZipEntry): Buffer {
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);
}View on GitHub (pinned to 2b7df46e8a)
Solutions
- Re-upload or re-download the Office file; verify byte size matches the original.
- Check that the caller passes the complete file Buffer, not a truncated slice.
- Remove any transfer that could mangle bytes (e.g., text-mode FTP, bad base64 decode) and retest.
- If files are intentionally larger than limits, confirm MAX_ENTRY_UNCOMPRESSED_BYTES/limits are not causing upstream truncation.
Example fix
// before: passing a possibly truncated slice const data = readZipEntryData(buffer.subarray(0, 1000), entry); // after: pass the full original buffer const data = readZipEntryData(fullBytes, entry);
Defensive patterns
Strategy: validation
Validate before calling
import { stat } from "node:fs/promises";
async function zipLooksComplete(filePath: string): Promise<boolean> {
const buf = await readFile(filePath);
// EOCD signature "PK\x05\x06" must appear in the final 64KB
const eocd = buf.subarray(Math.max(0, buf.byteLength - 65558)).lastIndexOf(Buffer.from("PK0506", "hex"));
return eocd !== -1;
} Type guard
function isTruncated(buf: Buffer, declaredEnd: number): boolean {
return declaredEnd > buf.byteLength; // true => refuse to parse
} Try / catch
try {
const text = extractOfficeText(kind, bytes);
} catch (err) {
if (err instanceof Error && err.message.includes("is out of bounds")) {
throw new ApiError(400, "corrupt_attachment", "Attachment archive is truncated or corrupted; please re-upload.");
}
throw err;
} Prevention
- Verify file size/checksum (e.g., sha256) after download before extraction.
- Use binary-safe transfers only; avoid text-mode transformations.
- Check the ZIP End-of-Central-Directory record exists before parsing.
- Reject uploads that fail the platform's own zip integrity check (unzip -t).
When it happens
Trigger: Calling extractOfficeText (via the office-attachments plugin) on a Buffer whose local header's data region extends past the end of the file: truncated download/upload, byte-sliced buffer, or central-directory sizes disagreeing with a locally re-parsed truncated file.
Common situations: Partially uploaded or interrupted transfers of docx/xlsx files stored via the attachments plugin; files corrupted by text-mode transfer; a caller passing a truncated slice of the original buffer.
Related errors
- ZIP uncompressed size mismatch for ${entry.name}.
- 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/5b2a7a6779f53137.
Report an issue: GitHub.