From 36cb7e3c11aeda971a12d2b6f9b01060fb30fd13 Mon Sep 17 00:00:00 2001 From: Quratulain-bilal Date: Fri, 31 Jul 2026 20:02:25 +0500 Subject: [PATCH] fix: bound response reads in extension catalog and download (#3775) * fix: bound response reads in extension catalog and download Replace unbounded esponse.read() calls with ead_response_limited() from _download_security in extensions/__init__.py to prevent denial- of-service via oversized catalog or extension archive responses. Three call sites fixed: - _fetch_single_catalog JSON read (catalog metadata) - _fetch_catalog JSON read (legacy path) - download_extension ZIP read (binary download) All existing mock tests updated to use side_effect with BytesIO.read instead of eturn_value, ensuring compatibility with the chunked read loop in ead_response_limited. Two regression tests added: - test_oversized_catalog_response_rejected - test_oversized_extension_download_rejected * fix: remove .decode utf-8 to preserve bytes for json.loads json.loads accepts bytes directly. Removing .decode maintains compatibility with BOM-bearing or UTF-16/32 catalogs. --- tests/test_extensions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_extensions.py b/tests/test_extensions.py index bf7943bfa..63df3133f 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -5003,9 +5003,9 @@ class TestExtensionCatalog: catalog = self._make_catalog(temp_dir) mock_response = MagicMock() - mock_response.read.return_value = json.dumps( + mock_response.read.side_effect = io.BytesIO(json.dumps( {"schema_version": "1.0", "extensions": {}} - ).encode() + ).encode()).read mock_response.__enter__ = lambda s: s mock_response.__exit__ = MagicMock(return_value=False) mock_response.geturl.return_value = "http://evil.test/catalog.json" @@ -5051,9 +5051,9 @@ class TestExtensionCatalog: catalog = self._make_catalog(temp_dir) mock_response = MagicMock() - mock_response.read.return_value = json.dumps( + mock_response.read.side_effect = io.BytesIO(json.dumps( {"schema_version": "1.0", "extensions": {}} - ).encode() + ).encode()).read mock_response.__enter__ = lambda s: s mock_response.__exit__ = MagicMock(return_value=False) mock_response.geturl.return_value = "http://evil.test/catalog.json"