专题篇章 · 拆开一只生产级 Coding Agent

Hooks:明确 deny 才阻断

核对生命周期事件、matcher、PreToolUse 阻断和故障 fail-open 语义

本页解决的问题

先给结论

「Hooks:明确 deny 才阻断」要解决的关键问题是什么?

核对生命周期事件、matcher、PreToolUse 阻断和故障 fail-open 语义

判断标准

让这个结论先证明自己值得留下。 把这一页当成决策工具,而不是需要背下来的定义。把概念连到一个真实任务、一个可观察结果,以及一个能改变你判断的失败上。

下一步

写下一个问题:试完这个方法后,你能用什么证据回答它?

常见误区

结论听起来很完整,却没有检查最关键的假设。

Grok Build Source Course · 12 / 19

Hooks:明确 deny 才阻断

把 Hook 看成事件上的可编程检查点。PreToolUse 可以返回明确拒绝,进程崩溃、超时和不可解析输出则走 fail-open,让工具调用继续。

15 个事件名PreToolUse 可阻断JSON 配置进程 stdin / stdout
01 / OBJECTIVES

课程目标

分清两类结果

识别显式 Deny 与 Hook 自身执行失败,它们对工具调用产生相反结果。

读懂事件匹配

掌握 matcher 的精确名、正则模式与 Bash 兼容别名。

写出可测试配置

按用户指南的 JSON 结构配置命令 Hook,并设计四条故障测试。

02 / CORE VISUAL

一次 PreToolUse 的决策路径

03 / EVENTS

源码中的事件面

会话与工具

八个主流程检查点

SessionStartSessionEndStopStopFailurePreToolUsePostToolUsePostToolUseFailurePermissionDenied。其中只有 PreToolUseis_blocking() 为真。

用户、代理与压缩

七个扩展检查点

UserPromptSubmitNotificationSubagentStartSubagentStop、兼容别名 SubagentEndPreCompactPostCompact

关键边界

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

事件枚举负责定义触发点,is_blocking() 单独声明阻断能力。读取事件列表时,要同时追踪结果如何回到调用方。

crates/codegen/xai-grok-hooks/src/event.rs
04 / SEMANTICS

阻断与 fail-open 矩阵

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

安全含义:Hook 适合策略提醒、审计和可恢复的前置检查。需要强制保证时,还应使用权限层与沙箱。源码注释明确要求 Hook 故障不能破坏工具可用性。

05 / SOURCE

真实源码证据

dispatcher.rs

失败默认放行

match result {
    HookRunnerResult::Decision(
        HookDecision::Deny { reason, .. }
    ) => {
        return PreToolUseResult {
            decision: HookDecision::Deny { ... },
            results: run_results,
        };
    }
    HookRunnerResult::Failed(err) => {
        tracing::warn!(
            error = %err,
            "hook failed; ignoring (fail-open)"
        );
    }
    _ => {}
}
crates/codegen/xai-grok-hooks/src/dispatcher.rs
matcher.rs + command.rs

匹配与退出码

pub const DENY_EXIT_CODE: i32 = 2;

pub fn matches(&self, tool_name: &str) -> bool {
    self.regex.is_match(tool_name)
        || self.matches_compat_alias(tool_name)
}

兼容映射让配置里的 Bash 可命中内部工具名 run_terminal_command。匹配器由正则编译,用户指南示例使用工具名。

crates/codegen/xai-grok-hooks/src/matcher.rs · runner/command.rs
06 / CONFIG

配置按真实 JSON 结构书写

