{"record":{"id":"8101656644012d1b","repo":"can1357/oh-my-pi","slug":"xz-output-exceeds-its-size-limit","errorCode":null,"errorMessage":"XZ output exceeds its size limit","messagePattern":"XZ output exceeds its size limit","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/codecs/xz.ts","lineNumber":502,"sourceCode":"\treturn output;\n}\n\n/** Whether bytes begin with the XZ stream-header magic. */\nexport function isXz(bytes: Uint8Array): boolean {\n\treturn bytes.byteLength >= XZ_MAGIC.byteLength && equalBytes(bytes.subarray(0, XZ_MAGIC.byteLength), XZ_MAGIC);\n}\n\n/** Decompress all concatenated streams in an XZ container within `maxOutput`. */\nexport async function xzDecompress(bytes: Uint8Array, maxOutput: number): Promise<Uint8Array> {\n\tif (!Number.isSafeInteger(maxOutput) || maxOutput < 0) throw new ArchiveError(\"Invalid XZ output limit\");\n\ttry {\n\t\tconst streams = discoverStreams(bytes);\n\t\tlet totalSize = 0;\n\t\tfor (const stream of streams)\n\t\t\tfor (const record of stream.records) {\n\t\t\t\ttotalSize += record.uncompressedSize;\n\t\t\t\tif (!Number.isSafeInteger(totalSize) || totalSize > maxOutput)\n\t\t\t\t\tthrow new ArchiveError(\"XZ output exceeds its size limit\");\n\t\t\t}\n\t\tconst output = new Uint8Array(totalSize);\n\t\tlet outputPosition = 0;\n\t\tfor (const stream of streams) {\n\t\t\tlet blockPosition = stream.start + 12;\n\t\t\tfor (const record of stream.records) {\n\t\t\t\tconst block = await decodeBlock(bytes, blockPosition, record, stream.checkId);\n\t\t\t\toutput.set(block, outputPosition);\n\t\t\t\toutputPosition += block.byteLength;\n\t\t\t\tblockPosition += Math.ceil(record.unpaddedSize / 4) * 4;\n\t\t\t}\n\t\t\tif (blockPosition !== stream.indexStart)\n\t\t\t\tthrow new ArchiveError(\"Invalid XZ stream: blocks do not align with index\");\n\t\t}\n\t\treturn output;\n\t} catch (error) {\n\t\tif (error instanceof ArchiveError) throw error;\n\t\tthrow new ArchiveError(`Invalid XZ stream: ${error instanceof Error ? error.message : String(error)}`);","sourceCodeStart":484,"sourceCodeEnd":520,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/codecs/xz.ts#L484-L520","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\nawait xzDecompress(bytes, 64 * 1024 * 1024);\n// after: allow the declared size plus headroom, still capped\nconst declared = 512 * 1024 * 1024;\nawait xzDecompress(bytes, Math.min(declared, hardCap));","handlingStrategy":"validation","validationCode":"import { isXz } from '@oh-my-pi/pi-utils/ar/codecs/xz';\nif (!isXz(bytes)) throw new Error('not XZ');\n// size the budget to the largest plausible uncompressed output\nconst MAX = 512 * 1024 * 1024;\nawait xzDecompress(bytes, MAX); // raises this error only if declared output > MAX","typeGuard":null,"tryCatchPattern":"try {\n  const out = await xzDecompress(bytes, MAX_OUTPUT);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message === 'XZ output exceeds its size limit') {\n    // likely a decompression bomb or a limit too small for a legit large archive\n    throw new Error('archive exceeds decompression budget; raise MAX_OUTPUT for trusted input only');\n  } else throw err;\n}","preventionTips":["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"],"tags":["xz","decompression-bomb","size-limit","archive"],"backgroundTag":"decompression-limit-exceeded","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}