From 32e6d7235a07574d554adb79e3ca7e856bd22ffe Mon Sep 17 00:00:00 2001 From: Ersa-tech <186737553+Ersa-tech@users.noreply.github.com> Date: Sun, 31 May 2026 07:35:46 -0500 Subject: [PATCH] security: enforce multifile download path boundary --- .../app/services/download/download_default.py | 6 ++++-- .../services/download/test_download_queue.py | 20 ++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/invokeai/app/services/download/download_default.py b/invokeai/app/services/download/download_default.py index 13e86d18284..45e102045d4 100644 --- a/invokeai/app/services/download/download_default.py +++ b/invokeai/app/services/download/download_default.py @@ -189,10 +189,12 @@ def multifile_download( on_error=on_error, ) + dest_root = dest.resolve() for part in parts: url = part.url - path = dest / part.path - assert path.is_relative_to(dest), "only relative download paths accepted" + path = (dest / part.path).resolve() + if not path.is_relative_to(dest_root): + raise ValueError("only relative download paths accepted") job = DownloadJob( source=url, dest=path, diff --git a/tests/app/services/download/test_download_queue.py b/tests/app/services/download/test_download_queue.py index edf3c115ac4..5163f879208 100644 --- a/tests/app/services/download/test_download_queue.py +++ b/tests/app/services/download/test_download_queue.py @@ -315,7 +315,7 @@ def test_multifile_no_rel_paths(tmp_path: Path, mm2_session: Session) -> None: requests_session=mm2_session, ) - with pytest.raises(AssertionError) as error: + with pytest.raises(ValueError) as error: queue.multifile_download( parts=[RemoteModelFile(url=AnyHttpUrl("http://www.civitai.com/models/12345"), path=Path("/etc/passwd"))], dest=tmp_path, @@ -323,6 +323,24 @@ def test_multifile_no_rel_paths(tmp_path: Path, mm2_session: Session) -> None: assert str(error.value) == "only relative download paths accepted" +def test_multifile_no_parent_traversal_paths(tmp_path: Path, mm2_session: Session) -> None: + queue = DownloadQueueService( + requests_session=mm2_session, + ) + + with pytest.raises(ValueError) as error: + queue.multifile_download( + parts=[ + RemoteModelFile( + url=AnyHttpUrl("http://www.civitai.com/models/12345"), + path=Path("../outside.safetensors"), + ) + ], + dest=tmp_path, + ) + assert str(error.value) == "only relative download paths accepted" + + @contextmanager def clear_config() -> Generator[None, None, None]: try: