can1357/oh-my-pi · error · ArchiveError

Invalid XZ output limit

Error message

Invalid XZ output limit

What it means

Thrown when the maxOutput argument passed to xzDecompress is not a valid non-negative safe integer. The limit is validated up front before any decoding work happens, so this is a caller-side input bug, not a data problem.

Source

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

	output = output.slice();
	for (let index = filters.length - 2; index >= 0; index--) applyFilter(output, filters[index]!);
	for (let index = compressedEnd; index < checkStart; index++)
		if (bytes[index] !== 0) throw new ArchiveError("Invalid XZ stream: non-zero block padding");
	verifyCheck(checkId, output, bytes.subarray(checkStart, checkStart + integritySize));
	const paddedEnd = offset + Math.ceil(record.unpaddedSize / 4) * 4;
	if (checkStart + integritySize !== paddedEnd)
		throw new ArchiveError("Invalid XZ stream: block size does not match its index record");
	return output;
}

/** Whether bytes begin with the XZ stream-header magic. */
export function isXz(bytes: Uint8Array): boolean {
	return bytes.byteLength >= XZ_MAGIC.byteLength && equalBytes(bytes.subarray(0, XZ_MAGIC.byteLength), XZ_MAGIC);
}

/** Decompress all concatenated streams in an XZ container within `maxOutput`. */
export async function xzDecompress(bytes: Uint8Array, maxOutput: number): Promise<Uint8Array> {
	if (!Number.isSafeInteger(maxOutput) || maxOutput < 0) throw new ArchiveError("Invalid XZ output limit");
	try {
		const streams = discoverStreams(bytes);
		let totalSize = 0;
		for (const stream of streams)
			for (const record of stream.records) {
				totalSize += record.uncompressedSize;
				if (!Number.isSafeInteger(totalSize) || totalSize > maxOutput)
					throw new ArchiveError("XZ output exceeds its size limit");
			}
		const output = new Uint8Array(totalSize);
		let outputPosition = 0;
		for (const stream of streams) {
			let blockPosition = stream.start + 12;
			for (const record of stream.records) {
				const block = await decodeBlock(bytes, blockPosition, record, stream.checkId);
				output.set(block, outputPosition);
				outputPosition += block.byteLength;
				blockPosition += Math.ceil(record.unpaddedSize / 4) * 4;

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass a non-negative safe integer, e.g. 64 * 1024 * 1024
  2. Sanitize the value first: maxOutput = Math.floor(Number(x)); check Number.isSafeInteger
  3. Fix the config/flag parsing that produced the bad value

Example fix

// before
await xzDecompress(bytes, Number(opts.maxMb) * 1024); // NaN if opts.maxMb undefined
// after
const limit = Math.max(0, Math.floor(Number(opts.maxMb ?? 64) * 1024 * 1024));
await xzDecompress(bytes, limit);
Defensive patterns

Strategy: validation

Validate before calling

function validLimit(n: unknown): n is number {
  return typeof n === 'number' && Number.isSafeInteger(n) && n >= 0;
}
if (!validLimit(maxOutput)) throw new TypeError(`bad maxOutput: ${maxOutput}`);

Type guard

function isSizeLimit(v: unknown): v is number {
  return typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
}

Try / catch

try {
  const out = await xzDecompress(bytes, limit);
} catch (err) {
  if (err instanceof ArchiveError && err.message === 'Invalid XZ output limit') {
    // caller bug, not data bug: fix the limit value upstream
  } else throw err;
}

Prevention

When it happens

Trigger: Calling xzDecompress(bytes, maxOutput) with NaN, a negative number, a float, a non-integer, or a value beyond Number.MAX_SAFE_INTEGER.

Common situations: maxOutput read from misparsed config/CLI args (e.g. parseInt returning NaN); unit confusion (MiB vs bytes producing fractional values); unbounded defaults like -1 or Infinity.

Related errors


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