{"record":{"id":"4fdce225405301e4","repo":"can1357/oh-my-pi","slug":"invalid-cab-archive-declared-cabinet-size-is-out","errorCode":null,"errorMessage":"Invalid CAB archive: declared cabinet size is out of bounds","messagePattern":"Invalid CAB archive: declared cabinet size is out of bounds","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/cab.ts","lineNumber":252,"sourceCode":"\t\tconst folder = await this.#folder.readAll();\n\t\tconst end = this.#offset + size;\n\t\tif (!Number.isSafeInteger(end) || this.#offset < 0 || end > folder.byteLength) {\n\t\t\tthrow new ArchiveError(`Invalid CAB archive: member '${memberPath}' is outside its folder data`);\n\t\t}\n\t\treturn folder.slice(this.#offset, end);\n\t}\n}\n\nasync function readCabArchive(source: ByteSource, options: Parameters<FormatReader>[1]): Promise<ArchiveIndexEntry[]> {\n\tif (source.size < FIXED_HEADER_SIZE) throw new ArchiveError(\"Invalid CAB archive: truncated CFHEADER\");\n\tconst fixed = await readExact(source, 0, FIXED_HEADER_SIZE);\n\tif (!hasSignature(fixed)) throw new ArchiveError(`Invalid CAB archive: expected ${CAB_SIGNATURE} signature`);\n\tif (readUInt32LE(fixed, 4) !== 0 || readUInt32LE(fixed, 12) !== 0 || readUInt32LE(fixed, 20) !== 0) {\n\t\tthrow new ArchiveError(\"Invalid CAB archive: reserved CFHEADER fields must be zero\");\n\t}\n\tconst cabinetSize = readUInt32LE(fixed, 8);\n\tif (cabinetSize < FIXED_HEADER_SIZE || cabinetSize > source.size) {\n\t\tthrow new ArchiveError(\"Invalid CAB archive: declared cabinet size is out of bounds\");\n\t}\n\tconst fileTableOffset = readUInt32LE(fixed, 16);\n\tif (fileTableOffset < FIXED_HEADER_SIZE || fileTableOffset > cabinetSize) {\n\t\tthrow new ArchiveError(\"Invalid CAB archive: CFFILE table offset is out of bounds\");\n\t}\n\tif (fixed[24] !== 3 || fixed[25] !== 1) {\n\t\tthrow new ArchiveError(`Unsupported CAB format version ${fixed[25]}.${fixed[24]} (expected 1.3)`);\n\t}\n\tconst folderCount = readUInt16LE(fixed, 26);\n\tconst fileCount = readUInt16LE(fixed, 28);\n\tconst flags = readUInt16LE(fixed, 30);\n\tif (flags & 0x0003) throw new ArchiveError(\"Unsupported multi-volume CAB archive (previous/next cabinet link)\");\n\tassertEntryCount(folderCount + fileCount, options.limits);\n\tif (folderCount === 0 && fileCount !== 0)\n\t\tthrow new ArchiveError(\"Invalid CAB archive: files exist without a folder\");\n\n\tlet headerReserveSize = 0;\n\tlet folderReserveSize = 0;","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/cab.ts#L234-L270","documentation":"The CFHEADER declares the total cabinet size at offset 8. The library requires this value to be at least the fixed header size (36) and no larger than the actual source size; otherwise the declared layout cannot be trusted and further reads could go out of bounds.","triggerScenarios":"Calling readCab() on a signed CAB whose cbCabinet field (bytes 8-11) is zero, negative-wrapped, or larger than the file — e.g. the file was truncated after the header was written, or the size field was zeroed by corruption.","commonSituations":"Partially downloaded or truncated .cab files, streaming sources where only a prefix was buffered, archives copied with an incorrect byte count, or fuzzed inputs with an inflated size field.","solutions":["Check the file is fully downloaded/transferred: compare its byte length against the size recorded in the producing tool's manifest.","Hex-dump bytes 8-11 and compare the little-endian value to the actual file size; if it exceeds it, the file is truncated.","Re-export or re-download the archive; the library will not guess at a corrected size.","If the cabinet is genuinely multi-part, ensure you have the complete single volume rather than a partial member."],"exampleFix":"// before\nconst entries = await readCab(partialBuffer); // truncated download\n// after\nconst stat = await fs.stat(\"data1.cab\");\nif (stat.size !== expectedSize) throw new Error(\"incomplete download\");\nconst entries = await readCab(await Bun.file(\"data1.cab\").arrayBuffer());","handlingStrategy":"validation","validationCode":"const stat = await Bun.file(path).stat?.() ?? (await import(\"node:fs/promises\")).stat(path);\nconst buf = new Uint8Array(await Bun.file(path).arrayBuffer());\nconst declared = buf[8] | (buf[9]! << 8) | (buf[10]! << 16) | (buf[11]! << 24) >>> 0;\nif (declared > buf.byteLength) throw new Error(`Truncated CAB: header declares ${declared} bytes, file has ${buf.byteLength}`);","typeGuard":null,"tryCatchPattern":"try {\n\tconst entries = await readCab(source);\n} catch (err) {\n\tif (err instanceof ArchiveError && err.message.includes(\"declared cabinet size is out of bounds\")) {\n\t\tthrow new Error(\"CAB file is truncated or incomplete; re-download it\", { cause: err });\n\t}\n\tthrow err;\n}","preventionTips":["Compare file size against the expected size before parsing.","Never parse archives from partially streamed/prefixed buffers; pass the complete bytes.","Verify downloads with checksums when available."],"tags":["archive","cab","out-of-bounds","truncated-file"],"backgroundTag":"truncated-archive-file","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}