学 AI 产品 · 专业 AI 产品经理播客第 2 章 · T2 解剖 Grok Build:Rust 写的生产级 Coding Agent · EP 06
第 2 章 · EP 06

从工具请求到受限执行 · Hooks:明确 deny 才阻断

时长 17:54音色 云健 · 男声

同步字幕

章节导航(点击跳转)

0:00开场 · 一次工具调用要走多少道关1:35
1:35一 · 前三段,解析、计划门与钩子2:27
4:02二 · 第四段,策略规则的合并与优先级1:35
5:37三 · 第五段,快速路径与用户确认1:59
7:36四 · 第六段,执行时沙箱还在约束2:48
10:25五 · 钩子是一组事件上的可编程检查点1:49
12:15六 · 阻断与失败放行矩阵2:00
14:15七 · 配置结构与一条测试纪律2:03
16:19收尾 · 五条可以带走的原则1:34
解读全文

grok06 · 从工具请求到受限执行 · Hooks:明确 deny 才阻断

来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)
本内容改编自小山学堂《学 AI 产品,从入门到精通》,为二次演绎配音版

本集定位

本集属于技术侧(S2)模块 T2「解剖 Grok Build」,取材自 2 节课:

  1. 从工具请求到受限执行:完整授权链
  2. Hooks:明确 deny 才阻断

核心命题有两个:

  • 授权不能被简化成「判断这是个什么工具」——它是一条六段链路;
  • Hook 只有明确 deny 才阻断,自身故障一律 fail-open——强制保证必须落在权限层与沙箱层。

一、能力地图

读完本集,你应该能够:

能力具体表现
复述授权链按 PARSE → PLAN → HOOKS → POLICY → DECIDE → ENFORCE 六段说出每段的判据
区分两层讲清「权限层决定能否尝试」与「沙箱层决定能做到什么」
判断规则胜负知道策略优先级为 deny > ask > allow,且与来源顺序无关
处理混合命令知道 Bash 会被分段,每段独立过检,不可借首段放行
识别阻断事件15 个事件里只有 PreToolUse 的 is_blocking() 为真
解释 fail-open能列出六类 hook 结果分别对应阻断还是放行
写出可测配置按「事件 → matcher 组 → 处理器列表」写 JSON,并设计四条故障测试
定位强制保证知道哪些规则必须移到权限层或沙箱,不能留在 hook

二、六段授权链


01 PARSE    工具输入 → AccessKind(Read / Edit / Bash / Grep / MCPTool / WebFetch / WebSearch)
02 PLAN     plan_mode_edit_gate 可在发出权限请求前拒绝修改
03 HOOKS    PreToolUse 显式 deny 立即阻断;timeout / 崩溃 / 格式错误 fail-open
04 POLICY   合并 requirements、managed settings、managed config、Grok config、Claude fallback
05 DECIDE   managed deny 最先短路 → yolo pin / session grants / Auto / sandbox Bash / 只读安全项 / MCP 与域名 → prompt
06 ENFORCE  Permission Allow 只放行本次请求;沙箱 active 时仍受 capability set 约束

2.1 第一段的关键提醒

决策输入比 ToolKind 更具体。

ToolInput 会携带路径、命令、域名或 MCP 名称等细节。同样一个工具,操作仓库源码与操作家目录密钥是完全不同的两件事。

2.2 第二段的关键提醒

计划模式下修改动作被拦在权限请求之前,用户根本不会看到弹窗。不问用户反而是更强的约束——它绕过了确认环节,而不是依赖它。

提示1 · 减少不必要的确认,是在保护确认机制本身

用户的确认是稀缺资源。一个每次都弹窗的系统,用户很快会养成无脑点同意的习惯——届时弹窗不再是防线,反而成了风险放大器。评估一个系统的授权设计,别只看「有没有弹窗」,要看「有多少请求在到达弹窗之前就被自动裁决掉了」。


三、第四段:策略合并与优先级

permission/resolution.rs 合并五路来源:

  1. 请求自带的 requirements
  2. managed settings
  3. managed config
  4. Grok config
  5. Claude settings fallback

规则评估与来源顺序无关,优先级为 deny > ask > allow。

为什么这条很关键

不同来源的配置由不同的人写:托管设置来自组织,项目配置来自仓库,请求自带的要求来自模型。若按来源顺序决定胜负,那么谁能控制顺序,谁就能控制安全——这显然是错的。「与来源顺序无关」是一条抗操纵的设计。

