can1357/oh-my-pi · error · ArchiveError
Invalid ZIP archive: malformed central directory
Error message
Invalid ZIP archive: malformed central directory
What it means
Thrown while walking the central directory when a record at the current offset does not start with the central header signature 0x02014b50. The directory is present and long enough, but its contents are not the expected sequence of central file headers. ArchiveError.
Source
Thrown at packages/utils/src/ar/zip.ts:457
} catch (error) {
throw archiveError(error, `Failed to read ZIP member '${memberPath}'`);
}
}
}
function parseCentralDirectory(
source: ByteSource,
directory: Uint8Array,
info: CentralDirectoryInfo,
options: FormatReadOptions,
): ParsedZipEntry[] {
const parsed: ParsedZipEntry[] = [];
let offset = 0;
for (let index = 0; index < info.entries; index++) {
if (offset + 46 > directory.byteLength)
throw new ArchiveError("Invalid ZIP archive: truncated central directory");
if (readUInt32LE(directory, offset) !== CENTRAL_HEADER_SIGNATURE) {
throw new ArchiveError("Invalid ZIP archive: malformed central directory");
}
const versionMadeBy = readUInt16LE(directory, offset + 4);
const flags = readUInt16LE(directory, offset + 8);
const method = readUInt16LE(directory, offset + 10);
const dosTime = readUInt16LE(directory, offset + 12);
const dosDate = readUInt16LE(directory, offset + 14);
const crc = readUInt32LE(directory, offset + 16);
const compressedRaw = readUInt32LE(directory, offset + 20);
const uncompressedRaw = readUInt32LE(directory, offset + 24);
const nameLength = readUInt16LE(directory, offset + 28);
const extraLength = readUInt16LE(directory, offset + 30);
const commentLength = readUInt16LE(directory, offset + 32);
const diskStartRaw = readUInt16LE(directory, offset + 34);
const externalAttributes = readUInt32LE(directory, offset + 38);
const localOffsetRaw = readUInt32LE(directory, offset + 42);
const nameStart = offset + 46;
const extraStart = nameStart + nameLength;
const commentStart = extraStart + extraLength;View on GitHub (pinned to 9690622007)
Solutions
- Verify with `unzip -t`; treat the archive as corrupt and re-obtain it
- If the file is untrusted, reject it — signature disagreement is a classic malformed-zip indicator
- Re-zip from original sources to regenerate a consistent central directory
- If you must salvage, extract members via local headers with `zip -FF`
Example fix
// before: parsing a zip with corrupted central directory
const entries = await listZipMembers(buf); // throws 3719
// after: repair structure first
// $ `zip -FF broken.zip --out fixed.zip`
const entries = await listZipMembers(await Bun.file('fixed.zip').bytes()); Defensive patterns
Strategy: validation
Validate before calling
// Preflight: every 46-byte stride in the directory must begin with the central signature
const u32 = (b, o) => b[o] | (b[o+1] << 8) | (b[o+2] << 16) | (b[o+3] << 24);
for (let off = 0; off < directoryLen; ) {
if (u32(dirBytes, off) !== 0x02014b50) throw new Error('malformed central directory — archive untrusted');
off += 46 + u16(dirBytes, off + 28) + u16(dirBytes, off + 30) + u16(dirBytes, off + 32);
} Try / catch
try {
const zip = await readZip(source);
} catch (err) {
if (err instanceof ArchiveError && err.message.includes('malformed central directory')) {
throw new Error('central directory corrupted or entry count falsified; reject or repair with zip -FF');
}
throw err;
} Prevention
- Treat signature mismatches in untrusted archives as rejection criteria (malformed-zip hardening)
- Only ingest archives from vetted writers; validate with `unzip -t` first
- Never patch EOCD entry counts manually; regenerate archives
- Keep raw bytes when reporting the error to aid diagnosis
When it happens
Trigger: parseZipDirectory reads info.entries entries but readUInt32LE(directory, offset) !== CENTRAL_HEADER_SIGNATURE — entries count too high (EOCD lies), directory bytes corrupted/overwritten, or offset misalignment from a mis-sliced buffer.
Common situations: Malicious or fuzzed zip files (signature mismatch at entry N); EOCD entry count patched by broken tools; archives whose central directory was partially overwritten while sizes stayed consistent; zips built by nonstandard writers.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Invalid ZIP archive: central directory is out of bounds or m
- Invalid ZIP archive: truncated central directory
- Invalid ZIP archive: malformed local header for '${memberPat
- Invalid EPUB: missing content.opf
- Invalid ZIP archive: ${what} has an invalid range
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/571c4fbee34c9701.
Report an issue: GitHub.