首页
/ RTK Hook 系统源码解析:从 rtk init 多 Agent 安装、SHA-256 完整性校验到 Deny > Ask > Allow 权限判决模型

RTK Hook 系统源码解析:从 rtk init 多 Agent 安装、SHA-256 完整性校验到 Deny > Ask > Allow 权限判决模型

2026-09-06 15:12:31作者:昌雅子Ethen

RTK(Rust Token Killer)的 hook 层负责让 AI 编程助手自动将原始 CLI 命令重写为 RTK 等价命令,从而在不依赖用户手动配置的前提下获得 token 节省。本文以仓库内 src/hooks/README.md 为骨架,结合 init.rsintegrity.rspermissions.rsrewrite_cmd.rs 等源码实现,系统讲解 rtk init 的安装模式、PatchMode 补丁策略、五态完整性校验、原子写入机制与跨 Agent 的权限判决模型,读完后可完整理解 RTK hook 的生命周期管理设计并具备为新增 Agent 编写安装逻辑的能力。

一、模块定位:生命周期管理层,而非重写执行层

src/hooks 是整个 RTK 中面向 LLM agent 的 hook 生命周期管理层:负责 hook 的安装、卸载、完整性校验、使用审计与信任管理。一个关键的边界划分是——它创建并维护位于仓库根目录 hooks/ 下的 hook 产物,但自身不执行重写逻辑,重写模式注册表在 discover/registry.rs,命令输出过滤在 src/cmds/

