Vendored deer-flow upstream (bytedance/deer-flow) plus prompt-injection hardening: - New deerflow.security package: content_delimiter, html_cleaner, sanitizer (8 layers — invisible chars, control chars, symbols, NFC, PUA, tag chars, horizontal whitespace collapse with newline/tab preservation, length cap) - New deerflow.community.searx package: web_search, web_fetch, image_search backed by a private SearX instance, every external string sanitized and wrapped in <<<EXTERNAL_UNTRUSTED_CONTENT>>> delimiters - All native community web providers (ddg_search, tavily, exa, firecrawl, jina_ai, infoquest, image_search) replaced with hard-fail stubs that raise NativeWebToolDisabledError at import time, so a misconfigured tool.use path fails loud rather than silently falling back to unsanitized output - Native client back-doors (jina_client.py, infoquest_client.py) stubbed too - Native-tool tests quarantined under tests/_disabled_native/ (collect_ignore_glob via local conftest.py) - Sanitizer Layer 7 fix: only collapse horizontal whitespace, preserve newlines and tabs so list/table structure survives - Hardened runtime config.yaml references only the searx-backed tools - Factory overlay (backend/) kept in sync with deer-flow tree as a reference / source See HARDENING.md for the full audit trail and verification steps.
56 lines
2.5 KiB
Python
56 lines
2.5 KiB
Python
"""Tests for subagent availability and prompt exposure under local bash hardening."""
|
|
|
|
from deerflow.agents.lead_agent import prompt as prompt_module
|
|
from deerflow.subagents import registry as registry_module
|
|
|
|
|
|
def test_get_available_subagent_names_hides_bash_when_host_bash_disabled(monkeypatch) -> None:
|
|
monkeypatch.setattr(registry_module, "is_host_bash_allowed", lambda: False)
|
|
|
|
names = registry_module.get_available_subagent_names()
|
|
|
|
assert names == ["general-purpose"]
|
|
|
|
|
|
def test_get_available_subagent_names_keeps_bash_when_allowed(monkeypatch) -> None:
|
|
monkeypatch.setattr(registry_module, "is_host_bash_allowed", lambda: True)
|
|
|
|
names = registry_module.get_available_subagent_names()
|
|
|
|
assert names == ["general-purpose", "bash"]
|
|
|
|
|
|
def test_build_subagent_section_hides_bash_examples_when_unavailable(monkeypatch) -> None:
|
|
monkeypatch.setattr(prompt_module, "get_available_subagent_names", lambda: ["general-purpose"])
|
|
|
|
section = prompt_module._build_subagent_section(3)
|
|
|
|
assert "Not available in the current sandbox configuration" in section
|
|
assert 'bash("npm test")' not in section
|
|
assert 'read_file("/mnt/user-data/workspace/README.md")' in section
|
|
assert "available tools (ls, read_file, web_search, etc.)" in section
|
|
|
|
|
|
def test_build_subagent_section_includes_bash_when_available(monkeypatch) -> None:
|
|
monkeypatch.setattr(prompt_module, "get_available_subagent_names", lambda: ["general-purpose", "bash"])
|
|
|
|
section = prompt_module._build_subagent_section(3)
|
|
|
|
assert "For command execution (git, build, test, deploy operations)" in section
|
|
assert 'bash("npm test")' in section
|
|
assert "available tools (bash, ls, read_file, web_search, etc.)" in section
|
|
|
|
|
|
def test_bash_subagent_prompt_mentions_workspace_relative_paths() -> None:
|
|
from deerflow.subagents.builtins.bash_agent import BASH_AGENT_CONFIG
|
|
|
|
assert "Treat `/mnt/user-data/workspace` as the default working directory for file IO" in BASH_AGENT_CONFIG.system_prompt
|
|
assert "`hello.txt`, `../uploads/input.csv`, and `../outputs/result.md`" in BASH_AGENT_CONFIG.system_prompt
|
|
|
|
|
|
def test_general_purpose_subagent_prompt_mentions_workspace_relative_paths() -> None:
|
|
from deerflow.subagents.builtins.general_purpose import GENERAL_PURPOSE_CONFIG
|
|
|
|
assert "Treat `/mnt/user-data/workspace` as the default working directory for coding and file IO" in GENERAL_PURPOSE_CONFIG.system_prompt
|
|
assert "`hello.txt`, `../uploads/input.csv`, and `../outputs/result.md`" in GENERAL_PURPOSE_CONFIG.system_prompt
|