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
- Treat the archive as corrupt — restore from a known-good copy and verify checksums.
- Ensure no bytes were inserted/removed before decoding (header stripped exactly 3 bytes, single stream).
- Keep the provided maxOutput limit sane: raising it wildly does not fix this; the issue is input integrity, not limits.
- 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
- Always keep a sane maxOutput limit — never pass Infinity — when decoding untrusted archives.
- Reject archives that fail checksums instead of attempting best-effort decode.
- Remember this guard also protects against infinite/deep chains; do not attempt to bypass it.
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
- Corrupt compress (.Z) dictionary code ${code}
- Archive path cannot contain '..'
- Unsafe embedded addon archive entry: ${filename}
- Archive entry escapes extraction directory: ${archivePath}
- Bzip2 block exhausted its Huffman selectors
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/44b5f98c405a8f98.
Report an issue: GitHub.