提示2 · 安全配置的合并方向必须是从严不从宽

这条与上一集「全局优先保护、项目只能新增 profile 名称」是同一个原则:系统尽量排除「谁先写 / 谁后写」的影响,只关心「有没有人说过不行」。落地自查:你的配置系统里,是否存在「后加载者覆盖先加载者」的路径? 只要存在,它就是攻击面。


四、第五段:决策的短路顺序


managed policy deny        ← 最先短路,命中即止
  ↓
yolo pin → session grants → Auto fast path / classifier
  → sandbox Bash auto → 只读安全项 → MCP 与域名授权
  ↓
prompt(仍未决定才问用户)

源码片段(教学截取)


// Managed policy runs before YOLO and sandbox fast paths.
if let Some(Decision::Reject(reason)) = policy_decision { /* PolicyDeny */ }

if matches!(&access, AccessKind::Bash(_))
    && xai_grok_sandbox::should_auto_allow_bash()
    && !policy_forced_prompt
    && !auto_forced_prompt { /* allow */ }

关键校正

「沙箱内所有写操作自动批准」并非通用规则。

源码中的 sandbox fast path 专门检查 Bash,且受 policy_forced_prompt 与 auto_forced_prompt 约束;Edit 另有自己的会话授权与 edit policy。

同理,Auto 分类器的判定也可以被强制提示开关覆盖——模型的主观判断永远排在被配置的规则后面。


五、第六段:权限层与沙箱层必须分开

权限层沙箱层
回答什么这次工具请求是否允许进入执行获准执行的进程实际能访问哪些文件与网络
读取什么AccessKind、目标细节、组织策略、会话授权、Auto verdict、用户选择capability set、子进程网络策略
失效后果规则可以要求 ask,也可以直接 deny若未 active,权限弹窗 ≠ 内核隔离

一句话:授权决定「能否尝试」,沙箱限制「执行时能做到什么」。两层叠加才是一次工具执行的真实安全边界。

5.1 Bash 分段(bash_command_splitting)

tree-sitter-bash 将可安全分解的脚本拆成每个 plain command,识别 &&、||、分号与管道。

  • 每个非 setup segment 都要独立通过安全命令、策略或授权检查——防止 ls && rm 借首段放行;
  • wrapper 会递归剥离到实际命令;危险前缀含 rm、chmod、chown、kill、git push;
  • 命令替换、复杂控制流或无法可靠分解的脚本 → 保守 prompt,用户只为完整脚本确认一次。

提示3 · 判据不是「这条命令像不像坏命令」

而是「每一段各自能不能独立通过检查」。模型很擅长生成看起来无害的命令行,若只对整条命令做一次判断,拼接技巧就成了现成的绕过手段。给自己的命令过滤加一条自查:是否存在「只校验首段 / 只校验前缀」的捷径? 有就是漏洞。


六、Hooks:事件面与阻断能力

6.1 15 个事件名

主流程 8 个:SessionStart · SessionEnd · Stop · StopFailure · PreToolUse · PostToolUse · PostToolUseFailure · PermissionDenied

扩展 7 个:UserPromptSubmit · Notification · SubagentStart · SubagentStop · 兼容别名 SubagentEnd · PreCompact · PostCompact

6.2 关键边界

「事件被触发」不等于「能控制主流程」。

事件枚举负责定义触发点,is_blocking() 单独声明阻断能力。15 个事件里只有 PreToolUse 的 is_blocking() 为真。

6.3 为什么只有一个能阻断

Hook 的输入是事件信封(经 stdin 传给外部命令),输出是 stdout 上的结构化决策。它是一段外部程序,不是内联策略——外部程序会崩溃、会超时、会被杀掉。

阻断意味着主流程要等一个外部进程的决定,这个等待有代价,系统只在一个地方付这个代价。


七、阻断与 fail-open 矩阵

Hook 结果dispatcher 解释工具调用
JSON decision = deny显式拒绝阻断
无有效 JSON,退出码 2fallback 拒绝阻断
有效 JSON allow,退出码 2JSON 优先放行 + 冲突警告
退出码非 0 且非 2HookRunResult::Failed放行 + 警告
超时或进程崩溃HookRunResult::Failed放行 + 警告
stdout 无效或 decision 未知回退退出码或 Failed输出本身不阻断;fallback 退出码 2 仍拒绝

HookRunnerResult::Failed(err) => {
    tracing::warn!(error = %err, "hook failed; ignoring (fail-open)");
}

