From b9f70e80e05a0cf90844f05a6a95d80b3c44c011 Mon Sep 17 00:00:00 2001 From: yaojin Date: Wed, 5 Aug 2026 19:08:57 +0800 Subject: [PATCH] fix: reject traversal in agent file delivery --- backend/app/services/agent_tools.py | 10 +++++++ ...st_agent_tools_remaining_typed_outcomes.py | 27 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/backend/app/services/agent_tools.py b/backend/app/services/agent_tools.py index 430579801..0224cdf65 100644 --- a/backend/app/services/agent_tools.py +++ b/backend/app/services/agent_tools.py @@ -9256,6 +9256,11 @@ async def _resolve_a2a_target_by_id( return target, None +def _has_parent_path_segment(path: str) -> bool: + """Return whether a user-supplied storage-relative path traverses upward.""" + return any(segment == ".." for segment in path.replace("\\", "/").split("/")) + + async def _send_file_to_agent_outcome( from_agent_id: uuid.UUID, args: dict, @@ -9290,6 +9295,11 @@ async def _send_file_to_agent_outcome( "send_file_to_agent requires target_agent_id and file_path.", "invalid_tool_arguments", ) + if _has_parent_path_segment(rel_path): + return _typed_failure( + "send_file_to_agent file_path must not contain parent directory traversal.", + "workspace_path_invalid", + ) storage = get_storage_backend() source_key = normalize_storage_key(f"{from_agent_id}/{rel_path}") diff --git a/backend/tests/test_agent_tools_remaining_typed_outcomes.py b/backend/tests/test_agent_tools_remaining_typed_outcomes.py index a0aaa9435..7958dc859 100644 --- a/backend/tests/test_agent_tools_remaining_typed_outcomes.py +++ b/backend/tests/test_agent_tools_remaining_typed_outcomes.py @@ -119,6 +119,33 @@ async def test_remaining_default_tools_have_native_typed_validation_failures( assert outcome.error_code == "invalid_tool_arguments" +@pytest.mark.asyncio +@pytest.mark.parametrize( + "file_path", + ( + "../other-agent/workspace/secret.txt", + "workspace/../../other-agent/workspace/secret.txt", + r"workspace\\..\\..\\other-agent\\workspace\\secret.txt", + ), +) +async def test_send_file_to_agent_rejects_parent_traversal_before_storage_access( + monkeypatch, + file_path: str, +) -> None: + def forbidden_storage_access(): + raise AssertionError("storage must not be accessed for a traversal path") + + monkeypatch.setattr(agent_tools, "get_storage_backend", forbidden_storage_access) + + outcome = await agent_tools._send_file_to_agent_outcome( + uuid.uuid4(), + {"target_agent_id": str(uuid.uuid4()), "file_path": file_path}, + ) + + assert outcome.status == "failed" + assert outcome.error_code == "workspace_path_invalid" + + @pytest.mark.asyncio async def test_duckduckgo_uses_http_and_parse_facts_and_timeout_is_retryable( monkeypatch,