Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions backend/app/services/agent_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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}")
Expand Down
27 changes: 27 additions & 0 deletions backend/tests/test_agent_tools_remaining_typed_outcomes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down