can1357/oh-my-pi · error · ArchiveError
Invalid archive range
Error message
Invalid archive range
What it means
`assertValidRange` validates every `[start, end)` byte range before any read in the archive ByteSource layer. It rejects ranges where start or end is not a safe integer, start is negative, or end is less than start — i.e. ranges that could never name a valid byte span. This is a defensive pre-condition shared by memory and file sources so downstream code never sees nonsensical offsets.
Source
Thrown at packages/utils/src/ar/source.ts:17
import { LRUCache } from "../lru";
import { ArchiveError } from "./error";
/**
* A byte window into an archive — file-backed (lazy, ranged reads) or
* in-memory. Format readers index through this so ZIP/ASAR/RAR payloads are
* only read when a member is actually extracted.
*/
export interface ByteSource {
readonly size: number;
read(start: number, end: number): Promise<Uint8Array>;
}
/** Reject a nonsensical `[start, end)` range before any read. */
export function assertValidRange(start: number, end: number): void {
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start) {
throw new ArchiveError("Invalid archive range");
}
}
/** Read an exact in-memory range, throwing (not clamping) when it runs past the buffer. */
export function readMemoryRange(buffer: Uint8Array, start: number, end: number): Uint8Array {
assertValidRange(start, end);
if (end > buffer.byteLength) {
throw new ArchiveError("Invalid archive: truncated data");
}
return buffer.subarray(start, end);
}
/** Wrap borrowed bytes as a {@link ByteSource}. */
export function memoryByteSource(buffer: Uint8Array): ByteSource {
return {
size: buffer.byteLength,
async read(start, end) {
return readMemoryRange(buffer, start, end);View on GitHub (pinned to 9690622007)
Solutions
- Audit the caller's range math — confirm you pass (start, end) as an exclusive end, not (start, length)
- Validate offsets/sizes parsed from archive headers against source.size before use
- Clamp computed ranges to [0, size] with Math.max/Math.min and re-check before reading
Example fix
// before: passing length as end
await source.read(offset, entrySize);
// after: compute the exclusive end and validate
const end = offset + entrySize;
if (!Number.isSafeInteger(end) || end > source.size) throw new Error("bad entry range");
await source.read(offset, end); Defensive patterns
Strategy: validation
Validate before calling
function safeRange(start: number, end: number, size?: number): void {
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start) throw new Error(`invalid range [${start}, ${end})`);
if (size !== undefined && end > size) throw new Error(`range end ${end} exceeds size ${size}`);
} Type guard
function isValidRange(start: unknown, end: unknown): boolean {
return typeof start === "number" && typeof end === "number" && Number.isSafeInteger(start) && Number.isSafeInteger(end) && start >= 0 && end >= start;
} Try / catch
try {
const bytes = await src.read(start, end);
} catch (err) {
if (err instanceof ArchiveError && err.message === "Invalid archive range") {
logger.error("bad read range", { start, end });
}
throw err;
} Prevention
- Pass an exclusive end, not a length: read(start, start + length)
- Clamp computed ranges to [0, size] with Math.max/Math.min
- Sanitize offsets parsed from untrusted archive metadata before use
When it happens
Trigger: Calling `readMemoryRange`/`read` (or any archive API that reads ranges) with a negative offset, a fractional or NaN offset, an offset exceeding Number.MAX_SAFE_INTEGER, or an end before start — typically from bad arithmetic on header-derived sizes/offsets.
Common situations: Reading offsets from a corrupt archive header where a size field is huge (overflowing past MAX_SAFE_INTEGER or wrapping negative); buggy caller math passing (start, length) instead of (start, end).
Related errors
- tmpfiles option ttl must be between 60 seconds and 48 hours
- oldText must not be empty.
- At least one output ID is required
- Unrecognized source format. Did you mean './${source}' (loca
- app.surface must be a surface UUID (e.g. CMUX_SURFACE_ID), n
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/32e38378896d6116.
Report an issue: GitHub.