From 63991bbd9751015f459dbb27e0440b14c1c77e3a Mon Sep 17 00:00:00 2001 From: binhnt92 Date: Tue, 12 May 2026 12:45:26 +0700 Subject: [PATCH] fix(memory): skip OpenViking upload symlinks --- plugins/memory/openviking/__init__.py | 7 ++++ .../memory/test_openviking_provider.py | 38 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/plugins/memory/openviking/__init__.py b/plugins/memory/openviking/__init__.py index 62078000866..ecb02b3de7e 100644 --- a/plugins/memory/openviking/__init__.py +++ b/plugins/memory/openviking/__init__.py @@ -336,10 +336,17 @@ ADD_RESOURCE_SCHEMA = { def _zip_directory(dir_path: Path) -> Path: """Create a temporary zip file containing a directory tree.""" + root = dir_path.resolve() zip_path = Path(tempfile.gettempdir()) / f"openviking_upload_{uuid.uuid4().hex}.zip" with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf: for file_path in dir_path.rglob("*"): + if file_path.is_symlink(): + continue if file_path.is_file(): + try: + file_path.resolve().relative_to(root) + except ValueError: + continue arcname = str(file_path.relative_to(dir_path)).replace("\\", "/") zipf.write(file_path, arcname=arcname) return zip_path diff --git a/tests/plugins/memory/test_openviking_provider.py b/tests/plugins/memory/test_openviking_provider.py index 127528205b2..3f609cd1d67 100644 --- a/tests/plugins/memory/test_openviking_provider.py +++ b/tests/plugins/memory/test_openviking_provider.py @@ -1,4 +1,5 @@ import json +import zipfile from types import SimpleNamespace from unittest.mock import MagicMock @@ -156,6 +157,43 @@ def test_tool_add_resource_uploads_existing_local_directory_and_cleans_zip(tmp_p assert result["root_uri"] == "viking://resources/docs" +def test_tool_add_resource_directory_zip_skips_symlink_escape(tmp_path): + secret = tmp_path / "outside-secret.txt" + secret.write_text("do not upload\n", encoding="utf-8") + docs = tmp_path / "docs" + docs.mkdir() + (docs / "guide.md").write_text("# Guide\n", encoding="utf-8") + link = docs / "leak.txt" + try: + link.symlink_to(secret) + except OSError as exc: + pytest.skip(f"symlinks unavailable in test environment: {exc}") + + provider = OpenVikingMemoryProvider() + provider._client = MagicMock() + archive_entries = {} + + def inspect_upload(path): + with zipfile.ZipFile(path) as archive: + archive_entries["names"] = archive.namelist() + archive_entries["payloads"] = { + name: archive.read(name) + for name in archive.namelist() + } + return "upload_docs.zip" + + provider._client.upload_temp_file.side_effect = inspect_upload + provider._client.post.return_value = { + "status": "ok", + "result": {"root_uri": "viking://resources/docs"}, + } + + json.loads(provider._tool_add_resource({"url": str(docs)})) + + assert archive_entries["names"] == ["guide.md"] + assert b"do not upload" not in b"".join(archive_entries["payloads"].values()) + + def test_tool_add_resource_cleans_local_directory_zip_when_add_fails(tmp_path): docs = tmp_path / "docs" docs.mkdir()