{"record":{"id":"492e94ff1155e3e6","repo":"can1357/oh-my-pi","slug":"invalid-cab-archive-truncated-cfdata-header","errorCode":null,"errorMessage":"Invalid CAB archive: truncated CFDATA header","messagePattern":"Invalid CAB archive: truncated CFDATA header","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/cab.ts","lineNumber":146,"sourceCode":"\t\tconst description = this.#description;\n\t\tif (description.method === 2) {\n\t\t\tthrow new ArchiveError(`Unsupported CAB compression method: Quantum (level ${description.parameter})`);\n\t\t}\n\t\tif (description.method > 3) {\n\t\t\tthrow new ArchiveError(`Unsupported CAB compression method: ${description.method}`);\n\t\t}\n\t\tif (description.method === 3 && (description.parameter < 15 || description.parameter > 21)) {\n\t\t\tthrow new ArchiveError(`Unsupported CAB LZX window size: ${description.parameter} bits (expected 15-21)`);\n\t\t}\n\n\t\tconst compressedSize = description.dataEnd - description.dataStart;\n\t\tassertInMemorySize(compressedSize, this.#limits);\n\t\tconst bytes = await readExact(this.#source, description.dataStart, description.dataEnd);\n\t\tlet position = 0;\n\t\tlet outputSize = 0;\n\t\tfor (let block = 0; block < description.blockCount; block++) {\n\t\t\tif (position + DATA_BLOCK_SIZE + this.#dataReserveSize > bytes.byteLength) {\n\t\t\t\tthrow new ArchiveError(\"Invalid CAB archive: truncated CFDATA header\");\n\t\t\t}\n\t\t\tconst compressed = readUInt16LE(bytes, position + 4);\n\t\t\tconst uncompressed = readUInt16LE(bytes, position + 6);\n\t\t\tif (uncompressed === 0) throw new ArchiveError(\"Unsupported multi-volume CAB archive: split CFDATA block\");\n\t\t\tif (uncompressed > MAX_DATA_OUTPUT) {\n\t\t\t\tthrow new ArchiveError(`Invalid CAB archive: CFDATA expands to ${uncompressed} bytes (maximum 32768)`);\n\t\t\t}\n\t\t\tconst payloadStart = position + DATA_BLOCK_SIZE + this.#dataReserveSize;\n\t\t\tconst payloadEnd = payloadStart + compressed;\n\t\t\tif (payloadEnd > bytes.byteLength) throw new ArchiveError(\"Invalid CAB archive: truncated CFDATA payload\");\n\t\t\tconst expectedChecksum = readUInt32LE(bytes, position);\n\t\t\tif (expectedChecksum !== 0) {\n\t\t\t\tconst payloadChecksum = cabChecksum(bytes.subarray(payloadStart, payloadEnd));\n\t\t\t\tconst actualChecksum = cabChecksum(bytes.subarray(position + 4, payloadStart), payloadChecksum);\n\t\t\t\tif (actualChecksum !== expectedChecksum) {\n\t\t\t\t\tthrow new ArchiveError(`Invalid CAB archive: CFDATA block ${block} checksum mismatch`);\n\t\t\t\t}\n\t\t\t}","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/cab.ts#L128-L164","documentation":"While walking description.blockCount CFDATA blocks inside a folder's data region, the reader checks that each block's fixed header (DATA_BLOCK_SIZE plus the folder's reserve area) fits in the bytes read from the source. When it does not, the folder claims more blocks than its data can hold, so the archive is declared truncated. This catches size-field lies in CFHEADER/CFFOLDER versus actual file length.","triggerScenarios":"readAll() where a CFFOLDER's cbData/blockCount fields imply more or larger CFDATA headers than the bytes between dataStart and dataEnd — typically a CAB cut short or with corrupted folder sizes.","commonSituations":"Interrupted downloads of multi-hundred-MB .cab distribution files; antivirus quarantining the tail of a file; archives assembled by concatenating partial volumes; offsets corrupted by a bad disk sector.","solutions":["Re-download/re-copy the .cab and verify its size or SHA against the publisher's checksum.","Run `cabextract -t file.cab` to confirm truncation independently before debugging your code.","Compare the folder's cbData and blockCount against the file's actual remaining length to identify which field is wrong.","If the file is one volume of a set, ensure all volumes are present and you are reading the correct one (multi-volume CABs are rejected elsewhere).","If you generate these CABs, fix the writer so blockCount/cbData match the emitted data."],"exampleFix":"// before: trusting a partial download\nawait readCabArchive(await Bun.file('part.cab').bytes()); // truncated CFDATA header\n// after: verify completeness first\nconst expected = 48317234;\nconst buf = await Bun.file('part.cab').bytes();\nif (buf.byteLength < expected) throw new Error(`CAB incomplete: ${buf.byteLength}/${expected} bytes`);\nawait readCabArchive(buf);","handlingStrategy":"validation","validationCode":"const size = (await Bun.stat(cabPath)).size;\nif (size !== expectedSize)\n  throw new Error(`CAB size mismatch: got ${size}, expected ${expectedSize} — download is truncated`);","typeGuard":null,"tryCatchPattern":"try {\n  return await readCabArchive(bytes);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('truncated CFDATA')) {\n    throw new Error(`CAB folder sizes exceed file length (${bytes.byteLength} bytes) — re-acquire the archive`);\n  }\n  throw err;\n}","preventionTips":["Compare file size/checksum after every download before parsing.","Complete multi-part downloads before opening any part.","Verify shipped CAB fixtures once with cabextract -t.","Watch for antivirus/proxy tooling that truncates or quarantines archive tails."],"tags":["archive","cab","truncated","corruption"],"backgroundTag":"archive-truncated","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}