From c02815b0c112e9db2c79c4af1db8987b97db78c8 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Tue, 27 Oct 2026 19:49:51 +0000 Subject: [PATCH] Restrict remote archive extraction to safe members --- .../large_file/large_file/large_file_base.py | 4 ++- tests/large_file/test_archive_extraction.py | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/large_file/test_archive_extraction.py diff --git a/great_ai/large_file/large_file/large_file_base.py b/great_ai/large_file/large_file/large_file_base.py index 9a5aef9..32cdf11 100644 --- a/great_ai/large_file/large_file/large_file_base.py +++ b/great_ai/large_file/large_file/large_file_base.py @@ -197,7 +197,9 @@ class LargeFileBase(ABC): ) logger.info(f"Decompressing {self._local_name}") - shutil.unpack_archive(str(tmp_file_archive), tmp, COMPRESSION_ALGORITHM) + shutil.unpack_archive( + str(tmp_file_archive), tmp, COMPRESSION_ALGORITHM, filter="data" + ) shutil.move(str(local_root_path / self._local_name), str(destination)) else: logger.info(f"File {self._local_name} found in cache") diff --git a/tests/large_file/test_archive_extraction.py b/tests/large_file/test_archive_extraction.py new file mode 100644 index 0000000..28a90c8 --- /dev/null +++ b/tests/large_file/test_archive_extraction.py @@ -0,0 +1,31 @@ +import io +import tarfile +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest +from great_ai.large_file import LargeFileS3 + + +def test_remote_archive_cannot_escape_cache(tmp_path: Path) -> None: + backend = MagicMock() + backend.list_objects_v2.return_value = {"Contents": [{"Key": "model/0"}]} + sentinel = tmp_path / "escaped" + + def download( + self: LargeFileS3, remote_path: str, local_path: Path, hide_progress: bool + ) -> None: + with tarfile.open(local_path, "w:gz") as archive: + member = tarfile.TarInfo("../escaped") + member.size = 7 + archive.addfile(member, io.BytesIO(b"payload")) + + with ( + patch.object(LargeFileS3, "cache_path", tmp_path / "cache"), + patch.object(LargeFileS3, "_client", backend), + patch.object(LargeFileS3, "_download", download), + patch("tempfile.tempdir", str(tmp_path)), + ): + with pytest.raises(tarfile.OutsideDestinationError): + LargeFileS3("model").get() + assert not sentinel.exists()