源码注释明确要求:Hook 故障不能破坏工具可用性。

提示4 · 凡是「必须拦住」的规则,都要放在失败即阻断的那一层

系统明确区分了两件事:「我决定了不行」(阻断)与 「我没能做出决定」(放行)。理解了这个区分,就能判断一个检查机制能承担什么职责:

  • 必须拦住 → 权限层 / 沙箱层(失败即阻断)
  • 最好提醒一下 → Hook(失败即放行)

把强制规则放进 Hook,等于把它降级成了建议。


八、配置结构与测试纪律

8.1 JSON 层级


事件(PreToolUse)
  → matcher 组(正则 + Bash 兼容别名)
    → 处理器列表(type: command / command / timeout)

"hooks": {
  "PreToolUse": [
    { "matcher": "Bash",
      "hooks": [ { "type": "command",
                   "command": "bin/safe-shell-guard.sh",
                   "timeout": 5 } ] }
  ]
}
  • 命令从 stdin 接收事件信封;有效 JSON 决策优先,无有效 JSON 时按退出码解释,DENY_EXIT_CODE = 2 表达拒绝;
  • matcher 由正则编译,兼容映射让配置里的 Bash 命中内部工具名 run_terminal_command;
  • 全局 Hook:~/.grok/hooks/;项目 Hook:.grok/hooks/,受 folder trust 控制;
  • 保留环境变量会被过滤,未解析变量在启动前报错。

8.2 四条测试路径

  1. 脚本返回 JSON deny → 工具确实被阻断;
  2. 依次制造退出码 1、超时、无效 stdout → 三者全部放行并产生告警;
  3. 退出码改为 2 → 无效 stdout 下仍走明确拒绝;
  4. 写一句边界说明:哪条策略必须移到权限层或沙箱。

第 2 条最容易被跳过,也最该测——你以为在保护你的那个钩子,可能早就在静默放行了。

额外建议:Hook 脚本要尽量短、尽量快地返回,它跑在主流程关键路径上;设明确超时,并确认超时后的行为符合预期。

九、审查清单

  • 授权是否被简化成了 ToolKind 判断?决策输入是否包含路径 / 命令 / 域名等细节?
  • 策略优先级是否为 deny > ask > allow,且与来源顺序无关?
  • 是否存在「后加载配置覆盖先加载配置」的路径?
  • 是否有人相信「沙箱内所有写操作自动批准」?(错:sandbox fast path 专门检查 Bash,且受两个 forced prompt 约束)
  • 权限层与沙箱层是否被分开描述与分开验证?
  • Bash 是否分段判断,每个非 setup segment 独立过检?
  • Hook 事件列表是否被误认为「都能阻断」?(只有 PreToolUse)
  • 是否验证过 Hook 在退出码 1 / 超时 / 无效输出下确实放行?
  • 「必须拦住」的规则是否放到了权限层或沙箱层,而非 Hook?
  • Hook 脚本是否设置了明确 timeout,且对其失败行为有预期?

十、约束说明

  1. 取材边界:仅取自小山学堂对应 2 节课正文,不引入外部案例。
  2. 源码语义:枚举、字段与行为取自素材引用的源码快照(xai-grok-workspace/permission、xai-grok-hooks);实现可能随版本演进,落地前以当前版本源码为准。
  3. 教学截取:文中源码片段为教学压缩,省略日志字段、遥测与错误包装;条件与执行顺序与源码一致。
  4. fail-open 是当前实现语义:源码注释明确要求 Hook 故障不破坏工具可用性,但这不代表所有产品都如此,跨产品比较须以各自公开行为为准。
  5. 不要绝对化:沙箱在部分环境会退化,不得表述为「任何环境都无法绕过」。
  6. 二次演绎:音频为改写后的口播版本,段落顺序与措辞相对原课程有调整,以音频与本文为准。

十一、一句话总结

真正可靠的系统不会把安全寄托在某一个组件上,而是让每一层都清楚自己负责什么,以及自己失效时会发生什么。知道自己失效时的行为,这本身就是可靠性的一部分。

最后留一个问题:你的系统里,有没有哪个安全组件,你从未验证过它失效时会发生什么?


*来源:xueai.miyang.cn(小山学堂 · 洛小山《学 AI 产品,从入门到精通》)*

*本内容改编自小山学堂《学 AI 产品,从入门到精通》,为二次演绎配音版*