该模块明确拥有的职责包括:

  • rtk init 安装流程(通过 main.rs#L39-L62 中的 AgentTarget 枚举驱动);
  • SHA-256 完整性校验与 hook 版本检查;
  • 审计日志分析(rtk hook-audit);
  • rtk rewrite CLI 入口;
  • TOML 过滤器信任管理(rtk trust)。

两条边界说明值得注意(引自 src/hooks/README.md):

  1. rewrite_cmd.rs 是一个薄 CLI 桥——它存在的唯一目的是服务 hook(hook 以子进程方式调用 rtk rewrite),并完全委托给 discover/registry
  2. trust.rs 控制项目级 TOML 过滤器的执行许可。它放在 hooks 模块而非核心过滤引擎中,因为信任流程与"hook 安装时发现的过滤器"这一场景绑定。

模块的总入口是 src/hooks/mod.rs,其中 is_claude_hook_command()discover::lexer::shell_split 解析命令,识别 rtk hook claude 形态(含绝对路径与带引号路径,单元测试见 mod.rs#L26-L45),保证 hook 命令无论以何种路径调用都能被正确识别。

二、核心目的:命令拦截与自动重写

hook 的工作模型是:AI 助手即将执行一条原始 CLI 命令(如 git status)时,hook 拦截该命令,将其重写为 RTK 等价形式(如 rtk git status),使 LLM agent 自动获得 token 节省收益。整个仓库的 hook 实现分为两类:

  • 安装产物:部署到用户机器上的脚本/插件,位于根目录 hooks/(如 hooks/claude/rtk-rewrite.shhooks/opencode/rtk.tshooks/pi/rtk.ts);
  • 原生 Rust hook 命令constants.rs 定义了 rtk hook claudertk hook cursorrtk hook droidrtk hook vibe 等原生命令,注释明确标注它们"replaces rtk-rewrite.sh",即新版安装直接注册二进制命令而非 shell 脚本。

三、rtk init:安装模式全景

rtk init 支持以下安装流程(完整继承自文档,含各模式创建与修补的文件):

模式 命令 创建 修补
Default (global) rtk init -g Hook, SHA-256 hash, RTK.md settings.json, CLAUDE.md
Hook only rtk init -g --hook-only Hook, SHA-256 hash settings.json
Claude-MD (legacy) rtk init --claude-md 134-line RTK block CLAUDE.md
Windsurf rtk init -g --agent windsurf .windsurfrules --
Cline rtk init --agent cline .clinerules --
Codex rtk init --codex RTK.md in $CODEX_HOME or ~/.codex AGENTS.md
Cursor rtk init -g --agent cursor Cursor hook hooks.json
Pi rtk init --agent pi .pi/extensions/rtk.ts --
Hermes rtk init --agent hermes Python plugin in ~/.hermes/plugins/rtk-rewrite/ config.yaml plugins.enabled

AgentTarget 枚举:源码中的实际覆盖面

文档描述安装流程"6 agents via AgentTarget enum,现包括 Mistral Vibe + 3 个特殊模式(Gemini、Codex、OpenCode)"。从 main.rs#L39-L62 的当前源码看,AgentTarget 枚举已扩展为 11 个变体:Claude(默认)、CursorWindsurfClineKilocodeAntigravityKimiPiHermesDroidVibe,说明该枚举是随新 Agent 支持持续增长的注册表,而非固定清单。

init 流程的源码要点

init.rs 中值得关注的实现细节:

  • 嵌入产物:OpenCode 插件与 Pi 扩展通过 include_str! 在编译期嵌入 hooks/opencode/rtk.tshooks/pi/rtk.ts,RTK 感知指令则嵌入 hooks/claude/rtk-awareness.md(Claude 与 Codex 各有一份精简版),因此安装不依赖仓库目录存在;
  • 过滤器模板:当目标位置没有 filters.toml 时,rtk init 会写入带注释的模板(FILTERS_TEMPLATE / FILTERS_GLOBAL_TEMPLATE),示例展示了 match_commandstrip_lines_matchingmax_lineson_empty 等字段用法;
  • 参数冲突校验run() 入口先做模式互斥检查,例如 --codex 不能与 --opencode--claude-md--hook-only--auto-patch--no-patch 组合;OpenCode、Cursor、Windsurf 模式是 global-only,缺少 -g 会直接报错并给出正确用法提示(见 init.rs#L278-L308)。

四、完整性校验:防止 hook 被篡改

完整性系统的动机在 integrity.rs#L1-L13 的模块注释中写明:RTK 安装的 PreToolUse hook 会以 permissionDecision: "allow" 自动批准重写后的命令,绕过了 Claude Code 的权限提示,因此任何未授权的 hook 修改都构成命令注入向量。系统流程分三步:

  1. 安装时integrity::store_hash() 计算 hook 文件的 SHA-256,写入 ~/.claude/hooks/.rtk-hook.sha256(只读 0o444);
  2. 运行时integrity::runtime_check() 重新计算哈希并比对,若被篡改则阻止执行;
  3. 按需rtk verify 打印详细校验状态(PASS/FAIL/WARN/SKIP)。

五种完整性状态定义在 integrity.rs#L28-L39IntegrityStatus 枚举中:

状态 含义
Verified 哈希与存储值一致
Tampered 哈希不匹配(阻止执行)
NoBaseline hook 存在但无存储哈希(旧版安装)
NotInstalled 无 hook、无哈希
OrphanedHash 哈希文件存在但 hook 缺失

源码层面还有两处值得注意的工程取舍(见 integrity.rs#L79-L108store_hash):

  • 哈希文件格式兼容 sha256sum -c<hex_hash> rtk-rewrite.sh,可用标准工具复核;
  • 0o444 只读权限自述为"speed bump 而非安全边界"——拥有写权限的攻击者可以 chmod 掉它,其价值在于把"意外覆盖"变成"需要刻意为之的动作"。这种对安全模型边界的诚实标注,是阅读该源码时值得借鉴的做法。

五、PatchMode:settings 文件的三种修补策略

rtk init 修改 agent 配置文件(如 settings.json)时的行为由 PatchMode 控制,定义在 init.rs#L78-L84

模式 标志 行为
Ask (default) -- 提示用户 [y/N];stdin 非终端时默认 No
Auto --auto-patch 不提示直接修补;面向 CI/脚本化安装
Skip --no-patch 打印手工操作说明,由用户自行修补

与修补相关的完整状态由 PatchResult 枚举表达(init.rs#L94-L102):PatchedAlreadyPresent(hook 已在 settings.json 中)、Declined(用户拒绝提示)、Skipped--no-patch)、WouldPatch(dry-run 下本应添加)。所有 init 子模式共享 InitContext { verbose, dry_run } 上下文,dry-run 模式只打印"would create / would update"而不触碰文件系统,结束后统一输出 [dry-run] Nothing written. 尾注——这让安装流程可以在不落盘的情况下被完整验证。

过滤器信任也有对等的三态 FilterTrust { Ask, Trust, Skip }(默认 Ask,见 init.rs#L86-L92),与 PatchMode 形成"修补设置文件"与"信任项目过滤器"两条并列的交互式决策链。

六、原子性与幂等性

所有文件操作使用原子写入(tempfile + rename)防止崩溃时产生半截文件;settings 类文件在修改前备份为 .bak;全部操作幂等——多次运行 rtk init 安全无副作用。init.rs 中的 write_if_changed() 进一步实现了"内容未变则不写盘"(verbose 下打印 already up to date),并对 symlink 目标做了显式处理,保证 rename 落在真实文件上、symlink 本身被保留。

七、权限模型:Deny > Ask > Allow

RTK 强制执行一套与 Claude Code 最小权限默认值对齐的权限优先级:

Deny > Ask > Allow (explicit) > Default (ask)

规则来源与解析规则:

  • 所有 Claude Code settings.json 文件加载(项目 + 全局,含 .local 变体,路径常量见 constants.rsSETTINGS_JSON / SETTINGS_LOCAL_JSON);
  • 只提取 Bash(...) 规则,其他作用域(Read、Write)被忽略。

四种判决到 hook 行为的映射(完整继承自文档):

判决 触发条件 rewrite_cmd 退出码 Hook 行为
Deny permissions.deny 规则匹配 2 Passthrough — 交由宿主工具处理拒绝
Ask permissions.ask 规则匹配 3 重写 + 让宿主工具提示用户
Allow permissions.allow 规则匹配 0 重写 + 自动放行
Default 无规则匹配 3 重写 + 让宿主工具提示用户

判决逻辑的源码级拆解

permissions.rscheck_command_with_rules() 实现了三条比"简单优先级"更严格的安全规则:

  1. Deny 抢占:任一命令段匹配 deny 规则立即返回 Deny,先于一切其他构造;
  2. 不可证伪构造强制 Ask:若命令包含 contains_unattestable_construct(如命令替换、文件目标重定向等无法静态证明安全的构造),直接降级为 Ask,永不自动放行
  3. Allow 要求全段匹配:复合命令(&& 链)中每一个非空段都必须独立命中 allow 规则才给 Allow,任一段不匹配即失去 Allow 状态。源码注释指明这是 issue #1213 的修复——此前单个段命中 allow 就升级整条链为 Allow,可被用于绕过。

退出码契约与 rewrite_cmd

rewrite_cmd.rs#L12-L17 的文档注释给出了 hook 消费侧看到的完整契约:

退出码 stdout 含义
0 重写结果 重写被允许 — hook 可自动放行重写命令
1 (空) 无 RTK 等价命令 — hook 原样透传
2 (空) Deny 规则命中 — 交由 Claude Code 原生拒绝处理
3 重写结果 Ask 规则命中 — 重写但让 Claude Code 提示用户

hook 中的典型消费方式即文档给出的模式:REWRITTEN=$(rtk rewrite "$CMD") || exit 0。注意 rewrite_cmd.rsAllow 走正常 Ok(()) 返回,而 Ask/Deny/Passthrough 通过 std::process::exit 直接退出——退出码本身就是协议。

各 Agent 的 ask 支持矩阵

工具 ask 支持 Default 下的行为
Claude Code (rtk-rewrite.sh) Yes permissionDecision: "ask" — 提示用户
Copilot VS Code (rtk hook copilot) Yes permissionDecision: "ask" — 提示用户
Cursor (rtk hook cursor) Ready permission: "ask" — Cursor 实施该权限后将提示用户;在此之前放行
Gemini CLI (rtk hook gemini) No(仅 allow/deny) allow(限制 — Gemini 无 ask 模式)
Copilot CLI (rtk hook copilot) No updatedInput deny-with-suggestion(行为不变)
Codex ask 被解析但为 no-op allow(限制 — fails open)
Mistral Vibe (rtk hook vibe) 无原生 ask 界面 passthrough — Vibe 自身对重写命令触发审批提示

对应地,permissions.rs#L34-L41Host 枚举按宿主加载各自的规则来源:Claude(Claude settings.json)、Cursor.cursor 目录)、Gemini.gemini 目录)、Droid.factory 目录 + FACTORY_HOME_OVERRIDE)、Vibe(当前为空规则集,即完全 passthrough 语义)。

实现分工

  • permissions.rs — 加载 deny/ask/allow 规则、评估优先级、返回 PermissionVerdict
  • rewrite_cmd.rs — 将判决映射为退出码(shell hook 消费);
  • hook_cmd.rs — 将判决映射为 JSON permissionDecision 字段(Copilot/Gemini 等 stdin/stdout JSON 协议消费),main.rs#L899-L921HookCommands 枚举登记了 ClaudeCursorGeminiCopilotDroidVibe 六个处理器及一个 Check 干跑入口(rtk hook check --agent <agent> <command>)。

八、非阻塞保证:Exit Code Contract

hook_cmd.rs 中的 hook 处理器必须在每一条路径上都返回 Ok(())——成功、无匹配、解析错误乃至意外输入皆然。返回 Err 会传播到 main() 导致非零退出,从而阻塞 agent 命令的执行,违反 hooks/README.md 中记载的非阻塞保证。这是 hook 类组件的核心不变量:hook 是旁路加速器,绝不能成为命令执行的故障点。模块上 #[deny(clippy::print_stdout, clippy::print_stderr)](见 mod.rs#L6)也侧面印证了该模块对输出通道的严格纪律——JSON 协议处理器不允许有额外输出污染 stdout。

九、配套机制:信任管理、版本检查与审计

文档将信任管理列为本模块职责之一,trust.rs 的模块注释给出了完整的 trust-before-load 模型:.rtk/filters.toml 从 CWD 以最高优先级加载,攻击者可将其提交到公共仓库来控制 LLM 看到的内容(隐藏恶意代码、压制安全扫描输出、用 replace/match_output 原语改写命令输出)。为此:未信任的过滤器被直接跳过而非"带警告加载";rtk trust 在用户审查后存储 SHA-256;内容变化即失效信任(需重新审查);RTK_TRUST_PROJECT_FILTERS=1 供 CI 流水线覆盖。

另外两个文档点名的能力在源码中同样可查证:

  • hook 版本检查hook_check.rsCURRENT_HOOK_VERSION = 3maybe_warn() 每 24 小时至多提示一次,HookStatus 枚举区分 Ok / Outdated / Missing,并能识别"新二进制 hook 已注册但旧脚本未清理"的半迁移状态,提示用户重跑 rtk init -g 清理;
  • 审计rtk hook-audit --since N(默认 7 天)展示 hook 重写指标,前提是环境变量 RTK_HOOK_AUDIT=1(见 main.rs#L869-L875 的命令定义与 hook_audit_cmd.rs)。

十、开发指南:接入一个新的 AI 编程 Agent

文档给出的四步扩展流程与源码结构一一对应,是贡献该模块的标准路径:

  1. 安装逻辑:在 init.rs 中按现有 agent 模式添加安装函数(可参考 embedded include_str! 插件与 write_if_changed 原子写入的既有写法);
  2. 自定义 hook 协议处理器:若 agent 需要专属协议(如 Gemini 的 BeforeTool 或 Vibe 的 pre_tool 事件键,常量见 constants.rsPRE_TOOL_USE_KEY / BEFORE_TOOL_KEY),在 hook_cmd.rs 添加处理器函数,并在 main.rs 中同步添加 HookCommands::<Agent> 变体与 AgentTarget::<Agent> 枚举项;
  3. 权限面接线:若 agent 有可安装的权限面(denylist/allowlist),在 permissions.rscheck_command_for() 中通过新的 Host::<Agent> 变体接入;
  4. 完整性基线:在 integrity.rs 中为新 hook 文件登记期望哈希。

两条注意事项:hook_check.rs::maybe_warn() 目前只检查 Claude Code hook,其他 agent 没有过期 hook 的警告路径;验证方式是在全新环境中运行 rtk init,然后在目标 agent 中确认 hook 能正确重写命令。

十一、相关文件索引

文件 职责
src/hooks/mod.rs 模块入口、is_claude_hook_command 识别
src/hooks/init.rs 各 agent 安装流程、PatchMode、dry-run、模板
src/hooks/integrity.rs SHA-256 存储/校验、五态 IntegrityStatus
src/hooks/permissions.rs deny/ask/allow 规则加载与判决
src/hooks/rewrite_cmd.rs rtk rewrite 退出码契约
src/hooks/hook_cmd.rs 各 agent 的 JSON 协议处理器
src/hooks/hook_check.rs hook 版本检查与过期警告
src/hooks/verify_cmd.rs rtk verify:TOML 过滤器内联测试执行
src/hooks/trust.rs 项目过滤器信任存储
src/hooks/hook_audit_cmd.rs hook 重写审计指标
src/hooks/constants.rs 目录、事件键、hook 命令常量
hooks/README.md 部署产物(安装脚本/插件)总览
docs/contributing/TECHNICAL.md 整体架构参考

需要说明的适用前提:本文以当前仓库 src/hooks 的源码状态为准;AgentTarget 枚举成员、Host 变体与各 agent 的 ask 支持矩阵会随新 Agent 接入而变化,接入新 agent 前建议先核对 main.rspermissions.rs 的最新定义。

登录后查看全文
热门项目推荐
相关项目推荐