~/.grok/hooks/*.json · project/.grok/hooks/*.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bin/safe-shell-guard.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

配置层级是「事件 → matcher 组 → 处理器列表」。命令从 stdin 接收事件信封;有效 JSON 决策优先,无有效 JSON 时再按退出码解释,退出码 2 表达拒绝。全局 Hook 位于 ~/.grok/hooks/,项目 Hook 位于 .grok/hooks/ 且受 folder trust 控制。保留环境变量会被过滤,未解析变量会在启动前报错。

crates/codegen/xai-grok-hooks/examples/hooks/safe-shell.json · xai-grok-pager/docs/user-guide/10-hooks.md
07 / LAB

课堂练习:验证四条路径

25 MIN

提交物
配置、脚本、测试记录

  1. 配置一个匹配 BashPreToolUse 命令 Hook。
  2. 让脚本对 rm -rf 返回 JSON deny,记录工具被阻断的结果。
  3. 依次制造退出码 1、超时、无效 stdout,验证三者均放行并产生告警。
  4. 将退出码改为 2,再验证无效 stdout 下仍可走明确拒绝路径。
  5. 写一句边界说明:哪条策略必须移到权限层或沙箱。
Takeaway

判断 Hook 是否安全,先问两个问题:它能否表达明确拒绝,以及它自己失效时主流程如何处理。Grok Build 的答案很清楚,显式 deny 阻断,Hook 故障 fail-open。

源码快照说明:本页依据本地 grok-build-main 快照中的 hooks crate、用户指南与示例配置整理。代码片段为教学截取,省略日志字段和错误包装;事件名、JSON 层级、退出码与决策语义保持源码一致。

「Hooks: 明确 deny 才阻断」里的风险边界在哪里

「把 Hook 看成事件上的可编程检查点。」把安全问题从一句“请模型不要犯错”拉回到权限、数据和环境。真正需要保护的是:模型即使判断失误,系统也不能让错误变成不可逆的结果。

把模型建议和实际权限分开

在「识别显式 Deny 与 Hook 自身执行失败,它们对工具调用产生相反结果」涉及的流程中,要分别检查用户能要求什么、模型能建议什么、工具实际允许什么,以及谁有权批准写入或发送。网页、文档和工具返回值都可能携带不可信指令,不能因为它们看起来像说明就自动提升权限。

  • 配置一个匹配 Bash 的 PreToolUse 命令 Hook
  • 让脚本对 rm -rf 返回 JSON deny ,记录工具被阻断的结果
  • 依次制造退出码 1、超时、无效 stdout,验证三者均放行并产生告警

安全设计必须包含失败和恢复

结合「源码快照说明: 本页依据本地 grok-build-main 快照中的 hooks crate、用户指南与示例配置整理。代码片段为教学截取,省略日志字段和错误包装;事件名、JSON 层级、退出码与决策语义保持源码一致」做一次反向演练:加入错误输入、缺失凭证或迟迟不到的审批,确认系统会拒绝、暂停并留下可追踪信息,而不是继续执行到底。

从「Hooks: 明确 deny 才阻断」走到「分清两类结果」

「Hooks: 明确 deny 才阻断」先把问题落在「把 Hook 看成事件上的可编程检查点。 PreToolUse 可以返回明确拒绝,进程崩溃、超时和不可解析输出则走 fail-open,让工具调用继续」上;到了「分清两类结果」,讨论继续推进到「识别显式 Deny 与 Hook 自身执行失败,它们对工具调用产生相反结果」。两段连起来,重点就不只是记住一个结论,而是看清它成立所依赖的条件。

把这条判断带到下一个场景

做安全判断时,把“模型想做什么”和“系统允许做什么”分开,逐个检查数据边界、工具权限、人工确认和失败后的恢复路径。

  • 「Hooks: 明确 deny 才阻断」:把 Hook 看成事件上的可编程检查点。 PreToolUse 可以返回明确拒绝,进程崩溃、超时和不可解析输出则走 fail-open,让工具调用继续
  • 「分清两类结果」:识别显式 Deny 与 Hook 自身执行失败,它们对工具调用产生相反结果
  • 「最后的要点」:写一句边界说明:哪条策略必须移到权限层或沙箱

最后的「最后的要点」把讨论落到「写一句边界说明:哪条策略必须移到权限层或沙箱」。回看这条线索时,最值得保留的是:当输入、规模或风险改变,哪些判断需要重新做一遍。

标记为已学完 阅读进度会自动记录
← 上一篇下一篇 →

继续阅读

同一条线上的下一篇。

文章讨论

读到这里,留下一个判断。

把刚想明白的地方、还没想通的问题,留给下一位一起学习的人。

正在讨论 Hooks:明确 deny 才阻断 拆开一只生产级 Coding Agent
3条讨论文章讨论 · 与共学社区同步
在共学社区查看
AM
Asha Morgan内容编辑
观点实践记录

我把这篇文章里的一个判断改写成了今天可以验证的小实验。比记住结论更有用的是,知道下一步要观察什么。

文章讨论7 有帮助
LH
Lin Harper独立开发者
观点观点

读完以后我先回头找它成立的条件,而不是直接把方法搬进项目。这个顺序让后面的取舍清楚很多。

文章讨论5 有帮助
KM
Kiki Moore产品运营
问题问题

如果把这个判断放到真实工作里,最先需要补的约束是什么?我想知道从阅读到第一次实践之间,哪一步最值得先做。

文章讨论4 有帮助