{"record":{"id":"54064ee181b29526","repo":"can1357/oh-my-pi","slug":"invalid-zip-archive-malformed-local-header-for","errorCode":null,"errorMessage":"Invalid ZIP archive: malformed local header for '${memberPath}'","messagePattern":"Invalid ZIP archive: malformed local header for '(.+?)'","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/zip.ts","lineNumber":406,"sourceCode":"\n\tasync read(size: number, memberPath: string): Promise<Uint8Array> {\n\t\ttry {\n\t\t\tassertArchiveMemberSize(Math.max(size, this.#compressedSize), memberPath, this.#limits);\n\t\t\tif ((this.#flags & (ENCRYPTED_FLAG | STRONG_ENCRYPTION_FLAG)) !== 0 || this.#method === 99) {\n\t\t\t\tthrow new ArchiveError(`Encrypted ZIP member '${memberPath}' is not supported`);\n\t\t\t}\n\t\t\tif (SUPPORTED_METHODS[this.#method] !== true) {\n\t\t\t\tthrow new ArchiveError(`Unsupported ZIP compression method ${this.#method} for '${memberPath}'`);\n\t\t\t}\n\t\t\tconst headerEnd = checkedEnd(\n\t\t\t\tthis.#localHeaderOffset,\n\t\t\t\t30,\n\t\t\t\tthis.#source.size,\n\t\t\t\t`local header for '${memberPath}'`,\n\t\t\t);\n\t\t\tconst header = await this.#source.read(this.#localHeaderOffset, headerEnd);\n\t\t\tif (header.byteLength !== 30 || readUInt32LE(header, 0) !== LOCAL_HEADER_SIGNATURE) {\n\t\t\t\tthrow new ArchiveError(`Invalid ZIP archive: malformed local header for '${memberPath}'`);\n\t\t\t}\n\t\t\tconst localFlags = readUInt16LE(header, 6);\n\t\t\tif ((localFlags & (ENCRYPTED_FLAG | STRONG_ENCRYPTION_FLAG)) !== 0) {\n\t\t\t\tthrow new ArchiveError(`Encrypted ZIP member '${memberPath}' is not supported`);\n\t\t\t}\n\t\t\tif (readUInt16LE(header, 8) !== this.#method) {\n\t\t\t\tthrow new ArchiveError(\n\t\t\t\t\t`Invalid ZIP archive: local and central compression methods disagree for '${memberPath}'`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst dataStart = this.#localHeaderOffset + 30 + readUInt16LE(header, 26) + readUInt16LE(header, 28);\n\t\t\tconst dataEnd = checkedEnd(dataStart, this.#compressedSize, this.#source.size, `data for '${memberPath}'`);\n\t\t\tif (this.#method === 0 && this.#compressedSize !== size) {\n\t\t\t\tthrow new ArchiveError(\n\t\t\t\t\t`Invalid ZIP archive: size mismatch for '${memberPath}' (expected ${size}, got ${this.#compressedSize})`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst compressed = await this.#source.read(dataStart, dataEnd);","sourceCodeStart":388,"sourceCodeEnd":424,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/zip.ts#L388-L424","documentation":"Thrown during member read when the 30-byte local file header at the recorded offset is missing, shorter than 30 bytes, or does not begin with the local header signature 0x04034b50. The central directory pointed at a position that is not a valid local header, so the archive structure is corrupt or inconsistent. Raised as an ArchiveError for the specific member.","triggerScenarios":"ZipMemberSource reads a member whose central-directory localHeaderOffset points into a region without the PK\\x03\\x04 signature — e.g. after in-place editing, byte truncation, or a wrong-offset central directory; also when the source was truncated so fewer than 30 bytes remain at that offset.","commonSituations":"Partially downloaded or interrupted ZIP transfers; files modified by tools that rewrite the central directory without relocating local headers; zips concatenated or stripped of leading bytes (some self-extracting archives); hand-patched archives.","solutions":["Re-obtain the archive and verify integrity (`unzip -t archive.zip`) — the file is structurally damaged","Re-download the file; check its size/hash against the publisher's checksum","If it came from a stream, ensure the complete bytes were captured (no truncation at end)","Try `zip -FF broken.zip --out fixed.zip` to salvage the structure"],"exampleFix":"// before: reading a truncated download\nconst zip = await readZip(Bun.file('partial.zip')); // throws 3711\n// after: verify first\nconst ok = (await Bun.file('full.zip').arrayBuffer()).byteLength === expectedSize;","handlingStrategy":"validation","validationCode":"const bytes = new Uint8Array(await file.arrayBuffer());\nconst off = findLocalHeaderOffset(bytes, member); // from central directory\nif (off + 30 > bytes.byteLength || readU32(bytes, off) !== 0x04034b50) throw new Error('archive structurally damaged; re-download');","typeGuard":null,"tryCatchPattern":"try {\n  const data = await zip.read(member);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('malformed local header')) {\n    throw new Error('ZIP is corrupt (local header missing) — re-download or repair with zip -FF');\n  }\n  throw err;\n}","preventionTips":["Verify file size/checksum immediately after download, before parsing","Never hex-patch zips; regenerate them from source files","Avoid opening files mid-transfer; wait for copy/upload completion","Run `unzip -t` as a preflight gate in pipelines"],"tags":["zip","corrupt-archive","signature-mismatch","archive-parsing"],"backgroundTag":"corrupt-zip-archive","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}