can1357/oh-my-pi · error · ArchiveError

Invalid tar numeric value

Error message

Invalid tar numeric value

What it means

Tar stores numeric fields (mode, uid, size, mtime) as zero-padded octal ASCII. writeOctal validates the value before encoding: it must be a safe non-negative integer. Negative numbers, NaN, non-integers, or values beyond Number.MAX_SAFE_INTEGER cannot be represented and would corrupt the header, so this ArchiveError is thrown.

Source

Thrown at packages/utils/src/ar/tar.ts:611

export function sniffTar(bytes: Uint8Array): boolean {
	if (bytes.byteLength < BLOCK_SIZE) return false;
	if (isZeroBlock(bytes, 0)) return true;
	try {
		if (readTarString(bytes, NAME_OFFSET, NAME_LENGTH).length === 0) return false;
		const size = readTarSize(bytes, SIZE_OFFSET);
		return Number.isSafeInteger(paddedSize(size)) && checksumMatches(bytes, 0);
	} catch {
		return false;
	}
}

function writeField(target: Uint8Array, offset: number, length: number, value: Uint8Array): void {
	if (value.byteLength > length) throw new ArchiveError("Tar header field is too long");
	target.set(value, offset);
}

function writeOctal(target: Uint8Array, offset: number, length: number, value: number): void {
	if (!Number.isSafeInteger(value) || value < 0) throw new ArchiveError("Invalid tar numeric value");
	const digits = value.toString(8);
	if (digits.length > length - 1) throw new ArchiveError("Tar numeric value does not fit its header field");
	for (let index = offset; index < offset + length - 1 - digits.length; index++) target[index] = 0x30;
	for (let index = 0; index < digits.length; index++)
		target[offset + length - 1 - digits.length + index] = digits.charCodeAt(index);
	target[offset + length - 1] = 0;
}

function splitUstarPath(pathBytes: Uint8Array): readonly [Uint8Array, Uint8Array] | undefined {
	if (pathBytes.byteLength <= NAME_LENGTH) return [pathBytes, new Uint8Array(0)];
	for (let index = pathBytes.byteLength - 1; index > 0; index--) {
		if (pathBytes[index] !== 0x2f) continue;
		const prefix = pathBytes.subarray(0, index);
		const name = pathBytes.subarray(index + 1);
		if (prefix.byteLength <= PREFIX_LENGTH && name.byteLength > 0 && name.byteLength <= NAME_LENGTH) {
			return [name, prefix];
		}
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Sanitize numeric fields before writing: `Number.isSafeInteger(v) && v >= 0` or clamp (e.g. uid=-1 → 0)
  2. Convert string sizes explicitly with Number() and validate before use
  3. For pre-epoch timestamps, clamp mtime to 0 or use PAX base-256/extended records if supported
  4. If you need values above 2^53, use the base-256 GNU encoding path instead of the octal writer

Example fix

// before: uid of -1 (unknown) corrupts the header
await writeTar({ path, uid: stat.uid });
// after: clamp unsafe values
await writeTar({ path, uid: Number.isSafeInteger(stat.uid) && stat.uid >= 0 ? stat.uid : 0 });
Defensive patterns

Strategy: validation

Validate before calling

function assertTarNumber(v: number, field: string): void {
  if (!Number.isSafeInteger(v) || v < 0) {
    throw new Error(`${field} must be a non-negative safe integer, got ${v}`);
  }
}

Type guard

function isValidTarNumber(v: unknown): v is number {
  return typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
}

Prevention

When it happens

Trigger: Writing a tar entry whose size came from a computation yielding NaN, a negative mtime (pre-1970 timestamps), a uid/gid read as -1 (unknown owner convention), or a size value that is not a Number safe integer (e.g. parsed from a string or BigInt).

Common situations: System calls returning -1 for unavailable uid/gid being passed through verbatim; dates before the Unix epoch (old photo metadata, zip imports); sizes from JSON string fields not converted with Number; float sizes from unit conversions.

Related errors


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