Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions docs/llm_tool_permission_model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# LLM Tools 统一权限模型提案

## 背景

AstrBot 中插件 tools、MCP tools、Handoff tools 以及部分内置 tools 最终都会进入 LLM tool-calling 工具集合。当前第三方工具的权限控制主要依赖插件或工具开发者自行在 handler 内实现。

如果插件开发者忘记在工具内部检查管理员权限,普通用户可能通过自然语言诱导 LLM 调用高危工具。对于文件读写、命令执行、浏览器控制、MCP 外部能力、定时任务等工具,这会带来宿主机与账号安全风险。

## 当前问题

当前权限控制存在几个结构性问题:

1. 第三方插件工具没有统一鉴权入口。
2. MCP tools 默认缺少 AstrBot 侧统一权限模型。
3. LLM 请求构建阶段会把可用工具 schema 注入给模型,普通用户可能看到不应看到的高危工具 schema。
4. 执行阶段缺少对所有 LLM tools 的统一兜底校验。
5. WebUI 无法按工具维度调整 `member` / `admin` / `disabled` 权限。

因此,第三方 tool 是否安全主要取决于插件开发者是否主动写二次鉴权。

## 目标

希望 AstrBot 为所有 LLM tools 提供统一、可配置、向后兼容的权限模型。

建议权限级别:

```text
member 所有人可用
admin 仅 AstrBot 管理员可用
disabled 完全禁用,LLM 不可见,也不可执行
```

## 建议设计

### 1. 为 FunctionTool 增加 permission 字段

建议在 `FunctionTool` 中增加:

```python
permission: str | None = None
```

含义:

- `None`:工具未显式声明权限,走全局默认策略;
- `member`:所有用户可用;
- `admin`:仅管理员可用;
- `disabled`:完全禁用。

使用 `None` 而不是直接默认 `member`,可以让旧插件统一受全局默认策略控制,方便后续渐进式收紧。

### 2. 增加统一权限决策模块

建议新增类似模块:

```text
astrbot/core/agent/tool_permission.py
```

职责:

- 标准化权限值;
- 生成稳定 permission key;
- 解析工具最终生效权限。

建议 key 格式:

```text
builtin:{tool_name}
plugin:{handler_module_path}:{tool_name}
mcp:{server_name}:{tool_name}
unknown:{tool_name}
```
Comment on lines +94 to +101
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

建议与改进:处理 handler_module_path 为空的情况

在生成插件工具的 permission key 时,使用了 plugin:{handler_module_path}:{tool_name} 格式。

潜在问题:
FunctionTool 中,handler_module_path 是一个可选字段(str | None = None)。如果插件开发者是通过手动注册或动态构建的方式添加工具,该字段可能会为 None。这会导致生成的 key 变成 plugin:None:{tool_name},在存在多个此类工具时会产生冲突。

建议:
建议在设计中明确规定当 handler_module_pathNone 时的兜底策略。例如:

  1. 优先尝试获取插件的注册名称/ID。
  2. 如果完全无法获取,则退化为 unknown:{tool_name},或者在注册时强制要求或自动填充一个唯一的模块标识符。


权限优先级:

1. WebUI 覆盖配置;
2. 工具自身声明;
3. 全局未声明工具默认策略。

### 3. 请求阶段过滤 tool schema

在 LLM 请求真正发出前,根据当前事件用户权限过滤 `req.func_tool`。

建议位置:

```text
_plugin_tool_fix(event, req)
之后
AgentRunner.reset / provider request 之前
```
Comment on lines +148 to +156
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

建议与改进:避免过滤 req.func_tool 时的副作用与竞态条件

在请求阶段过滤 req.func_tool 时,需要特别注意并发安全和副作用。

潜在问题:
如果 req.func_tool 指向的是一个共享的全局 ToolSet 实例,直接在请求阶段对其进行 in-place 过滤(修改其内部的 tools 列表)会导致严重的竞态条件(Race Condition)。例如,一个普通用户的请求可能会意外地把管理员用户的可用工具也过滤掉,或者反之导致越权。

