memories · 业务流程草案 · 2026-10-05

Claude Code 调用 gh 时,改走 memories run 注入凭证

主人先用 sudo 把一个凭证和一个程序(比如 gh)绑定好。之后 Claude Code 每次要执行 gh …,PreToolUse hook 会把命令改写成 memories run --binding gh -- …。daemon 校验绑定和程序指纹后,把凭证放进环境变量 GH_TOKEN 再启动 gh。凭证始终不经过 Claude Code,也不经过 CLI 进程。整套流程沿用现有的 memories run,不新建 exec 命令。

状态:主人已同意下面七条建议,已按建议实现。经过多轮 Codex review,最终实现在下图的基础上有这些细化(以 README、SECURITY.md 和代码为准):

已定方案 待主人拍板(虚线框)

现有 run 和 hook 场景的差距

service.rs:5701 · main.rs:2384
现有行为为什么 hook 场景用不了本方案的处理
每次都要 sudo,而 sudo 绑定在发起 CLI 的父进程 PID 上Claude Code 的 Bash 工具每次启动的是新进程,父 PID 和主人终端不同,必然报 ElevationRequired新增「运行绑定」:主人 sudo 一次,把凭证、程序指纹和环境变量名绑定好。之后凭绑定名执行,不再需要 sudo
指纹要手打 USE 确认,或者传 --approve-digesthook 里没有终端,也不该由 AI 代填指纹指纹在绑定时确认并保存。执行前 daemon 重新计算并比对,不一致就拒绝
程序必须写绝对路径,不查 PATHAI 只会写 gh绑定时把 gh 解析成绝对路径并保存。hook 只认绑定名
stdout / stderr 全部丢弃,只回退出码AI 需要看到 gh pr list 的输出见待拍板 ①
子进程继承 daemon 的工作目录gh 要靠当前目录识别是哪个仓库Request::Run 增加 cwd。CLI 传自己的当前目录,daemon 检查它是一个存在的绝对路径后再使用
CLI 不管子进程退出码是多少,都以 0 退出AI 看不出 gh 失败了绑定模式下 CLI 用子进程的退出码退出。原有显式 run 的 JSON 输出保持不变
运行期间占住 daemon 的锁,最长 300 秒gh run watch 这类命令会卡住所有 memories 操作见待拍板 ⑥

流程 1:主人建立运行绑定

