{"record":{"id":"fbdcbe96b1102413","repo":"can1357/oh-my-pi","slug":"invalid-zip-archive-truncated-extra-field-header","errorCode":null,"errorMessage":"Invalid ZIP archive: truncated extra-field header","messagePattern":"Invalid ZIP archive: truncated extra-field header","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/zip.ts","lineNumber":259,"sourceCode":"\nfunction parseDosMtime(time: number, date: number): number | undefined {\n\tif (date === 0) return undefined;\n\tconst year = 1980 + ((date >>> 9) & 0x7f);\n\tconst month = ((date >>> 5) & 0x0f) - 1;\n\tconst day = date & 0x1f;\n\tconst hour = (time >>> 11) & 0x1f;\n\tconst minute = (time >>> 5) & 0x3f;\n\tconst second = (time & 0x1f) * 2;\n\tif (month < 0 || month > 11 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59) return undefined;\n\tconst value = new Date(year, month, day, hour, minute, second).getTime();\n\treturn Number.isFinite(value) ? value : undefined;\n}\n\nfunction parseExtra(extra: Uint8Array, rawName: Uint8Array): ParsedExtra {\n\tconst result: ParsedExtra = {};\n\tlet offset = 0;\n\twhile (offset < extra.byteLength) {\n\t\tif (offset + 4 > extra.byteLength) throw new ArchiveError(\"Invalid ZIP archive: truncated extra-field header\");\n\t\tconst id = readUInt16LE(extra, offset);\n\t\tconst size = readUInt16LE(extra, offset + 2);\n\t\tconst dataStart = offset + 4;\n\t\tconst dataEnd = dataStart + size;\n\t\tif (dataEnd > extra.byteLength) throw new ArchiveError(\"Invalid ZIP archive: malformed extra field\");\n\t\tconst data = extra.subarray(dataStart, dataEnd);\n\t\tif (id === 0x0001) {\n\t\t\tresult.zip64 = data;\n\t\t} else if (id === 0x7075) {\n\t\t\tif (data.byteLength < 5) throw new ArchiveError(\"Invalid ZIP archive: Unicode path extra field is too small\");\n\t\t\tif (data[0] === 1 && readUInt32LE(data, 1) === crc32(rawName)) {\n\t\t\t\ttry {\n\t\t\t\t\tresult.unicodePath = UTF8_FATAL_DECODER.decode(data.subarray(5));\n\t\t\t\t} catch {\n\t\t\t\t\t// A bad optional Unicode path falls back to the header name.\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (id === 0x5455) {","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/zip.ts#L241-L277","documentation":"Thrown by parseExtra when, while iterating an entry's extra-field blob as a sequence of (id:u16, size:u16, data) records, fewer than 4 bytes remain where a record header is expected. The extra-field region ends mid-header, so it cannot be parsed.","triggerScenarios":"Calling zip info/read on an archive where a central-directory or local-header extra field's total length leaves 1–3 trailing bytes, i.e. the extra_field_length in the surrounding header does not match the sum of contained record sizes.","commonSituations":"Archives produced by writers that pad extra fields incorrectly; corrupted archives where a record's size field was altered; zip files modified in place by tools that rewrote lengths inconsistently.","solutions":["Validate with `unzip -t` / `zipinfo -v` to see which entry has inconsistent extra-field lengths.","Re-zip the archive with a standard tool to regenerate consistent headers.","Repair with `zip -FF` if the archive is otherwise valuable.","If you control the writer, ensure every extra record is exactly 4+size bytes and extra_field_length equals the sum."],"exampleFix":"// before: trusting a third-party writer's output\nconst zip = readZip(legacyToolZip);\n// after: rebuild with a canonical writer\nawait $`unzip -q legacyTool.zip -d tmp`;\nawait $`zip -qr fixed.zip .`.cwd(\"tmp\");\nconst zip = readZip(\"fixed.zip\");","handlingStrategy":"try-catch","validationCode":"const probe = Bun.spawnSync([\"zipinfo\", path]);\nif (probe.exitCode !== 0) throw new Error(`zip headers inconsistent (zipinfo failed); re-zip before reading: ${path}`);","typeGuard":null,"tryCatchPattern":"try {\n  const zip = readZip(path);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes(\"truncated extra-field header\")) {\n    // rebuild to regenerate consistent extra fields\n    await $`unzip -q ${path} -d tmp`; await $`zip -qr fixed.zip .`.cwd(\"tmp\");\n    return readZip(\"fixed.zip\");\n  }\n  throw err;\n}","preventionTips":["Avoid zip writers that pad or hand-assemble extra fields; test their output with `zipinfo -v`.","Never modify extra_field_length without rewriting the contained records.","Checksum-verify archives from untrusted sources before parsing.","Route all zip production through one canonical tool in your pipeline."],"tags":["zip","archive","extra-field","corruption"],"backgroundTag":"zip-extra-field-malformed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}