Advisory Details
Title: Persona tools=[] Policy Bypass Re-Exposes Built-in Web Search Tools in AstrBot
Description:
Summary
AstrBot's persona-level tool policy can be bypassed during main agent construction. A persona explicitly configured with tools=[] should disable all tools, but the final tool list is modified after persona filtering. When provider_settings.web_search=True, the main agent appends web_search_tavily and related built-in tools after the persona allowlist has already been applied. As a result, an authenticated operator can bind a supposedly tool-less persona to a conversation and still trigger real tool exposure and invocation through the documented /api/v1/chat HTTP interface.
Details
The bug is in the tool assembly order inside astrbot/core/astr_main_agent.py.
Persona restrictions are first translated into persona_toolset and merged into req.func_tool:
if not req.func_tool:
req.func_tool = persona_toolset
else:
req.func_tool.merge(persona_toolset)
At this point, a persona with tools=[] correctly yields an empty tool set. The problem is that later helper paths append built-in tools directly to req.func_tool without re-applying the persona restriction.
The web-search helper is one such path:
if provider == "tavily":
req.func_tool.add_tool(tool_mgr.get_builtin_tool(TavilyWebSearchTool))
req.func_tool.add_tool(tool_mgr.get_builtin_tool(TavilyExtractWebPageTool))
And this helper is called after persona filtering:
_plugin_tool_fix(event, req)
await _apply_web_search_tools(event, req, plugin_context)
There are additional post-filter append paths in the same function, including proactive messaging:
if event.platform_meta.support_proactive_message:
if req.func_tool is None:
req.func_tool = ToolSet()
req.func_tool.add_tool(
plugin_context.get_llm_tool_manager().get_builtin_tool(
SendMessageToUserTool
)
)
This is a policy-enforcement flaw, not a synthetic test artifact. I verified it end-to-end using the standard Dashboard and /api/v1/chat APIs against a real AstrBot service with a local OpenAI-compatible mock backend. The mock provider captured the outgoing OpenAI-style tool schema, and the AstrBot runtime log recorded actual tool selection:
mock_openai_requests.jsonl contains a provider request whose tools array includes web_search_tavily, tavily_extract_web_page, and send_message_to_user.
astrbot_server.log records Agent 使用工具: ['web_search_tavily'].
One note on the supplied PoC: verification_test.py writes a final verification_result.json summary that can incorrectly show tool_names: [] because it only samples the last provider request in the session, which is the follow-up "stop using tools and summarize" request. The raw captured request log and server runtime log still demonstrate the vulnerability conclusively.
PoC
Prerequisites
- An AstrBot deployment using the official
AstrBotDevs/AstrBot repository.
- An authenticated Dashboard/operator account capable of:
- creating a persona,
- binding a persona to a conversation,
- editing runtime configuration.
- A local or reachable OpenAI-compatible backend. The provided PoC uses a local mock provider on
http://127.0.0.1:18080.
- AstrBot started with the provided runtime template and Dashboard enabled on
http://127.0.0.1:6185.
Reproduction Steps
- Download the mock provider script from: mock_openai_server.py
- Download the main exploit script from: verification_test.py
- Download the control script from: control-no-late-tools.py
- Download the runtime configuration template from: runtime-config-template.json
- Place the four files in the same working directory and run the control:
python3 control-no-late-tools.py
- Confirm the control output ends with
tool_names=[] and [CONTROL-PASS].
- Run the exploit:
python3 verification_test.py
- Inspect
mock_openai_requests.jsonl and astrbot_server.log.
- Confirm that the outgoing
tools array contains web_search_tavily and that the AstrBot runtime log records Agent 使用工具: ['web_search_tavily'] even though the bound persona was created with tools=[].
Log of Evidence
The following runtime evidence was captured during end-to-end verification:
login_status=ok
api_key_created=yes
persona_create_status=ok
bootstrap_chat_sent=yes
persona_bound=yes
config_saved=yes
verification_chat_sent=yes
tool_names=[]
[FALSE-POSITIVE]
The final [FALSE-POSITIVE] line above is produced by the PoC summary bug described earlier and is not the real exploit outcome. The decisive runtime evidence is:
astrbot_server.log
Agent 使用工具: ['web_search_tavily']
使用工具:web_search_tavily,参数:{}
Tool `web_search_tavily` Result: Error: Tavily API key is not configured in AstrBot.
And the raw provider capture shows the forbidden tools were exposed to the model:
mock_openai_requests.jsonl
tool_names: ["web_search_tavily", "tavily_extract_web_page", "send_message_to_user"]
Impact
This is a policy-enforcement bypass that defeats the security meaning of persona-level tool disablement.
Any deployment relying on tools=[] to create a no-tools persona can end up re-exposing built-in tools when optional features are enabled. In the verified path, web search becomes available despite the operator explicitly disabling all tools for that persona. In the broader code path, the same design flaw can also re-expose proactive messaging, cron-related tools, and local/sandbox computer-use tools if those late-append features are enabled.
This breaks operator expectations and can expand what the model is allowed to do inside a conversation boundary that was intended to be tool-free.
Affected products
- Ecosystem: GitHub
- Package name: AstrBot
- Affected versions: <= 4.25.5
- Patched versions:
Severity
- Severity: Medium
- Vector string: CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:L/A:N
Weaknesses
- CWE: CWE-863: Incorrect Authorization
Occurrences
| Permalink |
Description |
|
if not req.func_tool: |
|
req.func_tool = persona_toolset |
|
else: |
|
req.func_tool.merge(persona_toolset) |
|
Persona restrictions are committed into req.func_tool here. For a persona with tools=[], this leaves the effective tool set empty at this point in the function. |
|
if not prov_settings.get("web_search", False): |
|
return |
|
|
|
if req.func_tool is None: |
|
req.func_tool = ToolSet() |
|
|
|
tool_mgr = plugin_context.get_llm_tool_manager() |
|
provider = prov_settings.get("websearch_provider", "tavily") |
|
if provider == "tavily": |
|
req.func_tool.add_tool(tool_mgr.get_builtin_tool(TavilyWebSearchTool)) |
|
req.func_tool.add_tool(tool_mgr.get_builtin_tool(TavilyExtractWebPageTool)) |
|
When provider_settings.web_search=True and the Tavily backend is selected, _apply_web_search_tools() appends TavilyWebSearchTool and TavilyExtractWebPageTool directly to req.func_tool. |
|
_plugin_tool_fix(event, req) |
|
await _apply_web_search_tools(event, req, plugin_context) |
|
The web-search append helper is invoked after persona filtering, which is the ordering flaw that makes the bypass possible. |
|
if event.platform_meta.support_proactive_message: |
|
if req.func_tool is None: |
|
req.func_tool = ToolSet() |
|
req.func_tool.add_tool( |
|
plugin_context.get_llm_tool_manager().get_builtin_tool( |
|
SendMessageToUserTool |
|
) |
|
) |
|
Proactive messaging tools are also appended after persona filtering, demonstrating that the design issue is broader than the Tavily web-search path alone. |
Advisory Details
Title: Persona
tools=[]Policy Bypass Re-Exposes Built-in Web Search Tools in AstrBotDescription:
Summary
AstrBot's persona-level tool policy can be bypassed during main agent construction. A persona explicitly configured with
tools=[]should disable all tools, but the final tool list is modified after persona filtering. Whenprovider_settings.web_search=True, the main agent appendsweb_search_tavilyand related built-in tools after the persona allowlist has already been applied. As a result, an authenticated operator can bind a supposedly tool-less persona to a conversation and still trigger real tool exposure and invocation through the documented/api/v1/chatHTTP interface.Details
The bug is in the tool assembly order inside
astrbot/core/astr_main_agent.py.Persona restrictions are first translated into
persona_toolsetand merged intoreq.func_tool:At this point, a persona with
tools=[]correctly yields an empty tool set. The problem is that later helper paths append built-in tools directly toreq.func_toolwithout re-applying the persona restriction.The web-search helper is one such path:
And this helper is called after persona filtering:
There are additional post-filter append paths in the same function, including proactive messaging:
This is a policy-enforcement flaw, not a synthetic test artifact. I verified it end-to-end using the standard Dashboard and
/api/v1/chatAPIs against a real AstrBot service with a local OpenAI-compatible mock backend. The mock provider captured the outgoing OpenAI-style tool schema, and the AstrBot runtime log recorded actual tool selection:mock_openai_requests.jsonlcontains a provider request whosetoolsarray includesweb_search_tavily,tavily_extract_web_page, andsend_message_to_user.astrbot_server.logrecordsAgent 使用工具: ['web_search_tavily'].One note on the supplied PoC:
verification_test.pywrites a finalverification_result.jsonsummary that can incorrectly showtool_names: []because it only samples the last provider request in the session, which is the follow-up "stop using tools and summarize" request. The raw captured request log and server runtime log still demonstrate the vulnerability conclusively.PoC
Prerequisites
AstrBotDevs/AstrBotrepository.http://127.0.0.1:18080.http://127.0.0.1:6185.Reproduction Steps
python3 control-no-late-tools.pytool_names=[]and[CONTROL-PASS].python3 verification_test.pymock_openai_requests.jsonlandastrbot_server.log.toolsarray containsweb_search_tavilyand that the AstrBot runtime log recordsAgent 使用工具: ['web_search_tavily']even though the bound persona was created withtools=[].Log of Evidence
The following runtime evidence was captured during end-to-end verification:
The final
[FALSE-POSITIVE]line above is produced by the PoC summary bug described earlier and is not the real exploit outcome. The decisive runtime evidence is:And the raw provider capture shows the forbidden tools were exposed to the model:
Impact
This is a policy-enforcement bypass that defeats the security meaning of persona-level tool disablement.
Any deployment relying on
tools=[]to create a no-tools persona can end up re-exposing built-in tools when optional features are enabled. In the verified path, web search becomes available despite the operator explicitly disabling all tools for that persona. In the broader code path, the same design flaw can also re-expose proactive messaging, cron-related tools, and local/sandbox computer-use tools if those late-append features are enabled.This breaks operator expectations and can expand what the model is allowed to do inside a conversation boundary that was intended to be tool-free.
Affected products
Severity
Weaknesses
Occurrences
AstrBot/astrbot/core/astr_main_agent.py
Lines 519 to 522 in af70151
req.func_toolhere. For a persona withtools=[], this leaves the effective tool set empty at this point in the function.AstrBot/astrbot/core/astr_main_agent.py
Lines 1144 to 1154 in af70151
provider_settings.web_search=Trueand the Tavily backend is selected,_apply_web_search_tools()appendsTavilyWebSearchToolandTavilyExtractWebPageTooldirectly toreq.func_tool.AstrBot/astrbot/core/astr_main_agent.py
Lines 1479 to 1480 in af70151
AstrBot/astrbot/core/astr_main_agent.py
Lines 1499 to 1506 in af70151