From 4eb100a0ad78f8101ad68fb0d282411f158d1a3d Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Thu, 5 Nov 2026 10:10:37 +0100 Subject: [PATCH 1/3] Fix security issue path traversal --- libcloud/storage/base.py | 2 +- libcloud/test/storage/test_base.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/libcloud/storage/base.py b/libcloud/storage/base.py index 42b80f6fcf..0c872bb830 100644 --- a/libcloud/storage/base.py +++ b/libcloud/storage/base.py @@ -763,7 +763,7 @@ def _save_object( raise LibcloudError(value="Path %s does not exist" % (destination_path), driver=self) if not base_name: - file_path = pjoin(destination_path, obj.name) + file_path = pjoin(destination_path, os.path.basename(obj.name)) else: file_path = destination_path diff --git a/libcloud/test/storage/test_base.py b/libcloud/test/storage/test_base.py index ced1f9de74..f48e103745 100644 --- a/libcloud/test/storage/test_base.py +++ b/libcloud/test/storage/test_base.py @@ -16,6 +16,8 @@ import sys import errno import hashlib +import os +import tempfile from io import BytesIO from unittest import mock from unittest.mock import Mock @@ -53,6 +55,22 @@ def setUp(self): self.driver1.strict_mode = False self.driver1.strict_mode = False + def test_save_object_confines_provider_name_to_destination_directory(self): + with tempfile.TemporaryDirectory() as temp_dir: + destination_path = os.path.join(temp_dir, "downloads") + os.sep + os.mkdir(destination_path) + outside_path = os.path.join(temp_dir, "evil") + response = Mock() + response._response.iter_content.return_value = [b"PWNED"] + obj = Mock(size=5) + obj.name = "../../evil" + + self.driver1._save_object(response, obj, destination_path) + + self.assertFalse(os.path.exists(outside_path)) + with open(os.path.join(destination_path, "evil"), "rb") as file_handle: + self.assertEqual(file_handle.read(), b"PWNED") + def test__upload_object_iterator_must_have_next_method(self): valid_iterators = [BytesIO(b("134")), StringIO("bar")] invalid_iterators = ["foobar", "", False, True, 1, object()] From 03af3a40034d99b164b66e1dea3fd47709f3c22c Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Thu, 5 Nov 2026 10:15:26 +0100 Subject: [PATCH 2/3] Add changelog --- CHANGES.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 683f8b3538..0e6edebbda 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -76,6 +76,11 @@ Compute Storage ~~~~~~~ +- [Storage] Prevent path traversal when downloading objects to a directory by + using only the object name's basename as the local filename. + (#2206) + [Miguel Caballer - @micafer] + - [Azure Blobs] Fix SAS signatures for percent-encoded object paths by using the decoded path in the canonicalized resource while preserving the encoded path in the returned URL. From 7f8bd1c00ae8cec7089f1bac8381e1f4e20ba07b Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Fri, 6 Nov 2026 08:13:06 +0100 Subject: [PATCH 3/3] Fix isort --- libcloud/test/storage/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcloud/test/storage/test_base.py b/libcloud/test/storage/test_base.py index f48e103745..17ff2c2a53 100644 --- a/libcloud/test/storage/test_base.py +++ b/libcloud/test/storage/test_base.py @@ -13,10 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os import sys import errno import hashlib -import os import tempfile from io import BytesIO from unittest import mock