{"record":{"id":"d39fb3f8e0450016","repo":"can1357/oh-my-pi","slug":"invalid-cab-archive-truncated-cfdata-payload","errorCode":null,"errorMessage":"Invalid CAB archive: truncated CFDATA payload","messagePattern":"Invalid CAB archive: truncated CFDATA payload","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/cab.ts","lineNumber":156,"sourceCode":"\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}\n\t\t\toutputSize += uncompressed;\n\t\t\tassertInMemorySize(outputSize, this.#limits);\n\t\t\tposition = payloadEnd;\n\t\t}\n\t\tif (outputSize < description.requiredSize) {\n\t\t\tthrow new ArchiveError(\"Invalid CAB archive: folder data is shorter than its file table declares\");\n\t\t}\n\n\t\tconst output = new Uint8Array(outputSize);\n\t\tconst lzx = description.method === 3 ? new LzxDecoder(description.parameter) : undefined;","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/cab.ts#L138-L174","documentation":"Thrown by CabFolder.#decode when a CFDATA block's declared compressed size extends past the end of the folder's byte range read from the archive. The library validates every data block boundary before decompressing, so a cabinet whose payload region is shorter than the block table claims is rejected instead of producing a partial or garbage extraction.","triggerScenarios":"readCab() indexed a cabinet whose CFFOLDER declared a block whose cbData (compressed size at offset+4) makes payloadEnd exceed the folder's data region; typically the file was truncated mid-download or the folder data ranges were corrupted.","commonSituations":"Interrupted downloads or uploads of .cab files; partial Git LFS/CDN fetches; archives corrupted in transit; hand-crafted or fuzzed cabinet files; extracting from a network stream that was cut short.","solutions":["Re-download or re-copy the .cab archive and verify its size/hash against the source","Verify the archive opens with an independent tool (e.g. `cabextract -t file.cab`) to confirm the file is corrupt, not the library","If the file comes from your own build pipeline, check the step that produces the cabinet for premature stream close","Wrap extraction in try-catch on ArchiveError and surface a 'corrupt archive, re-fetch' message to users"],"exampleFix":"// before\nconst data = await Bun.file(downloadPath).arrayBuffer();\nawait readCab(new Uint8Array(data), opts);\n// after\nconst data = await Bun.file(downloadPath).arrayBuffer();\nconst expected = await fetchExpectedSize();\nif (data.byteLength !== expected) throw new Error(`incomplete download: ${data.byteLength}/${expected}`);\nawait readCab(new Uint8Array(data), opts);","handlingStrategy":"validation","validationCode":"const stat = await Bun.file(path).stat();\nconst expectedSize = await getExpectedSize(path); // from manifest/CDN\nif (stat.size !== expectedSize) throw new Error(`truncated archive: ${stat.size} != ${expectedSize}`);","typeGuard":null,"tryCatchPattern":"try {\n  await readCab(source, opts);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes('truncated CFDATA payload'))\n    throw new Error('Archive is incomplete or corrupt — re-download it.');\n  throw err;\n}","preventionTips":["Verify file size/hash against the source before extraction","Use resumable, checksum-verified downloads for large .cab files","Test archives with `cabextract -t` before processing"],"tags":["archive","cab","corruption","truncated-data"],"backgroundTag":"archive-truncated","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}