can1357/oh-my-pi · error · ArchiveError

Invalid ZIP archive: ${what} has an invalid range

Error message

Invalid ZIP archive: ${what} has an invalid range

What it means

checkedEnd in zip.ts validates that a start/size pair describes a sane in-range span before any ZIP structure (local header, data, central directory entry) is used. This branch fires when either value is negative, non-finite, or not a safe integer — i.e. the ZIP metadata is nonsensical, not merely too large.

Source

Thrown at packages/utils/src/ar/zip.ts:103

interface ParsedExtra {
	zip64?: Uint8Array;
	unicodePath?: string;
	mtimeMs?: number;
}

interface ParsedZipEntry {
	entry: ArchiveIndexEntry;
	isSymlink: boolean;
}

function archiveError(error: unknown, context: string): ArchiveError {
	if (error instanceof ArchiveError) return error;
	return new ArchiveError(`${context}: ${error instanceof Error ? error.message : String(error)}`);
}

function checkedEnd(start: number, size: number, archiveSize: number, what: string): number {
	if (!Number.isSafeInteger(start) || !Number.isSafeInteger(size) || start < 0 || size < 0) {
		throw new ArchiveError(`Invalid ZIP archive: ${what} has an invalid range`);
	}
	const end = start + size;
	if (!Number.isSafeInteger(end) || end > archiveSize) {
		throw new ArchiveError(`Invalid ZIP archive: ${what} exceeds archive size`);
	}
	return end;
}

function findEocd(tail: Uint8Array): number {
	for (let offset = tail.byteLength - EOCD_LENGTH; offset >= 0; offset--) {
		if (readUInt32LE(tail, offset) !== EOCD_SIGNATURE) continue;
		if (offset + EOCD_LENGTH + readUInt16LE(tail, offset + 20) === tail.byteLength) return offset;
	}
	throw new ArchiveError("Invalid ZIP archive: missing end of central directory");
}

async function readZip64Info(
	source: ByteSource,

View on GitHub (pinned to 9690622007)

Solutions

  1. Test the archive with `unzip -t` to confirm corruption, then re-obtain it
  2. If parsing untrusted input, treat this as an expected failure and reject the file up front
  3. Check any code that pre-processes or patches ZIP fields for integer overflow or wrong endianness

Example fix

// before
await readZip(untrustedBytes);
// after
if (!looksLikeZip(untrustedBytes)) throw new Error("not a zip");
try {
  await readZip(untrustedBytes);
} catch (e) {
  if (e instanceof ArchiveError) return rejectUpload("corrupt zip: " + e.message);
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!Number.isSafeInteger(bytes.length) || bytes.length < 22) throw new Error("not a plausible zip");
// check EOCD signature exists in tail
const dec = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
let eocd = -1;
for (let i = bytes.length - 22; i >= Math.max(0, bytes.length - 22 - 65535); i--) {
  if (dec.getUint32(i, true) === 0x06054b50) { eocd = i; break; }
}
if (eocd < 0) throw new Error("zip EOCD missing — refusing to parse");

Try / catch

try {
  const zip = await readZip(bytes);
} catch (err) {
  if (err instanceof ArchiveError && err.message.includes("invalid range")) {
    return quarantine(err, "zip metadata nonsensical — likely corrupt or hostile input");
  }
  throw err;
}

Prevention

When it happens

Trigger: readZip or its helpers computing a header/data/central-directory span where a parsed 32/64-bit field decodes to a negative or unsafe-integer value (e.g. a corrupted size or offset field, or a bad ZIP64 extra field).

Common situations: Corrupted ZIPs from interrupted downloads; ZIP files with flipped bits in central-directory fields; hand-modified archives; fuzzed or malicious inputs.

Related errors


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