fix(subagents): prohibit task tool in general-purpose system prompt (#4161)

* fix(subagents): prohibit task tool in general-purpose system prompt (#4159)

The general-purpose subagent correctly lists `task` in disallowed_tools
to prevent recursive nesting. However, the system prompt did not
explicitly tell the LLM that `task` is unavailable. When the subagent
sees the parent agent use `task`, it infers the tool is available and
attempts to call it, triggering a LangGraph tool validation error.

Add an explicit <tool_restrictions> block to the system prompt stating
that `task` is NOT available and the subagent must NEVER attempt to
call it. This prevents the LLM from attempting the call in the first
place, rather than relying on runtime rejection.

Add a regression test verifying the prompt contains the prohibition.

* fix(security): register tool_restrictions in input sanitization denylist

PR #4161 added <tool_restrictions> to general_purpose.py subagent prompt
but did not register it in _BLOCKED_TAG_NAMES. The anti-drift test
test_denylist_covers_framework_authority_blocks caught this: forging
<tool_restrictions> in untrusted input could trick the model into
believing it has (or lacks) tool restrictions it does not.

Add 'tool_restrictions' to _BLOCKED_TAG_NAMES alongside the other
subagent authority blocks (file_editing_workflow / guidelines /
output_format / working_directory).
This commit is contained in:
qin-chenghan 2026-07-14 22:51:21 +08:00 committed by GitHub
parent 9c77046d10
commit 60e50537f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 26 additions and 0 deletions

View File

@ -90,6 +90,10 @@ _BLOCKED_TAG_NAMES: frozenset[str] = frozenset(
"guidelines",
"output_format",
"working_directory",
# Subagent system-prompt block (general_purpose.py): declares the task
# tool off-limits. Forging this in untrusted input could trick the
# model into believing it has (or lacks) tool restrictions it does not.
"tool_restrictions",
# Common prompt-injection tag patterns
"system",
"instruction",

View File

@ -24,6 +24,14 @@ Do NOT use for simple, single-step operations.""",
- Do NOT ask for clarification - work with the information provided
</guidelines>
<tool_restrictions>
You are a subagent - the `task` tool is NOT available to you.
You must NEVER attempt to call `task` or dispatch further subagents.
Complete your delegated work directly using `bash`, `web_search`, `web_fetch`,
`read_file`, and other available tools.
If parallelism is needed, use bash background processes or handle steps sequentially.
</tool_restrictions>
<file_editing_workflow>
When revising an existing file, prefer `str_replace` over `write_file`
it sends only the diff and avoids re-emitting the whole file (mirrors

View File

@ -55,3 +55,17 @@ def test_general_purpose_subagent_prompt_mentions_workspace_relative_paths() ->
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
def test_general_purpose_subagent_prompt_prohibits_task_tool() -> None:
"""The system prompt must explicitly tell the LLM that `task` is unavailable.
Without this, subagents may attempt to call `task` after seeing the parent
agent use it, triggering a LangGraph tool validation error (#4159).
"""
from deerflow.subagents.builtins.general_purpose import GENERAL_PURPOSE_CONFIG
prompt = GENERAL_PURPOSE_CONFIG.system_prompt
assert "task" in prompt.lower()
assert "NOT available" in prompt or "must NEVER" in prompt
assert "disallowed_tools" not in prompt # don't leak internal implementation details