can1357/oh-my-pi · error · ArchiveError

Encrypted ARJ members are unsupported

Error message

Encrypted ARJ members are unsupported

What it means

Bit 0 (0x01) of an ARJ local file header's flags byte marks that individual member as encrypted with a password. Since the library implements no ARJ decryption, it refuses to index archives containing encrypted members. This is the per-file counterpart to the main-header encryption check.

Source

Thrown at packages/utils/src/ar/arj.ts:267

	if ((mainFlags & 0x04) !== 0) throw new ArchiveError("Multi-volume ARJ archives are unsupported");

	const entries: ArchiveIndexEntry[] = [];
	let offset = main.nextOffset;
	let metadataSize = main.metadataSize;
	let parsedCount = 0;
	for (;;) {
		const block = parseArjBlock(bytes, offset, options);
		metadataSize += block.metadataSize;
		assertIndexSize(metadataSize, options.limits, "index");
		if (block.isEnd) break;
		assertEntryCount(++parsedCount, options.limits);
		const firstHeaderSize = bytes[block.bodyStart]!;
		if (firstHeaderSize < 30 || firstHeaderSize > block.bodySize) throw new ArchiveError("Invalid ARJ local header");
		const hostOs = bytes[block.bodyStart + 3]!;
		const flags = bytes[block.bodyStart + 4]!;
		const method = bytes[block.bodyStart + 5]!;
		const fileType = bytes[block.bodyStart + 6]!;
		if ((flags & 0x01) !== 0) throw new ArchiveError("Encrypted ARJ members are unsupported");
		if ((flags & 0x0c) !== 0) throw new ArchiveError("Multi-volume ARJ members are unsupported");
		const packedSize = u32(bytes, block.bodyStart + 12);
		const size = u32(bytes, block.bodyStart + 16);
		const fileCrc = u32(bytes, block.bodyStart + 20);
		const accessMode = u16(bytes, block.bodyStart + 26);
		const filename = readCString(
			bytes,
			block.bodyStart + firstHeaderSize,
			block.bodyStart + block.bodySize,
			"filename",
		);
		readCString(bytes, filename.next, block.bodyStart + block.bodySize, "comment");
		assertArchivePathBytes(
			filename.next - (block.bodyStart + firstHeaderSize) - 1,
			"member path",
			options.limits.maxPathBytes,
		);
		const rawPath = normalizeHostPath(filename.value, hostOs);

View on GitHub (pinned to 9690622007)

Solutions

  1. Decrypt the archive externally with the original password (arj x -g<password>), then index the plaintext result.
  2. Ask the archive producer to re-create it without per-member encryption if the content is not actually secret.
  3. If partial extraction is acceptable, pre-scan headers yourself and remove/replace the encrypted members before calling readArj.
  4. Route encrypted-member archives to a tool that supports ARJ crypto and handle its output instead.

Example fix

// before
const entries = await readArj(bytes, options); // throws on any encrypted member
// after
const plain = await decryptArjMembers(bytes, password); // external tool
const entries = await readArj(plain, options);
Defensive patterns

Strategy: validation

Validate before calling

// Pre-scan local headers the same way readArj does; stop before parsing member data.
function hasEncryptedArjMembers(bytes: Uint8Array): boolean {
	// walk blocks; for each non-end block check (bytes[block.bodyStart + 4] & 0x01) !== 0
	return scanArjLocalFlags(bytes).some((flags) => (flags & 0x01) !== 0);
}
if (hasEncryptedArjMembers(bytes)) throw new Error("ARJ contains encrypted members; decrypt externally first");

Type guard

function isUnencryptedArjMemberFlags(flags: number): boolean {
	return (flags & 0x01) === 0;
}

Try / catch

try {
	entries = await readArj(source, options);
} catch (e) {
	if (e instanceof ArchiveError && e.message === "Encrypted ARJ members are unsupported") {
		return { status: "encrypted-member", hint: "decrypt with the original password via ARJ tooling" };
	}
	throw e;
}

Prevention

When it happens

Trigger: readArj parses a local header whose flags byte (bytes[bodyStart+4]) has 0x01 set — the member was stored with ARJ's per-file password ('-g' when adding files).

Common situations: Archives where only some files (e.g. secrets, license keys) were password-protected while the rest are plain; mixed archives assembled from multiple sources.

Related errors


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