建议:
建议在文档中明确指出:过滤操作必须在 ToolSet 的深拷贝(Deep Copy)或请求局部副本上进行。确保每个请求的 req.func_tool 都是独立的实例,从而彻底避免并发修改带来的安全隐患。


期望行为:

- `disabled` 工具永远不注入给 LLM;
- 非管理员用户看不到 `admin` 工具 schema;
- 管理员可以看到 `member` 和 `admin` 工具;
- 从源头降低 prompt injection 和 schema 暴露风险。

### 4. 执行阶段二次兜底校验

在 `ToolLoopAgentRunner._handle_function_tools()` 中,在构造 `valid_params` 与真正执行工具前增加权限检查。

这样即使出现:

- 历史上下文残留;
- 请求层过滤遗漏;
- 外部伪造 tool_call;
- 工具 schema 模式切换;

执行层仍然可以兜底拒绝未授权调用。

注意:无 `event` 的系统任务 / cron / 后台任务不应简单视为普通用户,否则可能误伤内部系统上下文。建议有 `event` 时严格走 `event.is_admin()`,无 `event` 时单独识别系统上下文。
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

建议与改进:明确“系统上下文”的安全识别机制

提案中提到:“无 event 时单独识别系统上下文”。

潜在问题:
如果“系统上下文”的识别机制不够严谨,可能会被恶意用户利用自然语言或 prompt injection 绕过权限检查(例如伪造无 event 的调用上下文)。

建议:
建议在设计中明确“系统上下文”的具体安全识别方案。例如:

  1. 引入一个不可伪造的、仅在系统内部任务(如 Cron、系统初始化)中传递的 SystemContextTokenTrustedContext 标志。
  2. 明确规定哪些特定的事件类(如 CronMessageEvent)属于受信任的系统事件,并进行严格的类型校验,而不是简单地通过 event is None 来判断。


### 5. 高危框架工具默认收紧

建议默认标记为 `admin`:

- Handoff tools / sub-agent transfer tools;
- MCP tools;
- FutureTaskTool / cron 管理工具;
- 文件改写、shell、浏览器控制等高危工具。

### 6. WebUI 提供工具权限管理

希望 WebUI 工具管理页展示:

- 工具名;
- 来源:builtin / plugin / mcp / unknown;
- 当前有效权限;
- permission key;
- 是否启用;
- 可编辑权限:`member` / `admin` / `disabled`。

修改后持久化到配置中,例如:

```json
{
"provider_settings": {
"undeclared_llm_tool_permission": "member",
"llm_tool_permission_overrides": {
"mcp:playwright:browser_navigate": "admin",
"plugin:plugins.example.main:write_file": "admin",
"builtin:future_task": "admin"
}
}
}
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

建议与改进:配置存储位置

将工具权限配置(如 undeclared_llm_tool_permissionllm_tool_permission_overrides)持久化在 provider_settings 中可能不够合适。

原因:
provider_settings 通常用于存储与特定 LLM 提供商(如 OpenAI、Claude、Ollama 等)相关的配置(例如 API 密钥、模型名称、温度等)。而工具权限是全局或通道(Channel/Origin)级别的属性,与具体使用哪个 LLM 提供商无关。如果用户切换了 LLM 提供商,不应该导致他们需要重新配置所有的工具权限。

建议:
建议将这些权限配置项放置在全局配置的独立根字段中(例如 tool_permission_settings),或者直接作为 provider_settings 的同级字段,以保证配置的内聚性和良好的用户体验。


## 预期收益

- 第三方插件即使遗漏鉴权,也有后端统一兜底;
- 普通用户不会看到高危工具 schema;
- MCP、插件、内置工具可以统一治理;
- 管理员可以在 WebUI 中动态调整工具权限;
- 旧插件保持兼容,未声明权限时默认按全局策略处理;
- 后续可以逐步将高危工具默认上调为 `admin`。

## 兼容性建议

MVP 阶段可先做:

1. `FunctionTool.permission: str | None = None`;
2. 新增 `tool_permission.py`;
3. 执行层权限兜底;
4. WebUI 工具列表返回 `permission` / `permission_key`;
5. 插件文档补充权限声明方式。

请求层过滤、WebUI set-permission 接口和高危工具默认收紧可以作为后续迭代补齐。