VYPR
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.

PackageAffected versionsPatched versions
bugsinkPyPI
< 2.0.62.0.6

Affected products

1

Patches

1
1201f754e392

brotli decompress: avoid non-termination

https://github.com/bugsink/bugsinkKlaas van SchelvenNov 8, 2025via ghsa
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

4

News mentions

0

No linked articles in our index yet.