{"record":{"id":"c42c5a0d7625675e","repo":"can1357/oh-my-pi","slug":"invalid-non-zero-compress-z-padding","errorCode":null,"errorMessage":"Invalid non-zero compress (.Z) padding","messagePattern":"Invalid non-zero compress \\(\\.Z\\) padding","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/codecs/lzw.ts","lineNumber":51,"sourceCode":"\n\talignCodeGroup(width: number): void {\n\t\tconst groupBits = width * 8;\n\t\tconst aligned = this.#groupStart + Math.ceil((this.#bitPosition - this.#groupStart) / groupBits) * groupBits;\n\t\tif (aligned > this.#bytes.byteLength * 8) {\n\t\t\tthrow new ArchiveError(\"Truncated compress (.Z) code group\");\n\t\t}\n\t\tthis.#bitPosition = aligned;\n\t\tthis.#groupStart = aligned;\n\t}\n\n\tassertFinalPadding(): void {\n\t\tthis.#assertZeroBits(this.#bytes.byteLength * 8);\n\t}\n\n\t#assertZeroBits(end: number): void {\n\t\tfor (let position = this.#bitPosition; position < end; position++) {\n\t\t\tif (((this.#bytes[position >>> 3]! >>> (position & 7)) & 1) !== 0) {\n\t\t\t\tthrow new ArchiveError(\"Invalid non-zero compress (.Z) padding\");\n\t\t\t}\n\t\t}\n\t}\n}\n\nclass BoundedOutput {\n\treadonly #limit: number;\n\t#bytes: Uint8Array;\n\t#length = 0;\n\n\tconstructor(limit: number, inputSize: number) {\n\t\tthis.#limit = limit;\n\t\tthis.#bytes = new Uint8Array(Math.min(limit, Math.max(64, Math.min(inputSize * 2, 64 * 1024))));\n\t}\n\n\tappendReversed(stack: Uint8Array, length: number): void {\n\t\tthis.#ensure(length);\n\t\tfor (let index = length - 1; index >= 0; index--) {","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/codecs/lzw.ts#L33-L69","documentation":"After the compress (.Z) end-of-stream code, the remaining bits up to the final byte boundary must be zero padding. #assertZeroBits scans each leftover bit and throws if any is non-zero. Non-zero padding means the stream did not terminate where it claims, so the input is malformed or there is extra data.","triggerScenarios":"Calling lzwDecompress (via assertFinalPadding) on input where bits following the EOF code are set — e.g. a second concatenated .Z member, appended garbage, or corruption in the final byte.","commonSituations":"Concatenated .Z archives decoded as a single stream; trailing junk after decompressed data; single-bit corruption in the last byte; hand-trimmed buffers that cut into the final code word.","solutions":["Split concatenated .Z members and decode each independently (the compress format supports concatenated members).","Strip any known trailing metadata/garbage before decompressing.","Verify file integrity with `zcat` / checksum comparison.","Ensure the buffer passed covers exactly the compressed stream, not extra trailing bytes."],"exampleFix":"// before: one call on concatenation\nlzwDecompress(concatBytes, max);\n// after: decode each .Z member\nfor (const member of splitZMembers(concatBytes)) lzwDecompress(member, max);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return lzwDecompress(bytes, maxOutput);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message.includes(\"padding\")) {\n    throw new Error(\"Non-zero bits after .Z EOF code — likely concatenated members or trailing garbage\", { cause: err });\n  }\n  throw err;\n}","preventionTips":["Split concatenated .Z members and decode each separately.","Strip known trailing metadata before decompressing.","Verify checksums so single-bit tail corruption is caught early.","Pass exactly the compressed stream bytes, no extra tail."],"tags":["archive","lzw","compress-z","malformed-input"],"backgroundTag":"trailing-garbage-after-stream","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}