High severity7.5OSV Advisory· Published Nov 10, 2025· Updated Apr 15, 2026
CVE-2025-64509
CVE-2025-64509
Description
Bugsink is a self-hosted error tracking tool. In versions prior to 2.0.6, a specially crafted Brotli-compressed envelope can cause Bugsink to spend excessive CPU time in decompression, leading to denial of service. This can be done if the DSN is known, which it is in many common setups (JavaScript, Mobile Apps). The issue is patched in Bugsink 2.0.6. The vulnerability is similar to, but distinct from, another brotli-related problem in Bugsink, GHSA-fc2v-vcwj-269v/CVE-2025-64508.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
bugsinkPyPI | < 2.0.6 | 2.0.6 |
Affected products
1Patches
11201f754e392brotli decompress: avoid non-termination
1 file changed · +25 −3
bugsink/streams.py+25 −3 modified@@ -23,6 +23,15 @@ class MaxLengthExceeded(ValueError): pass +class BrotliError(ValueError): + """similar to brotli.error, but separate from it, to clarify non-library failure""" + + +def brotli_assert(condition, message): + if not condition: + raise BrotliError(message) + + def zlib_generator(input_stream, wbits, chunk_size=DEFAULT_CHUNK_SIZE): z = zlib.decompressobj(wbits=wbits) @@ -41,19 +50,32 @@ def brotli_generator(input_stream, chunk_size=DEFAULT_CHUNK_SIZE): # I've also seen that the actual output data may be quite a bit larger than the output_buffer_limit; a detail that # I do not fully understand (but I understand that at least it's not _unboundedly_ larger). + # Peppered with assertions b/c the brotli package is ill-documented. + decompressor = brotli.Decompressor() input_is_finished = False while not (decompressor.is_finished() and input_is_finished): if decompressor.can_accept_more_data(): compressed_chunk = input_stream.read(chunk_size) - if not compressed_chunk: + if compressed_chunk: + data = decompressor.process(compressed_chunk, output_buffer_limit=chunk_size) + # assertion on data here? I'm not sure yet whether we actually hard-expect it. OK, you were ready to + # accept input, and you got it. Does it mean you have output per se? In the limit (a single compressed + # byte) one would say that the answer is "no". + + else: input_is_finished = True data = decompressor.process(b"", output_buffer_limit=chunk_size) # b"": no input available, "drain" - else: - data = decompressor.process(compressed_chunk, output_buffer_limit=chunk_size) + brotli_assert( + len(data) or decompressor.is_finished(), + "Draining done -> decompressor finished; if not, something's off") + else: data = decompressor.process(b"", output_buffer_limit=chunk_size) # b"" compressor cannot accept more input + brotli_assert( + len(data) > 0, + "A brotli processor that cannot accept input _must_ be able to produce output or it would be stuck.") if data: yield data
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.