can1357/oh-my-pi · error · ArchiveError

Invalid XZ stream: header CRC32 mismatch

Error message

Invalid XZ stream: header CRC32 mismatch

What it means

The XZ stream header ends with a CRC32 of its two flag bytes. discoverStreams() recomputes the CRC over header bytes start+6..start+8 and compares it to the stored value; a mismatch means the header was altered or corrupted after encoding, so the library refuses to register the stream.

Source

Thrown at packages/utils/src/ar/codecs/xz.ts:145

		checkSize(checkId);
		const indexSize = (read32LE(bytes, footerStart + 4) + 1) * 4;
		if (!Number.isSafeInteger(indexSize) || indexSize > footerStart)
			throw new ArchiveError("Invalid XZ stream: backward index size is invalid");
		const indexStart = footerStart - indexSize;
		const records = parseIndex(bytes, indexStart, indexSize);
		let blocksSize = 0;
		for (const record of records) {
			blocksSize += Math.ceil(record.unpaddedSize / 4) * 4;
			if (!Number.isSafeInteger(blocksSize)) throw new ArchiveError("XZ stream uses sizes too large to read safely");
		}
		const start = indexStart - blocksSize - 12;
		if (start < 0 || start + 12 > bytes.byteLength || !equalBytes(bytes.subarray(start, start + 6), XZ_MAGIC)) {
			throw new ArchiveError("Invalid XZ stream: header position or magic is invalid");
		}
		if (bytes[start + 6] !== flag0 || bytes[start + 7] !== flag1)
			throw new ArchiveError("Invalid XZ stream: header and footer flags differ");
		if (crc32(bytes.subarray(start + 6, start + 8)) !== read32LE(bytes, start + 8))
			throw new ArchiveError("Invalid XZ stream: header CRC32 mismatch");
		streams.unshift({ start, indexStart, footerStart, checkId, records });
		end = start;
		void padding;
	}
	return streams;
}

interface XzFilter {
	id: number;
	properties: Uint8Array;
}

function deltaDecode(bytes: Uint8Array, distance: number): void {
	const history = new Uint8Array(256);
	let position = 0;
	for (let index = 0; index < bytes.byteLength; index++) {
		const value = (bytes[index]! + history[(distance + position) & 0xff]!) & 0xff;
		history[position] = value;

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the file with xz -t / checksum and re-download or restore from backup
  2. Re-compress from original data to regenerate a valid header
  3. Avoid post-compression byte edits; change encoder settings and re-encode instead
  4. If inputs are untrusted, validate archives in a sandbox before processing

Example fix

// before
bytes[8] = 0x42; // header edit without CRC fix
await xzDecode(bytes);
// after
// regenerate: $ xz -c --check=crc64 in > fixed.xz
const bytes = new Uint8Array(await Bun.file("fixed.xz").arrayBuffer());
await xzDecode(bytes);
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try {
  await xzDecode(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("header CRC32 mismatch")) {
    throw new Error("XZ stream header corrupted — restore or re-compress the archive");
  }
  throw err;
}

Prevention

When it happens

Trigger: Corruption or tampering within the 12-byte stream header (magic, flags, or CRC field), a splice that pairs a header from one file with a body from another while flags coincidentally match, or a fuzzed archive.

Common situations: Damaged downloads/storage, files modified by tools that rewrite header bytes without fixing CRC, or maliciously crafted inputs.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/047a9f2c19c6f9f1. Report an issue: GitHub.