can1357/oh-my-pi · error · ArchiveError

Invalid CAB archive: expected ${CAB_SIGNATURE} signature

Error message

Invalid CAB archive: expected ${CAB_SIGNATURE} signature

What it means

readCabArchive reads the 36-byte fixed CFHEADER and checks its first four bytes for the 'MSCF' cabinet signature. If those bytes differ, the file is not a Microsoft CAB archive, so parsing is aborted with an ArchiveError before any further structure is trusted. This is the library's first-line sanity check against feeding it a completely wrong file format.

Source

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

	}

	async read(size: number, memberPath: string): Promise<Uint8Array> {
		if (size !== this.#declaredSize) {
			throw new ArchiveError(`Invalid CAB archive: size changed while extracting '${memberPath}'`);
		}
		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)");

View on GitHub (pinned to 9690622007)

Solutions

  1. Verify the file path points to an actual .cab file; check the first 4 bytes are 'MSCF' before calling readCab.
  2. If the cabinet is embedded inside an MSI/EXE, locate the inner cabinet offset and pass a source sliced at that offset rather than the wrapper itself.
  3. Re-download or re-export the file; a truncated-to-36+ bytes non-CAB blob usually means transfer corruption or the wrong artifact.
  4. Check whether the file is a related Microsoft format (e.g. MSI, MSP) and use the appropriate reader instead.

Example fix

// before
const entries = await readCab(Bun.file("setup.msi"));
// after
const buf = new Uint8Array(await Bun.file("data1.cab").arrayBuffer());
if (new TextDecoder().decode(buf.slice(0, 4)) !== "MSCF") throw new Error("not a CAB file");
const entries = await readCab(buf);
Defensive patterns

Strategy: validation

Validate before calling

const head = new Uint8Array(buf.slice(0, 4));
const isCab = head[0] === 0x4d && head[1] === 0x53 && head[2] === 0x43 && head[3] === 0x46;
if (!isCab) throw new Error(`Not a CAB archive (got magic ${Buffer.from(head).toString("hex")})`);

Type guard

function looksLikeCab(bytes: Uint8Array): boolean {
	return bytes.length >= 4 && bytes[0] === 0x4d && bytes[1] === 0x53 && bytes[2] === 0x43 && bytes[3] === 0x46;
}

Try / catch

try {
	const entries = await readCab(source);
} catch (err) {
	if (err instanceof ArchiveError && err.message.includes("expected MSCF signature")) {
		// wrong file type: surface a clear "not a CAB" message to the user
		return null;
	}
	throw err;
}

Prevention

When it happens

Trigger: Calling readCab() (which delegates to readCabArchive) on a file whose first 4 bytes are not 4D 53 43 46 ('MSCF') — e.g. a ZIP, MSI container, or any non-CAB binary — while the file is at least FIXED_HEADER_SIZE (36) bytes long so the truncation check passes first.

Common situations: Pointing the reader at the wrong file in an installer directory, extracting CAB payloads embedded in MSI/EXE wrappers without locating the inner cabinet offset, downloading a corrupted or HTML-error-page file, or confusing MSZIP-style archives with actual CAB format.

Related errors


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