{"record":{"id":"56511fb886faa1e9","repo":"can1357/oh-my-pi","slug":"invalid-ar-archive-truncated-member-data","errorCode":null,"errorMessage":"Invalid ar archive: truncated member data","messagePattern":"Invalid ar archive: truncated member data","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/unix-ar.ts","lineNumber":209,"sourceCode":"\tif (!sniffUnixAr(bytes)) throw new ArchiveError(\"Invalid ar archive signature\");\n}\n\n/** Parse a fully materialized Unix ar archive for composition by formats such as deb. */\nexport function readUnixArEntriesFromBuffer(bytes: Uint8Array, options: FormatReadOptions): ArchiveIndexEntry[] {\n\treadSignatureFromBuffer(bytes);\n\tconst records: RawArMember[] = [];\n\tlet longNames: Uint8Array | undefined;\n\tlet metadataSize = 0;\n\tfor (let position = SIGNATURE.length; position < bytes.byteLength; ) {\n\t\tif (bytes.byteLength - position < HEADER_SIZE)\n\t\t\tthrow new ArchiveError(\"Invalid ar archive: truncated member header\");\n\t\tconst header = parseHeader(readMemoryRange(bytes, position, position + HEADER_SIZE));\n\t\tmetadataSize += HEADER_SIZE;\n\t\tassertIndexSize(metadataSize, options.limits, \"index\");\n\t\tconst payloadOffset = position + HEADER_SIZE;\n\t\tconst payloadEnd = payloadOffset + header.physicalSize;\n\t\tif (!Number.isSafeInteger(payloadEnd) || payloadEnd > bytes.byteLength) {\n\t\t\tthrow new ArchiveError(\"Invalid ar archive: truncated member data\");\n\t\t}\n\t\tlet name = header.rawName;\n\t\tlet nameByteLength = Buffer.byteLength(name, \"utf-8\");\n\t\tlet dataOffset = payloadOffset;\n\t\tlet size = header.physicalSize;\n\t\tif (header.bsdNameLength !== undefined) {\n\t\t\tmetadataSize += header.bsdNameLength;\n\t\t\tassertIndexSize(metadataSize, options.limits, \"index\");\n\t\t\tconst nameBytes = readMemoryRange(bytes, payloadOffset, payloadOffset + header.bsdNameLength);\n\t\t\tconst decoded = decodeBsdName(nameBytes, options.limits);\n\t\t\tname = decoded.name;\n\t\t\tnameByteLength = decoded.byteLength;\n\t\t\tdataOffset += header.bsdNameLength;\n\t\t\tsize -= header.bsdNameLength;\n\t\t} else if (header.rawName === \"//\") {\n\t\t\tmetadataSize += header.physicalSize;\n\t\t\tassertIndexSize(metadataSize, options.limits, \"index\");\n\t\t\tlongNames = readMemoryRange(bytes, payloadOffset, payloadEnd);","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/unix-ar.ts#L191-L227","documentation":"After parsing a member header, header.physicalSize implied a payload end beyond the available bytes (or a non-safe integer). The member's data region is cut off, so the record cannot be represented and the archive is rejected.","triggerScenarios":"A member header's size field claims more data than exists (truncated archive, or a corrupted/huge size field like 0xFFFFFFFFFFFFFFFF that breaks Number.isSafeInteger after offset addition).","commonSituations":"Interrupted downloads; archives written with a wrong size field by a buggy tool; corrupted size bytes; mixing ar variants where BSD extended names shift offsets.","solutions":["Re-download/rebuild the archive and verify checksums; the data is genuinely cut off.","Check the size field of the last header with a hex editor if you must salvage; fix or extract with GNU ar.","Ensure the producing tool wrote the correct padded size (BSD ar pads extended names to even lengths).","Pre-compute expected archive size from members when generating, and compare before parsing."],"exampleFix":"// before: trusting header size blindly when salvaging\nconst entries = await readUnixAr(untrustedBytes);\n// after: verify size field plausibility first\nconst size = view.getBigUint64(offset, true); // inspect header size\nif (size > BigInt(untrustedBytes.byteLength)) throw new Error('oversized member size');","handlingStrategy":"validation","validationCode":"// plausible sanity: last 60-byte header's size must fit within the file\nconst lastHeader = bytes.byteLength - 60;\nif (lastHeader >= 8) {\n  const sizeText = new TextDecoder().decode(bytes.subarray(lastHeader + 48, lastHeader + 58)).trim();\n  const size = Number(sizeText);\n  if (Number.isFinite(size) && 8 + lastHeader + 60 + size > bytes.byteLength) {\n    throw new Error('archive truncated: final member data incomplete');\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await readUnixAr(source);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('truncated member data')) {\n    throw new Error(`archive member data cut off — file incomplete or corrupt`);\n  }\n  throw err;\n}","preventionTips":["Compare download size to Content-Length/publisher checksum before parsing.","Validate archives produced by your own writer with `ar t` in CI.","Guard against absurd size fields when ingesting untrusted archives.","Use resumable downloads that retry until the full byte count arrives."],"tags":["archive","ar-format","truncated-file","parsing"],"backgroundTag":"truncated-archive-member-data","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}