Skip to content

_GzipDecoder.decompress() incompatible with urllib3 2.6.0+ (missing max_length parameter) #494

@flo-ri-an

Description

@flo-ri-an

Summary

GCS blob downloads fail with TypeError: _GzipDecoder.decompress() got an unexpected keyword argument 'max_length' when using urllib3>=2.6.0. The _GzipDecoder class in google/resumable_media/requests/download.py subclasses urllib3.response.GzipDecoder but its decompress() method doesn't accept the max_length keyword argument introduced in urllib3 2.6.0.

Environment details

  • OS type and version: Linux (Ubuntu, also reproducible on other platforms)
  • Python version: 3.12
  • pip version: 24.x
  • google-resumable-media version: 2.7.2 (and likely all current versions)
  • urllib3 version: 2.6.0 or 2.6.1
  • google-cloud-storage version: 2.19.0

Root Cause

urllib3 2.6.0 (released December 5, 2025) introduced a breaking API change as part of the security fix for CVE-2025-66471 (GHSA-2xpw-w6gg-jr37, severity 8.9 High). This vulnerability allowed decompression bombs to cause DoS through excessive CPU/memory consumption.

The fix added a max_length parameter to decoder decompress() methods. In urllib3/response.py, the _decode() method now calls:

self._decoder.decompress(data, max_length=max_length)

However, google-resumable-media's _GzipDecoder subclass overrides decompress() without accepting this parameter:

# google/resumable_media/requests/download.py (current code)
class _GzipDecoder(urllib3.response.GzipDecoder):
    def __init__(self, checksum):
        super().__init__()
        self._checksum = checksum

    def decompress(self, data):  # ← Missing **kwargs or max_length parameter!
        self._checksum.update(data)
        return super().decompress(data)

The same issue affects _BrotliDecoder in the same file.

Steps to reproduce

  1. Install urllib3>=2.6.0 (or let it upgrade naturally)
  2. Attempt to download a gzip-encoded blob from Google Cloud Storage
pip install urllib3==2.6.1 google-cloud-storage

Code example

from google.cloud import storage

client = storage.Client()
bucket = client.bucket("your-bucket")
blob = bucket.blob("your-gzip-encoded-file")

# This fails with urllib3 2.6.0+
content = blob.download_as_bytes()

Stack trace

"Traceback (most recent call last):
                  ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/contextlib.py", line 81, in inner
    return func(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.12/site-packages/google/cloud/storage/blob.py", line 1473, in download_as_bytes
    self._prep_and_do_download(
  File "/app/.venv/lib/python3.12/site-packages/google/cloud/storage/blob.py", line 4423, in _prep_and_do_download
    self._do_download(
  File "/app/.venv/lib/python3.12/site-packages/google/cloud/storage/blob.py", line 1045, in _do_download
    response = download.consume(transport, timeout=timeout)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.12/site-packages/google/resumable_media/requests/download.py", line 263, in consume
    return _request_helpers.wait_and_retry(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.12/site-packages/google/resumable_media/requests/_request_helpers.py", line 155, in wait_and_retry
    response = func()
               ^^^^^^
  File "/app/.venv/lib/python3.12/site-packages/google/resumable_media/requests/download.py", line 259, in retriable_request
    self._write_to_stream(result)
  File "/app/.venv/lib/python3.12/site-packages/google/resumable_media/requests/download.py", line 131, in _write_to_stream
    for chunk in body_iter:
                 ^^^^^^^^^
  File "/app/.venv/lib/python3.12/site-packages/requests/models.py", line 820, in generate
    yield from self.raw.stream(chunk_size, decode_content=True)
  File "/app/.venv/lib/python3.12/site-packages/urllib3/response.py", line 1246, in stream
    yield from self.read_chunked(amt, decode_content=decode_content)
  File "/app/.venv/lib/python3.12/site-packages/urllib3/response.py", line 1414, in read_chunked
    decoded = self._decode(
              ^^^^^^^^^^^^^
  File "/app/.venv/lib/python3.12/site-packages/urllib3/response.py", line 642, in _decode
    data = self._decoder.decompress(data, max_length=max_length)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: _GzipDecoder.decompress() got an unexpected keyword argument 'max_length'

Suggested Fix

Update _GzipDecoder.decompress() (and _BrotliDecoder.decompress()) to accept and forward the max_length parameter:

class _GzipDecoder(urllib3.response.GzipDecoder):
    def __init__(self, checksum):
        super().__init__()
        self._checksum = checksum

    def decompress(self, data, **kwargs):  # Accept additional kwargs
        self._checksum.update(data)
        return super().decompress(data, **kwargs)  # Forward them to parent

Alternatively, explicitly handle max_length:

def decompress(self, data, max_length=0):
    self._checksum.update(data)
    return super().decompress(data, max_length=max_length)

Workaround

Pin urllib3 to versions before 2.6.0:

urllib3>=2.0,<2.6

Additional Context

Metadata

Metadata

Assignees

Labels

api: storageIssues related to the googleapis/google-resumable-media-python API.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions