{"record":{"id":"b0c865f30a113afd","repo":"nats-io/nats-server","slug":"compressed-buffer-is-too-short","errorCode":null,"errorMessage":"compressed buffer is too short","messagePattern":"compressed buffer is too short","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/filestore.go","lineNumber":14541,"sourceCode":"\t\treturn nil, fmt.Errorf(\"short write on body (%d != %d)\", n, bodyLen)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\treturn nil, fmt.Errorf(\"error closing compression writer: %w\", err)\n\t}\n\n\t// Now add the checksum back onto the end of the block.\n\tif n, err := output.Write(checksum); err != nil {\n\t\treturn nil, fmt.Errorf(\"error writing checksum: %w\", err)\n\t} else if n != checksumSize {\n\t\treturn nil, fmt.Errorf(\"short write on checksum (%d != %d)\", n, checksumSize)\n\t}\n\n\treturn output.Bytes(), nil\n}\n\nfunc (alg StoreCompression) Decompress(buf []byte) ([]byte, error) {\n\tif len(buf) < checksumSize {\n\t\treturn nil, fmt.Errorf(\"compressed buffer is too short\")\n\t}\n\tbodyLen := int64(len(buf) - checksumSize)\n\tinput := bytes.NewReader(buf[:bodyLen])\n\n\tvar reader io.ReadCloser\n\tswitch alg {\n\tcase NoCompression:\n\t\treturn buf, nil\n\tcase S2Compression:\n\t\treader = io.NopCloser(s2.NewReader(input))\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"compression algorithm not known\")\n\t}\n\n\t// Decompress the block content. The checksum isn't compressed so\n\t// we can preserve it from the end of the block as-is.\n\tchecksum := buf[bodyLen:]\n\toutput, err := io.ReadAll(reader)","sourceCodeStart":14523,"sourceCodeEnd":14559,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/filestore.go#L14523-L14559","documentation":"StoreCompression.Decompress requires the trailing checksum to be present: a buffer shorter than checksumSize cannot even contain the checksum, so it cannot be a valid compressed block. Returned before any decompression is attempted.","triggerScenarios":"Calling Decompress (or any filestore read path that decompresses a stored block) with a byte slice whose len < checksumSize — e.g. an empty slice, a truncated read, or a corrupted/zero-length stored block.","commonSituations":"Truncated filestore block files after a crash, reading past a partially written block, a bug slicing off the checksum before calling Decompress, or feeding Decompress arbitrary bytes it was never meant to receive.","solutions":["Verify the source of the buffer: check the block file was fully written (length + checksum)","Don't strip the checksum from the buffer before calling Decompress","Check the filestore for truncated blocks and re-fetch/re-sync the affected block","Log the buffer length to confirm it is below checksumSize and trace where truncation happens"],"exampleFix":"// before\nout, err := alg.Decompress(buf[:bodyOnly])\n// after\nif len(buf) < checksumSize { return ErrCorruptBlock }\nout, err := alg.Decompress(buf)","handlingStrategy":"validation","validationCode":"if len(buf) < checksumSize { return fmt.Errorf(\"cannot decompress: buffer %d < checksum size\", len(buf)) }","typeGuard":"func isDecompressable(buf []byte) bool { return len(buf) >= checksumSize }","tryCatchPattern":"out, err := alg.Decompress(buf)\nif err != nil {\n    if strings.Contains(err.Error(), \"compressed buffer is too short\") {\n        // block source truncated; refetch or resync the block\n    }\n    return err\n}","preventionTips":["Never strip the trailing checksum before calling Decompress","Validate block file sizes when reading from storage (len == declared bodyLen + checksumSize)","Detect truncation at the file-read layer and resync from a peer/backup"],"tags":["compression","checksum","data-corruption"],"backgroundTag":"buffer-too-short","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}