多层人工干预安全网关在 LobeHub 中的实现
学习目标
理解 LobeHub 如何在 GeneralChatAgent 中实现 7 阶段工具调用干预检查流水线,包括全局安全黑名单、动态策略解析、多种用户模式和 turn-scoped 范围守卫。
前置知识
本章涉及 Agent 工具调用安全网关的通用原理,建议先阅读:
下文假设你已理解上述概念,直接聚焦 LobeHub 的具体实现。
项目实践
LobeHub 的 7 阶段流水线
在 GeneralChatAgent.checkInterventionNeeded() 中,LobeHub 实现了完整的 7 阶段检查:
全局安全黑名单
LobeHub 的默认安全黑名单包括危险命令模式:
// 伪代码:默认安全黑名单const DEFAULT_SECURITY_BLACKLIST = [ { pattern: /^rm\s+(-rf?|--?\S*)?\s/i, policy: "always" }, { pattern: /^mkfs\./, policy: "always" }, { pattern: /^curl.*\|\s*(sh|bash)/, policy: "overridable" },];黑名单 resolver 作为默认全局 Resolver:
// 伪代码:创建默认全局审计器function createDefaultGlobalAudits() { return [{ resolver: (args, metadata) => { const blacklist = metadata.securityBlacklist ?? DEFAULT_SECURITY_BLACKLIST; return blacklist.some(rule => matchesPattern(args.command, rule.pattern)); }, policy: "always", }];}Turn-scoped 范围守卫
LobeHub 解决 stale pending tool messages 问题的具体实现:
// 伪代码:LobeHub turn-scoped 范围守卫private getCurrentTurnPendingToolMessages(state): ToolMessage[] { // 1. 找到最近的发出工具调用的 assistant 消息 let currentAssistantId: string | undefined; for (let i = state.messages.length - 1; i >= 0; i--) { const m = state.messages[i]; if (m.role === "assistant" && (m.tool_calls?.length > 0 || m.tools?.length > 0)) { currentAssistantId = m.id; break; } } if (!currentAssistantId) return [];
// 2. 只返回 parentId 匹配的 pending tool messages return state.messages.filter( m => m.role === "tool" && m.pluginIntervention?.status === "pending" && m.parentId === currentAssistantId );}这个实现的关键点:通过 parentId 关联 tool message 和发出调用的 assistant message,确保只检查当前 turn 的 pending 工具。
工具三级分类
LobeHub 对内置工具进行三级分类管理:
| 分类 | 说明 | 典型工具 |
|---|---|---|
defaultToolIds | 默认启用的核心工具 | activator、skills、web-browsing、knowledge-base、memory |
alwaysOnToolIds | 始终启用、不可禁用 | activator、skills、skill-store |
runtimeManagedToolIds | 由运行时条件决定 | cloud-sandbox、knowledge-base、memory、local-system |
三种审批交互
LobeHub 的 AgentRuntime 支持三种审批指令:
// 伪代码:审批指令类型type AgentInstruction = | { type: "request_human_approve"; pendingToolsCalling: ToolCall[] } | { type: "request_human_prompt"; message: string } | { type: "request_human_select"; options: string[] };Runtime 执行 request_human_approve 时将状态设为 waiting_for_human,暂停事件流,等待用户决策后通过新 operationId 恢复。
问题与规避
| 问题 | 场景 | 规避策略 |
|---|---|---|
| stale pending messages | 用户未审批就离开页面 | turn-scoped 范围守卫:只检查当前 assistant turn 的 pending 工具 |
| headless 模式跳过关键工具 | 安全黑名单 always 阻止 | 在任务完成报告中记录跳过的工具,供事后审计 |
| auto-run 模式未知工具 | 用户选择 auto-run 接受风险 | 未知工具守卫在 auto-run 下不启用,用户自行承担风险 |
| JSON 解析失败的工具参数 | 工具调用参数非合法 JSON | 捕获异常,按空参数处理(toolArgs = {}) |
设计取舍
为什么需要 7 阶段而不是 3 阶段?
7 阶段的设计是为了覆盖不同的安全信任层级:
- 全局黑名单(Phase 1):无论用户配置如何都阻止的危险操作
- headless 模式(Phase 2):自动化任务,无人在场
- 动态策略(Phase 3):上下文敏感的精细控制
- Always 策略(Phase 4):工具作者声明的必须审批操作
- 用户模式(Phase 5-7):用户自主选择的信任级别
每一阶段都有独特的信任假设,不能简化为统一的”允许/拒绝”。
为什么 unknown tool 在 auto-run 下不拦截?
Auto-run 模式的用户已经明确接受了所有工具的执行风险。如果对未知工具进行拦截,反而破坏了 auto-run 的语义。Manual 和 allow-list 模式下,用户没有明确接受未知风险,因此需要拦截。
参考来源
- LobeHub GeneralChatAgent 干预检查 —
packages/agent-runtime/src/agents/GeneralChatAgent.ts的checkInterventionNeeded方法 - LobeHub 内置工具分类 —
packages/builtin-tools/src/index.ts - Agent 人工干预安全网关 — 通用干预机制原理