can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: declared cabinet size is out of bounds

Error message

Invalid CAB archive: declared cabinet size is out of bounds

What it means

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.

Source

Thrown at packages/utils/src/ar/cab.ts:252

		const folder = await this.#folder.readAll();
		const end = this.#offset + size;
		if (!Number.isSafeInteger(end) || this.#offset < 0 || end > folder.byteLength) {
			throw new ArchiveError(`Invalid CAB archive: member '${memberPath}' is outside its folder data`);
		}
		return folder.slice(this.#offset, end);
	}
}

async function readCabArchive(source: ByteSource, options: Parameters<FormatReader>[1]): Promise<ArchiveIndexEntry[]> {
	if (source.size < FIXED_HEADER_SIZE) throw new ArchiveError("Invalid CAB archive: truncated CFHEADER");
	const fixed = await readExact(source, 0, FIXED_HEADER_SIZE);
	if (!hasSignature(fixed)) throw new ArchiveError(`Invalid CAB archive: expected ${CAB_SIGNATURE} signature`);
	if (readUInt32LE(fixed, 4) !== 0 || readUInt32LE(fixed, 12) !== 0 || readUInt32LE(fixed, 20) !== 0) {
		throw new ArchiveError("Invalid CAB archive: reserved CFHEADER fields must be zero");
	}
	const cabinetSize = readUInt32LE(fixed, 8);
	if (cabinetSize < FIXED_HEADER_SIZE || cabinetSize > source.size) {
		throw new ArchiveError("Invalid CAB archive: declared cabinet size is out of bounds");
	}
	const fileTableOffset = readUInt32LE(fixed, 16);
	if (fileTableOffset < FIXED_HEADER_SIZE || fileTableOffset > cabinetSize) {
		throw new ArchiveError("Invalid CAB archive: CFFILE table offset is out of bounds");
	}
	if (fixed[24] !== 3 || fixed[25] !== 1) {
		throw new ArchiveError(`Unsupported CAB format version ${fixed[25]}.${fixed[24]} (expected 1.3)`);
	}
	const folderCount = readUInt16LE(fixed, 26);
	const fileCount = readUInt16LE(fixed, 28);
	const flags = readUInt16LE(fixed, 30);
	if (flags & 0x0003) throw new ArchiveError("Unsupported multi-volume CAB archive (previous/next cabinet link)");
	assertEntryCount(folderCount + fileCount, options.limits);
	if (folderCount === 0 && fileCount !== 0)
		throw new ArchiveError("Invalid CAB archive: files exist without a folder");

	let headerReserveSize = 0;
	let folderReserveSize = 0;

View on GitHub (pinned to 9690622007)

Solutions

  1. Check the file is fully downloaded/transferred: compare its byte length against the size recorded in the producing tool's manifest.
  2. Hex-dump bytes 8-11 and compare the little-endian value to the actual file size; if it exceeds it, the file is truncated.
  3. Re-export or re-download the archive; the library will not guess at a corrected size.
  4. If the cabinet is genuinely multi-part, ensure you have the complete single volume rather than a partial member.

Example fix

// before
const entries = await readCab(partialBuffer); // truncated download
// after
const stat = await fs.stat("data1.cab");
if (stat.size !== expectedSize) throw new Error("incomplete download");
const entries = await readCab(await Bun.file("data1.cab").arrayBuffer());
Defensive patterns

Strategy: validation

Validate before calling

const stat = await Bun.file(path).stat?.() ?? (await import("node:fs/promises")).stat(path);
const buf = new Uint8Array(await Bun.file(path).arrayBuffer());
const declared = buf[8] | (buf[9]! << 8) | (buf[10]! << 16) | (buf[11]! << 24) >>> 0;
if (declared > buf.byteLength) throw new Error(`Truncated CAB: header declares ${declared} bytes, file has ${buf.byteLength}`);

Try / catch

try {
	const entries = await readCab(source);
} catch (err) {
	if (err instanceof ArchiveError && err.message.includes("declared cabinet size is out of bounds")) {
		throw new Error("CAB file is truncated or incomplete; re-download it", { cause: err });
	}
	throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/4fdce225405301e4. Report an issue: GitHub.