From 02569136df23006a89b34222e132b975e124a8c2 Mon Sep 17 00:00:00 2001 From: yorick <34773171+yaokuku123@users.noreply.github.com> Date: Sat, 11 Apr 2026 16:52:10 +0800 Subject: [PATCH] fix(sandbox): improve sandbox security and preserve multimodal content (#2114) * fix: improve sandbox security and preserve multimodal content * Add unit test modifications for test_injects_uploaded_files_tag_into_list_content * format updated_content * Add regression tests for multimodal upload content and host bash default safety --- .../agents/middlewares/uploads_middleware.py | 24 +++++++++++-------- .../harness/deerflow/sandbox/security.py | 2 +- backend/tests/test_local_bash_tool_loading.py | 6 +++++ .../test_uploads_middleware_core_logic.py | 6 +++-- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/backend/packages/harness/deerflow/agents/middlewares/uploads_middleware.py b/backend/packages/harness/deerflow/agents/middlewares/uploads_middleware.py index 78c9a7b7b..0fb217bcc 100644 --- a/backend/packages/harness/deerflow/agents/middlewares/uploads_middleware.py +++ b/backend/packages/harness/deerflow/agents/middlewares/uploads_middleware.py @@ -262,21 +262,25 @@ class UploadsMiddleware(AgentMiddleware[UploadsMiddlewareState]): files_message = self._create_files_message(new_files, historical_files) # Extract original content - handle both string and list formats - original_content = "" - if isinstance(last_message.content, str): - original_content = last_message.content - elif isinstance(last_message.content, list): - text_parts = [] - for block in last_message.content: - if isinstance(block, dict) and block.get("type") == "text": - text_parts.append(block.get("text", "")) - original_content = "\n".join(text_parts) + original_content = last_message.content + if isinstance(original_content, str): + # Simple case: string content, just prepend files message + updated_content = f"{files_message}\n\n{original_content}" + elif isinstance(original_content, list): + # Complex case: list content (multimodal), preserve all blocks + # Prepend files message as the first text block + files_block = {"type": "text", "text": f"{files_message}\n\n"} + # Keep all original blocks (including images) + updated_content = [files_block, *original_content] + else: + # Other types, preserve as-is + updated_content = original_content # Create new message with combined content. # Preserve additional_kwargs (including files metadata) so the frontend # can read structured file info from the streamed message. updated_message = HumanMessage( - content=f"{files_message}\n\n{original_content}", + content=updated_content, id=last_message.id, additional_kwargs=last_message.additional_kwargs, ) diff --git a/backend/packages/harness/deerflow/sandbox/security.py b/backend/packages/harness/deerflow/sandbox/security.py index 7c881841f..478016ad1 100644 --- a/backend/packages/harness/deerflow/sandbox/security.py +++ b/backend/packages/harness/deerflow/sandbox/security.py @@ -39,7 +39,7 @@ def is_host_bash_allowed(config=None) -> bool: sandbox_cfg = getattr(config, "sandbox", None) if sandbox_cfg is None: - return True + return False if not uses_local_sandbox_provider(config): return True return bool(getattr(sandbox_cfg, "allow_host_bash", False)) diff --git a/backend/tests/test_local_bash_tool_loading.py b/backend/tests/test_local_bash_tool_loading.py index 1ca4ccb38..60c79a937 100644 --- a/backend/tests/test_local_bash_tool_loading.py +++ b/backend/tests/test_local_bash_tool_loading.py @@ -1,5 +1,6 @@ from types import SimpleNamespace +from deerflow.sandbox.security import is_host_bash_allowed from deerflow.tools.tools import get_available_tools @@ -79,3 +80,8 @@ def test_get_available_tools_keeps_bash_for_aio_sandbox(monkeypatch): assert "bash" in names assert "ls" in names + + +def test_is_host_bash_allowed_defaults_false_when_sandbox_missing(): + assert is_host_bash_allowed(SimpleNamespace()) is False + assert is_host_bash_allowed(SimpleNamespace(sandbox=None)) is False diff --git a/backend/tests/test_uploads_middleware_core_logic.py b/backend/tests/test_uploads_middleware_core_logic.py index 72639fb09..1837c1286 100644 --- a/backend/tests/test_uploads_middleware_core_logic.py +++ b/backend/tests/test_uploads_middleware_core_logic.py @@ -256,8 +256,10 @@ class TestBeforeAgent: assert result is not None updated_msg = result["messages"][-1] - assert "" in updated_msg.content - assert "analyse this" in updated_msg.content + assert isinstance(updated_msg.content, list) + combined_text = "\n".join(block.get("text", "") for block in updated_msg.content if isinstance(block, dict)) + assert "" in combined_text + assert "analyse this" in combined_text def test_preserves_additional_kwargs_on_updated_message(self, tmp_path): mw = _middleware(tmp_path)