can1357/oh-my-pi · error · ArchiveError

Corrupt compress (.Z) dictionary chain

Error message

Corrupt compress (.Z) dictionary chain

What it means

When expanding an LZW code into bytes, the decoder walks the parent chain of dictionary entries. This error means the chain walks into an undefined entry (>= dictionaryHead) or becomes pathologically long (stack overflow guard) — the dictionary tables are inconsistent, which can only happen with a corrupt or hostile bitstream (or an earlier decode desync).

Source

Thrown at packages/utils/src/ar/codecs/lzw.ts:148

			reader.assertFinalPadding();
			return output.finish();
		}
		if (code >= dictionaryHead) {
			throw new ArchiveError(`Corrupt compress (.Z) dictionary code ${code}`);
		}
		if (blockMode && code === CLEAR_CODE) {
			reader.alignCodeGroup(width);
			width = MIN_BITS;
			dictionaryHead = FIRST_BLOCK_CODE;
			needsPreviousSuffix = false;
			continue;
		}

		let current = code;
		let stackLength = 0;
		while (current >= 256) {
			if (current >= dictionaryHead || stackLength >= stack.length - 1) {
				throw new ArchiveError("Corrupt compress (.Z) dictionary chain");
			}
			stack[stackLength++] = suffixes[current]!;
			current = parents[current]!;
		}
		stack[stackLength++] = current;

		if (needsPreviousSuffix) {
			suffixes[dictionaryHead - 1] = current;
			if (code === dictionaryHead - 1) {
				stack[0] = current;
			}
		}
		output.appendReversed(stack, stackLength);

		if (dictionaryHead < dictionaryLimit) {
			needsPreviousSuffix = true;
			parents[dictionaryHead++] = code;
			if (dictionaryHead > 2 ** width && width < maxBits) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Treat the archive as corrupt — restore from a known-good copy and verify checksums.
  2. Ensure no bytes were inserted/removed before decoding (header stripped exactly 3 bytes, single stream).
  3. Keep the provided maxOutput limit sane: raising it wildly does not fix this; the issue is input integrity, not limits.
  4. If the data is untrusted, accept that this guard is a safety feature (prevents infinite loops) and reject the archive.

Example fix

// before
try { await lzwDecompress(untrustedBytes, limit); } catch { /* ignore */ }
// after
if (!isCompressZ(untrustedBytes)) throw new Error("not .Z");
try {
  const out = await lzwDecompress(untrustedBytes, limit);
} catch (e) {
  throw new Error(`archive corrupt, refusing to decode: ${e.message}`);
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  return await lzwDecompress(untrustedBytes, maxOutput);
} catch (e) {
  if (e instanceof ArchiveError && e.message.includes("Corrupt compress (.Z) dictionary chain")) {
    throw new Error("Refusing to decode: .Z dictionary chain is corrupt (possible malicious input)");
  }
  throw e;
}

Prevention

When it happens

Trigger: Decoding a .Z stream where a dictionary entry's parent points beyond dictionaryHead or a chain exceeds 2^maxBits - 1 links: bit-flipped payload, decoding from the wrong stream offset (desync), or adversarial input crafted to loop the chain.

Common situations: Corrupted archives from damaged storage/transfers; fuzz-tested inputs; decoding a stream that had earlier codes dropped or altered so subsequent entries were built from wrong data.

Related errors


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