一次性,终端里操作
flowchart TD
  A["主人在终端:memories sudo"] --> B["memories credential bind gh
--vault V --credential C --env GH_TOKEN
[--program PATH] [--deny 'auth token'] [--expires 8h]"] B --> C["CLI 在 PATH 里找 gh,解析成绝对路径
计算 SHA-256 指纹"] C --> D["显示:程序路径、指纹、凭证名、环境变量名、禁止的参数
主人输入 USE 确认"] D --> E["IPC 发送 RunBind"] E --> F{"服务端校验
sudo、vault 写权限、只接受 cli: 调用方
拒绝 MCP / 远程 / 无人值守 / 手机"} F -- 不通过 --> X1["拒绝并审计"] F -- 通过 --> G{"凭证类型
Credential,或者 Token 卡片下的凭证"} G -- 不是 --> X2["拒绝:不是凭证"] G -- 是 --> H["保存绑定
名称 gh → 路径、指纹、vault、凭证、
环境变量名、输出模式、禁止参数、过期时间"] H --> S{"绑定存在哪"} S --> I["审计 run.bind
只记名称、指纹、凭证 ID"] I --> J["提示:执行 memories hook install
把 gh 加进 Claude Code 的拦截列表"] class S,H pending classDef pending stroke:#b45309,stroke-width:2px,stroke-dasharray:6 4

流程 2:Claude Code 执行 gh 时的拦截与改写

memories hook claude-code
flowchart TD
  A["Claude Code 准备执行 Bash:
gh pr list --state open"] --> B["PreToolUse hook:
memories hook claude-code --commands gh
从 stdin 读取工具调用 JSON"] B --> C{"命令里有没有用到拦截列表里的命令?
按 shell 规则切词,只看命令位置"} C -- 没有 --> P["不输出,原样放行"] C -- 有 --> D{"命令结构能安全改写吗"} D -- "含 $( ) / 反引号 / eval / sh -c 包了 gh" --> X1["deny
原因:请把 gh 单独写成一条命令"] D -- 能 --> E{"memories daemon 在运行、
已解锁、存在绑定 gh?"} E -- 未解锁或没有 daemon --> X2["deny
原因:请主人解锁 memories"] E -- 没有绑定 --> X3["deny
原因:请主人执行 credential bind gh"] E -- 正常 --> F["把命令位置上的 gh 替换成
memories run --binding gh --
其余文本、引号、管道保持原样"] F --> G{"改写后怎么交给 Claude Code
allow 还是 ask"} G --> H["输出 updatedInput
主人设置里的 deny / ask 规则仍然生效
Bash 执行改写后的命令"] class D,G pending classDef pending stroke:#b45309,stroke-width:2px,stroke-dasharray:6 4

流程 3:daemon 按绑定执行

Request::Run 扩展
flowchart TD
  A["memories run --binding gh -- pr list --state open"] --> B["CLI 发送 Run:binding=gh、args、
cwd = 当前目录"] B --> C{"调用方是 cli: 吗
MCP / 远程 / 无人值守 / 手机一律拒绝"} C -- 否 --> X1["拒绝并审计"] C -- 是 --> D{"已解锁?绑定存在且未过期?"} D -- 否 --> X2["拒绝:说明原因"] D -- 是 --> E{"授权方式"} E --> F{"参数命中禁止规则?
例如 auth token、auth status --show-token"} F -- 命中 --> X3["拒绝并审计 run.denied"] F -- 没命中 --> G{"程序当前指纹 == 绑定里的指纹?"} G -- 不一致 --> X4["拒绝:gh 已变更,请重新 bind"] G -- 一致 --> H["审计 credential.use
绑定名、参数个数、cwd"] H --> I["解密凭证,设置环境变量 GH_TOKEN
在 cwd 下启动 gh"] I --> J{"输出怎么处理"} J --> K["审计 credential.result:退出码"] K --> L["CLI 把 stdout / stderr 原样写出
并用 gh 的退出码退出"] class E,J pending classDef pending stroke:#b45309,stroke-width:2px,stroke-dasharray:6 4

流程 4(可选):只授权,不注入凭证

对应「或让 Memories 给授权」
flowchart TD
  A["Claude Code 准备执行:
gh pr merge 42"] --> B["hook 识别出 gh,命中一条 gate 规则"] B --> C{"memories 规则判断"} C -- "规则允许,例如 pr list / pr view" --> D["allow:按原命令执行,不注入凭证"] C -- "规则要求确认,例如 pr merge" --> E["ask:Claude Code 弹窗请主人确认"] C -- "规则禁止,例如 repo delete" --> F["deny:附上原因"] class C pending classDef pending stroke:#b45309,stroke-width:2px,stroke-dasharray:6 4

这种模式只判断命令能不能执行,凭证仍然由程序自己保管,比如 gh 自己的 keyring。它和流程 2、3 可以并存:同一个绑定既注入凭证,也带一组 allow / ask / deny 规则。见待拍板 ③。

待主人拍板

① 输出要不要还给 AI

  • A. 原样返回 stdout / stderr。daemon 先把其中出现的凭证原文替换成 [REDACTED],再配合禁止参数拦下 gh auth token 这类会打印凭证的命令。单次输出上限 4 MiB。
  • B. 保持现在的做法,完全屏蔽输出。那 AI 只能执行 gh pr create 这类只看退出码的命令。

A,并且把「是否返回输出」做成每个绑定自己的开关,默认开启。没有输出的话 hook 对 gh 基本没有用处。

② 绑定的授权强度

  • A. 绑定建好后,只要 memories 已解锁、绑定没过期,同一用户的 cli: 调用就能使用。默认 8 小时过期,memories lock 时不删除绑定,但锁定期间无法使用。
  • B. 默认永不过期,直到主人手动 unbind。
  • C. 每次使用还要主人在终端里确认。这样 hook 基本没法用。

A。代价要说清楚:在有效期内,本机同一用户的任何程序都能借这个绑定以主人的 GitHub 身份执行 gh(但拿不到 token 原文)。这和 SECURITY.md 里「不防同一用户下的恶意进程」的现有前提一致,文档里也会写明。

③ 「让 Memories 给授权」指的是什么

  • A. 指流程 2、3:有绑定才能用凭证,这本身就是授权。
  • B. 另外再做流程 4 的 allow / ask / deny 规则,比如 pr merge 必须由主人确认。

B,规则和绑定放在一起,一次做完。每条规则按 gh 子命令的前缀匹配,例如 pr merge。

④ 绑定存在哪里

  • A. 只存在本机加密状态里(State 新增字段),不随 vault 同步。
  • B. 存进 vault,跟着同步到其他设备。

A。程序路径和指纹本来就是这台机器特有的。如果同步过去,就等于在别的设备上自动开了授权,扩大了授权范围。

⑤ hook 改写后交给 Claude Code 的方式,以及怎么安装

  • 交付方式:A. 返回 permissionDecision: "allow" 和 updatedInput。按 Claude Code 文档,即使返回 allow,主人在设置里配的 deny / ask 规则(包括企业托管规则)依然生效,只是省掉普通的放行弹窗。B. 返回 "ask",每次执行 gh 都弹窗请主人确认。
  • 安装:A. memories hook install 合并写入 ~/.claude/settings.json,matcher 为 Bash|PowerShell。写入前展示 diff,需要主人确认。B. 只打印配置片段,由主人自己粘贴。
  • 注意:文档没有写清楚,权限规则比对的是改写前还是改写后的命令。主人现有的类似 Bash(gh pr merge:*) 的规则,改写后可能就匹配不上了。所以涉及敏感子命令的拦截,要靠 ③ 里 memories 自己的规则来保证,不能只依赖 Claude Code 的规则。另外文档提醒,多个 hook 同时改写同一个工具的输入时,以最后完成的那个为准,所以不要和其他会改写 Bash 的 hook 一起用。

交付方式选 A,安装选 A,并且 ③ 选 B,由 memories 自己的规则把关敏感子命令。

⑥ 长时间运行的命令会占住 daemon

  • A. 保持现状:执行期间持有服务锁,300 秒超时。实现最简单,但 gh run watch 运行期间,所有 memories 操作都要排队。
  • B. 绑定模式下,daemon 在锁内完成校验、解密和审计,然后释放锁再启动子进程,结束后重新加锁写审计。

B。改动集中在 IPC 层的 Run 分支,SECURITY.md 里「执行期间会占住服务」那段说明也要同步更新。

⑦ 复合命令的范围

  • A. 支持 |、&&、||、; 连接的命令里,每一段命令位置上的 gh 都改写。遇到 $( )、反引号或 sh -c 时 deny。
  • B. 只支持单独一条 gh …,其他情况一律 deny。

A。AI 很常写 gh pr list | head 这样的命令,只支持 B 的话会频繁被拒。