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
This commit is contained in:
yorick 2026-04-11 16:52:10 +08:00 committed by GitHub
parent 024ac0e464
commit 02569136df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 25 additions and 13 deletions

View File

@ -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,
)

View File

@ -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))

View File

@ -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

View File

@ -256,8 +256,10 @@ class TestBeforeAgent:
assert result is not None
updated_msg = result["messages"][-1]
assert "<uploaded_files>" 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 "<uploaded_files>" in combined_text
assert "analyse this" in combined_text
def test_preserves_additional_kwargs_on_updated_message(self, tmp_path):
mw = _middleware(tmp_path)