{"record":{"id":"571c4fbee34c9701","repo":"can1357/oh-my-pi","slug":"invalid-zip-archive-malformed-central-directory","errorCode":null,"errorMessage":"Invalid ZIP archive: malformed central directory","messagePattern":"Invalid ZIP archive: malformed central directory","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/zip.ts","lineNumber":457,"sourceCode":"\t\t} catch (error) {\n\t\t\tthrow archiveError(error, `Failed to read ZIP member '${memberPath}'`);\n\t\t}\n\t}\n}\n\nfunction parseCentralDirectory(\n\tsource: ByteSource,\n\tdirectory: Uint8Array,\n\tinfo: CentralDirectoryInfo,\n\toptions: FormatReadOptions,\n): ParsedZipEntry[] {\n\tconst parsed: ParsedZipEntry[] = [];\n\tlet offset = 0;\n\tfor (let index = 0; index < info.entries; index++) {\n\t\tif (offset + 46 > directory.byteLength)\n\t\t\tthrow new ArchiveError(\"Invalid ZIP archive: truncated central directory\");\n\t\tif (readUInt32LE(directory, offset) !== CENTRAL_HEADER_SIGNATURE) {\n\t\t\tthrow new ArchiveError(\"Invalid ZIP archive: malformed central directory\");\n\t\t}\n\t\tconst versionMadeBy = readUInt16LE(directory, offset + 4);\n\t\tconst flags = readUInt16LE(directory, offset + 8);\n\t\tconst method = readUInt16LE(directory, offset + 10);\n\t\tconst dosTime = readUInt16LE(directory, offset + 12);\n\t\tconst dosDate = readUInt16LE(directory, offset + 14);\n\t\tconst crc = readUInt32LE(directory, offset + 16);\n\t\tconst compressedRaw = readUInt32LE(directory, offset + 20);\n\t\tconst uncompressedRaw = readUInt32LE(directory, offset + 24);\n\t\tconst nameLength = readUInt16LE(directory, offset + 28);\n\t\tconst extraLength = readUInt16LE(directory, offset + 30);\n\t\tconst commentLength = readUInt16LE(directory, offset + 32);\n\t\tconst diskStartRaw = readUInt16LE(directory, offset + 34);\n\t\tconst externalAttributes = readUInt32LE(directory, offset + 38);\n\t\tconst localOffsetRaw = readUInt32LE(directory, offset + 42);\n\t\tconst nameStart = offset + 46;\n\t\tconst extraStart = nameStart + nameLength;\n\t\tconst commentStart = extraStart + extraLength;","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/zip.ts#L439-L475","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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`"],"exampleFix":"// before: parsing a zip with corrupted central directory\nconst entries = await listZipMembers(buf); // throws 3719\n// after: repair structure first\n// $ `zip -FF broken.zip --out fixed.zip`\nconst entries = await listZipMembers(await Bun.file('fixed.zip').bytes());","handlingStrategy":"validation","validationCode":"// Preflight: every 46-byte stride in the directory must begin with the central signature\nconst u32 = (b, o) => b[o] | (b[o+1] << 8) | (b[o+2] << 16) | (b[o+3] << 24);\nfor (let off = 0; off < directoryLen; ) {\n  if (u32(dirBytes, off) !== 0x02014b50) throw new Error('malformed central directory — archive untrusted');\n  off += 46 + u16(dirBytes, off + 28) + u16(dirBytes, off + 30) + u16(dirBytes, off + 32);\n}","typeGuard":null,"tryCatchPattern":"try {\n  const zip = await readZip(source);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('malformed central directory')) {\n    throw new Error('central directory corrupted or entry count falsified; reject or repair with zip -FF');\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["zip","central-directory","signature-mismatch","malformed-archive"],"backgroundTag":"corrupt-zip-archive","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}