钩子与治理¶
本页介绍 .github/hooks/目录下的治理钩子。这些钩子为 Agent HQ 演示添加会话日志、工具调用前安全门禁、工具调用后审计日志和会话摘要。
retail-governance.json 架构¶
.github/hooks/retail-governance.json 声明了一个简单的钩子配置:
version:数字形式的配置版本,当前为1。hooks:一个映射,其中每个键都是生命周期事件名称。- 每个事件的值都是命令条目数组。
- 每个命令条目包含:
type:当前为command。bash:要执行的脚本路径。cwd:命令的工作目录。timeoutSec:最长运行时间,单位为秒。
示例条目:
{
"type": "command",
"bash": "./.github/hooks/scripts/security-gate.sh",
"cwd": ".",
"timeoutSec": 15
}
生命周期事件¶
治理文件将四个生命周期事件连接到四个 Shell 脚本:
| 事件 | 脚本 | 用途 |
|---|---|---|
sessionStart |
.github/hooks/scripts/session-init.sh |
初始化会话日志 |
preToolUse |
.github/hooks/scripts/security-gate.sh |
允许或拒绝工具使用 |
postToolUse |
.github/hooks/scripts/audit-logger.sh |
追加 JSONL 审计事件 |
sessionEnd |
.github/hooks/scripts/session-end.sh |
写入会话摘要 |
脚本行为¶
session-init.sh¶
session-init.sh 从标准输入读取钩子 JSON。它提取 .source、
.timestamp 和 .cwd(使用 jq),创建 logs/,并追加带格式的
SESSION START 块至 logs/session.log ,其中包含 UTC 时间、来源、当前工作目录和当前操作系统用户。
security-gate.sh¶
security-gate.sh 是 preToolUse 门禁。它从标准输入读取 JSON,并提取 .toolName 和 .toolArgs(使用 jq)。
对于 bash,它会拒绝匹配破坏性模式的命令:
rm -rf /rm -rf .DROP TABLEDROP DATABASEformatmkfs.- 包含以下内容的 fork 炸弹语法:
:(){
它还会拒绝引用凭据或机密路径及相关术语的 bash 命令:
.envcredentialssecrets.pem.keypassword
对于 edit 和 create,它会提取 .toolArgs.path。不匹配钩子允许列表表达式( src/、 tests/、 docs/或 .github/ )的路径会被拒绝,但少量顶层项目文件允许列表(README.md、 AGENTS.md、 mkdocs.yml)除外;这些文件将根据 仓库相对
路径进行匹配。以仓库根目录为锚点可以防止 /etc/README.md 仅凭文件名匹配绕过限制。请注意, SECURITY.md 和许可证文件仍不在允许列表中。这符合“未经许可不得修改”的规则;该规则定义于
AGENTS.md。
允许的操作会输出:
被拒绝的操作会向 logs/security-denials.log 追加一行,并输出以下结构:
{"permissionDecision":"deny","permissionDecisionReason":"Access to credential/secret files blocked by security policy"}
audit-logger.sh¶
audit-logger.sh 是 postToolUse 钩子。它读取 .toolName、
.toolArgs、 .timestamp和 .cwd,将字符串化的工具参数截断为 500 个字符,对操作进行分类,创建 logs/,并以每行一个 JSON 对象的形式追加到 logs/agent-audit.jsonl。
类别如下:
| 工具名称 | 类别 |
|---|---|
bash |
command-execution |
edit |
code-edit |
create |
file-creation |
view |
code-read |
grep、 glob |
code-search |
| 其他任何工具 | other |
审计记录格式如下:
{
"timestamp": "input timestamp",
"logged_at": "UTC write time",
"tool": "tool name",
"category": "operation category",
"args": "first 500 characters of toolArgs",
"cwd": "working directory"
}
session-end.sh¶
session-end.sh 从标准输入读取 .reason ,统计
logs/agent-audit.jsonl 和 logs/security-denials.log 中的行数(如果这些文件存在),然后追加带格式的 SESSION END 块至 logs/session.log。
已修复: preToolUse 从未运行¶
retail-governance.json 之前将 preToolUse 指向
./.github/hooks/scripts/security-gate-notworking.sh,但该文件在磁盘上不存在。实际脚本是 security-gate.sh,因此该门禁一直未执行且没有任何提示。现已修正。
由此可见,钩子配置错误可能会静默失败。请务必验证门禁是否确实触发,尤其是用于实施安全控制的拒绝路径。
手动测试钩子¶
在仓库根目录中,将具有代表性的钩子 JSON 通过管道传给脚本,即可直接运行该脚本:
echo '{"toolName":"bash","toolArgs":{"command":"cat .env"}}' | \
./.github/hooks/scripts/security-gate.sh
预期结果:输出一个拒绝操作的 JSON 对象,并在
logs/security-denials.log 中新增一行。
可以用相同方式测试允许的路径:
echo '{"toolName":"create","toolArgs":{"path":"docs/example.md"}}' | \
./.github/hooks/scripts/security-gate.sh
临时禁用钩子¶
进行短期本地实验时,可以删除相关事件条目,或将该事件数组设置为 [];请在 .github/hooks/retail-governance.json 中完成此修改,并在提交前恢复。应尽量只禁用必要的最小范围钩子;例如,测试安全门禁配置时仅清空 preToolUse 即可。