{"record":{"id":"dde135f124bc593f","repo":"can1357/oh-my-pi","slug":"invalid-xz-stream-truncated-block-header","errorCode":null,"errorMessage":"Invalid XZ stream: truncated block header","messagePattern":"Invalid XZ stream: truncated block header","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/codecs/xz.ts","lineNumber":433,"sourceCode":"\t\treturn;\n\t}\n\tif (checkId === 4) {\n\t\tconst actual = crc64(output);\n\t\tlet stored = 0n;\n\t\tfor (let index = 0; index < 8; index++) stored |= BigInt(expected[index]!) << BigInt(index * 8);\n\t\tif (actual !== stored) throw new ArchiveError(\"Invalid XZ stream: block CRC64 mismatch\");\n\t\treturn;\n\t}\n\tconst actual = new Uint8Array(new Bun.CryptoHasher(\"sha256\").update(output).digest());\n\tif (!equalBytes(actual, expected)) throw new ArchiveError(\"Invalid XZ stream: block SHA-256 mismatch\");\n}\n\nasync function decodeBlock(bytes: Uint8Array, offset: number, record: XzRecord, checkId: number): Promise<Uint8Array> {\n\tif (offset >= bytes.byteLength || bytes[offset] === 0)\n\t\tthrow new ArchiveError(\"Invalid XZ stream: missing block header\");\n\tconst headerSize = (bytes[offset]! + 1) * 4;\n\tif (offset + headerSize > bytes.byteLength || headerSize < 8)\n\t\tthrow new ArchiveError(\"Invalid XZ stream: truncated block header\");\n\tif (crc32(bytes.subarray(offset, offset + headerSize - 4)) !== read32LE(bytes, offset + headerSize - 4))\n\t\tthrow new ArchiveError(\"Invalid XZ stream: block header CRC32 mismatch\");\n\tconst cursor: Cursor = { bytes, pos: offset + 1, limit: offset + headerSize - 4 };\n\tconst flags = bytes[cursor.pos++]!;\n\tif ((flags & 0x3c) !== 0) throw new ArchiveError(\"Unsupported XZ block flags\");\n\tconst filterCount = (flags & 3) + 1;\n\tconst declaredCompressed = (flags & 0x40) !== 0 ? readVarInt(cursor) : undefined;\n\tconst declaredUncompressed = (flags & 0x80) !== 0 ? readVarInt(cursor) : undefined;\n\tconst filters: XzFilter[] = [];\n\tfor (let index = 0; index < filterCount; index++) {\n\t\tconst id = readVarInt(cursor);\n\t\tconst propertySize = readVarInt(cursor);\n\t\tif (propertySize > cursor.limit - cursor.pos)\n\t\t\tthrow new ArchiveError(\"Invalid XZ stream: truncated filter properties\");\n\t\tfilters.push({ id, properties: bytes.slice(cursor.pos, cursor.pos + propertySize) });\n\t\tcursor.pos += propertySize;\n\t}\n\twhile (cursor.pos < cursor.limit)","sourceCodeStart":415,"sourceCodeEnd":451,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/codecs/xz.ts#L415-L451","documentation":"The first byte of an XZ block header encodes the header size as ((byte / 4) - 1), so the computed size must be a multiple of 4 within spec bounds; the library also requires the header to fit within the remaining buffer and be at least 8 bytes (2 size units). It throws when the computed header size runs past the end of the data or is below the minimum, i.e. the block header is truncated or nonsensical.","triggerScenarios":"Decoding a truncated XZ file where the block header region is cut off; a corrupted first header byte yielding headerSize < 8 or a size that overruns the buffer.","commonSituations":"Partial downloads; files sliced or streamed incorrectly; bit corruption in the size byte; fuzzed inputs.","solutions":["Re-download/restore the complete archive and confirm byte size matches the source","Test with `xz -t` externally to confirm the file itself is truncated/corrupt","If reading from a stream, buffer the entire input before decoding instead of decoding a partial chunk","Check that no upstream code altered offsets into the buffer (e.g. skipping the stream header incorrectly)"],"exampleFix":"// before: passing a partial stream of unknown completeness\nconst head = buf.subarray(0, 1024);\nawait decodeXz(head);\n// after: guard for completeness or read the full file\nconst full = new Uint8Array(await Bun.file('archive.xz').arrayBuffer());\nawait decodeXz(full);","handlingStrategy":"validation","validationCode":"const bytes = new Uint8Array(await Bun.file('archive.xz').arrayBuffer());\nif (bytes.byteLength < 60) throw new Error('File too small to contain a complete XZ stream');","typeGuard":"null","tryCatchPattern":"try {\n  return await decodeXz(bytes);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('truncated block header')) {\n    throw new Error('XZ block header is cut off — input truncated; fetch the complete archive');\n  }\n  throw err;\n}","preventionTips":["Complete the download and verify byte count before decoding","Don't slice streams for parallel decode unless each slice is a full independent block/stream","Keep offsets consistent with the stream header parsing this decoder performs","Run `xz -t` on files from unknown producers"],"tags":["xz","archive","truncated-input","header","corrupt-input"],"backgroundTag":"truncated-archive","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}