can1357/oh-my-pi · error · ArchiveError
XZ output exceeds its size limit
Error message
XZ output exceeds its size limit
What it means
Thrown when the sum of uncompressed sizes declared across all XZ stream index records exceeds the maxOutput budget. Because XZ index records declare sizes up front, the library can refuse before allocating or decoding, protecting against zip-bomb style decompression blowups.
Source
Thrown at packages/utils/src/ar/codecs/xz.ts:502
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;
}
if (blockPosition !== stream.indexStart)
throw new ArchiveError("Invalid XZ stream: blocks do not align with index");
}
return output;
} catch (error) {
if (error instanceof ArchiveError) throw error;
throw new ArchiveError(`Invalid XZ stream: ${error instanceof Error ? error.message : String(error)}`);View on GitHub (pinned to 9690622007)
Solutions
- Raise maxOutput if the input is trusted and genuinely large (account for concatenated streams)
- Check the archive's real uncompressed size first with xz -l or similar and set the limit accordingly
- If processing untrusted input, keep the limit and treat the rejection as expected behavior (potential decompression bomb)
Example fix
// before await xzDecompress(bytes, 64 * 1024 * 1024); // after: allow the declared size plus headroom, still capped const declared = 512 * 1024 * 1024; await xzDecompress(bytes, Math.min(declared, hardCap));
Defensive patterns
Strategy: validation
Validate before calling
import { isXz } from '@oh-my-pi/pi-utils/ar/codecs/xz';
if (!isXz(bytes)) throw new Error('not XZ');
// size the budget to the largest plausible uncompressed output
const MAX = 512 * 1024 * 1024;
await xzDecompress(bytes, MAX); // raises this error only if declared output > MAX Try / catch
try {
const out = await xzDecompress(bytes, MAX_OUTPUT);
} catch (err) {
if (err instanceof ArchiveError && err.message === 'XZ output exceeds its size limit') {
// likely a decompression bomb or a limit too small for a legit large archive
throw new Error('archive exceeds decompression budget; raise MAX_OUTPUT for trusted input only');
} else throw err;
} Prevention
- Set limits based on the archive's known uncompressed size (xz -l)
- Never raise the limit blindly for untrusted input — this check is your bomb guard
- Account for concatenated multi-stream XZ totals
When it happens
Trigger: Calling xzDecompress on an archive whose declared uncompressed total exceeds maxOutput — most often a genuinely large archive passed with a too-small limit, or a maliciously crafted high-ratio archive.
Common situations: Default decompression limits (e.g. 100MB) hit by legitimate large .tar.xz archives; processing untrusted uploads where the limit correctly rejects a bomb; concatenated multi-stream XZ files summing past the cap.
Related errors
- Invalid ARJ basic header size
- ASAR payload is too large to encode safely
- ASAR header is too large to encode
- ASAR archive is too large to encode safely
- Invalid XZ stream: truncated integer
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/8101656644012d1b.
Report an issue: GitHub.