{"record":{"id":"607a0042abdfbd74","repo":"can1357/oh-my-pi","slug":"invalid-cab-archive-invalid-lzx-huffman-code-leng","errorCode":null,"errorMessage":"Invalid CAB archive: invalid LZX Huffman code length","messagePattern":"Invalid CAB archive: invalid LZX Huffman code length","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/codecs/lzx.ts","lineNumber":72,"sourceCode":"\t\tconst b0 = this.readByte();\n\t\tconst b1 = this.readByte();\n\t\tconst b2 = this.readByte();\n\t\tconst b3 = this.readByte();\n\t\treturn (b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)) >>> 0;\n\t}\n}\n\nclass LzxHuffmanTable {\n\treadonly #counts = new Uint32Array(17);\n\treadonly #firstCodes = new Uint32Array(17);\n\treadonly #firstSymbols = new Uint32Array(17);\n\treadonly #symbols: Uint16Array;\n\treadonly empty: boolean;\n\n\tconstructor(lengths: Uint8Array, allowEmpty = false) {\n\t\tlet symbolCount = 0;\n\t\tfor (const length of lengths) {\n\t\t\tif (length > 16) throw new ArchiveError(\"Invalid CAB archive: invalid LZX Huffman code length\");\n\t\t\tif (length !== 0) {\n\t\t\t\tthis.#counts[length]++;\n\t\t\t\tsymbolCount++;\n\t\t\t}\n\t\t}\n\t\tthis.empty = symbolCount === 0;\n\t\tif (this.empty && !allowEmpty) {\n\t\t\tthrow new ArchiveError(\"Invalid CAB archive: empty LZX Huffman tree\");\n\t\t}\n\n\t\tlet code = 0;\n\t\tlet symbolOffset = 0;\n\t\tfor (let length = 1; length <= 16; length++) {\n\t\t\tcode = (code + this.#counts[length - 1]!) * 2;\n\t\t\tif (code + this.#counts[length]! > 2 ** length) {\n\t\t\t\tthrow new ArchiveError(\"Invalid CAB archive: oversubscribed LZX Huffman tree\");\n\t\t\t}\n\t\t\tthis.#firstCodes[length] = code;","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/codecs/lzx.ts#L54-L90","documentation":"LZX Huffman code lengths are at most 16 bits; the LzxHuffmanTable constructor validates every length read from the block header's code-length trees. A value above 16 can only come from a corrupt or hostile bitstream (valid encodings can't produce it via readCodeLengths deltas... but a direct call with a bad lengths array can), so the decoder refuses to build the table.","triggerScenarios":"Constructing LzxHuffmanTable directly (or via a corrupted readCodeLengths path) with a Uint8Array containing an entry > 16: feeding pre-tree 4-bit values into the wrong array, a caller-supplied lengths buffer that was never mod-17 reduced, or bit desync corrupting stored lengths.","commonSituations":"Custom CAB/LZX tooling that builds Huffman tables from unvalidated decoded lengths; fuzzed archives; reusing the decoder after a previous desync so subsequent block headers decode to garbage lengths.","solutions":["If calling LzxHuffmanTable yourself, clamp/validate lengths to 0..16 before construction.","If decoding CABs normally, treat the archive as corrupt: validate with cabextract/7z and re-obtain the file.","Check for stream desync — once a prior frame/block decode fails mid-way, abandon that decoder; create a fresh LzxDecoder per folder.","Verify readCodeLengths inputs (first/last ranges) match the LZX spec (main tree split at 256, length tree of 249)."],"exampleFix":"// before\nconst table = new LzxHuffmanTable(rawLengths); // rawLengths may contain 17+\n// after\nconst lengths = Uint8Array.from(rawLengths, (n) => n & 0x0f); // or validate explicitly\nif (lengths.some((n) => n > 16)) throw new Error(\"bad lengths\");\nconst table = new LzxHuffmanTable(lengths);","handlingStrategy":"validation","validationCode":"if (Array.prototype.some.call(lengths, (n) => n > 16)) {\n  throw new Error(\"Huffman code length exceeds 16 bits — stream is corrupt\");\n}","typeGuard":"function hasValidLzxLengths(lengths: Uint8Array): boolean {\n  for (let i = 0; i < lengths.byteLength; i++) {\n    if (lengths[i]! > 16) return false;\n  }\n  return true;\n}","tryCatchPattern":"try {\n  return decoder.decompressFrame(frameBytes, outputSize);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"invalid LZX Huffman code length\")) {\n    throw new Error(\"Block header decoded impossible code lengths — abandon this decoder (state is desynced) and reject the archive\");\n  }\n  throw e;\n}","preventionTips":["Never construct LzxHuffmanTable from unvalidated lengths in custom tooling; clamp to 0..16 with explicit validation.","Discard an LzxDecoder after any mid-frame failure — residual bit state poisons later tables.","Treat this error on otherwise-valid CABs as a sign of upstream corruption; verify with an independent extractor."],"tags":["archive","cab","lzx","huffman","corrupt-data"],"backgroundTag":"invalid-huffman-table","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}