Skip to content

fix(desktop): trust bundled ApeMind workflows by title+description fallback#22

Merged
earayu merged 2 commits into
mainfrom
chore/fix-bundled-recipe-trust-by-title
May 28, 2026
Merged

fix(desktop): trust bundled ApeMind workflows by title+description fallback#22
earayu merged 2 commits into
mainfrom
chore/fix-bundled-recipe-trust-by-title

Conversation

@earayu
Copy link
Copy Markdown
Collaborator

@earayu earayu commented May 28, 2026

背景

PR #20 用 SHA-256(JSON.stringify(yaml.parse(file))) 预先种 hash 文件到 userData/recipe_hashes/,希望 bundled ApeMind workflow 首次打开时不弹"⚠️ 新配方警告"。

问题

@earayu2 反馈:本地最新构建上点击 bundled workflow 仍然弹警告(#鹅岛 msg e8cb0f3b)。

@冯诺伊曼 在 3F 本机查 ~/Library/Application Support/ApeMind Agent/recipe_hashes/ 确认有 3 个 .hash 文件,说明 seedDefaultRecipes 跑过了 hash 文件也写好了。但弹窗仍然出现,说明 IPC handler has-accepted-recipe-before 收到的 recipe 对象 hash 与种入时的 hash 不一致。

根因

has-accepted-recipe-before IPC 处理函数收到的 recipe 对象,是 goose-server(Rust 后端)通过 REST 返回给 Desktop 的。Rust 侧 JSON.stringify 后的字节序列(字段顺序、null 占位、可能的额外元数据)跟 Node 端直接 JSON.stringify(yaml.parse(content)) 不一致,所以 SHA-256 不相等,hash 文件存在但匹配不上。

方案

不去逆向 Rust 序列化(耦合上游、rebase 成本高),改成在 hash 不命中时按 title + description 双字段做兜底匹配

  1. seedDefaultRecipes(main.ts):除了写 .hash 文件,再写一个 userData/bundled-recipe-titles.json,内容是 [{ title, description }, ...],列出当前 bundled 的所有 workflow。

  2. recipeHash.ts has-accepted-recipe-before 处理:原有 hash 命中逻辑保留;当 ENOENT(没匹配的 hash 文件)时调用新的 isBundledRecipeByTitleAndDescription,从 bundled-recipe-titles.json 读列表,看运行时 recipe 的 title + description 是否完全在列表里。两个字段都匹配才返回 true

为什么 title + description 双字段:

  • 单 title 容易和用户导入的外部工作流误判(@梅西 提的)
  • title + description 两个字段都精确等于 ApeMind bundled YAML 里的,碰撞概率极低
  • 用户改了 description 之后会重新弹警告,这是预期的安全行为(modified recipe 应当重新审核)

影响

  • bundled ApeMind workflow 首次打开不再弹"新配方警告",无论 Rust 序列化与 yaml.parse 输出差多少
  • 用户记录的 hash(点 "Trust and Execute" 按钮形成的接受记录)继续走原路径
  • 外部 deeplink 导入的 workflow 默认仍弹警告(除非 title + description 完全等于 bundled)
  • bundled YAML 内容更新(升级时新版 description 替换)会让用户首次点新版时弹一次警告——可接受

验收

  • @冯诺伊曼 3F 用 Computer Use:清状态、重 package、首次打开 ApeMind workflow 不弹警告
  • 确认 userData/bundled-recipe-titles.json 存在且内容为 3 条 { title, description }
  • 编辑一个 bundled workflow 的 description 后再点击应弹警告(安全行为保留)

不解决什么

  • 不去对齐 Rust / Node 两侧的 JSON.stringify 输出(不动 goose-rs)
  • 不改 RecipeWarningModal 弹窗组件本身
  • 不为 bundled workflow 加 nonce / 签名机制(PoC 阶段过度设计)
  • 不处理 SubRecipe / Sub-workflow 嵌套场景(当前 bundled 没有嵌套)

earayu added 2 commits May 28, 2026 14:39
…ches

PR #20 pre-computed SHA-256 over `JSON.stringify(yaml.parse(file))` and
wrote `userData/recipe_hashes/<hash>.hash` for each bundled YAML. Field
inspection on 3F (冯诺伊曼) confirms three `.hash` files exist for the
three bundled recipes, but earayu2 still sees "⚠️ 新配方警告" when
opening any of them.

Root cause: the `recipe` object passed to the
`has-accepted-recipe-before` IPC at runtime comes from goose-server
(Rust). Its `JSON.stringify` shape (field order, `null` for absent
optional fields, extra metadata) differs from a naive
`JSON.stringify(yaml.parse(file))`, so the SHA-256 hashes do not match
and the existing pre-trusted file is never found.

Fix: add a second match path that does not depend on byte-exact JSON
serialization:

1. `seedDefaultRecipes` also writes
   `userData/bundled-recipe-titles.json` — an array of the `title`
   fields parsed from each bundled YAML.

2. `recipeHash.ts` `has-accepted-recipe-before` handler: when the hash
   lookup misses with ENOENT, fall back to checking whether
   `recipe.title` is in the bundled-titles list. If yes, return true.

Effect: bundled recipes are trusted on first open even if the runtime
recipe shape differs from yaml.parse output. Existing hash-based trust
still works for user-recorded acceptances (e.g. user clicks "Trust and
Execute" on a non-bundled recipe).

5-cat compliance: still category 3 (默认配置) + category 4 (UI
behavior). The IPC handler change is localized to one file; no change
to goose-rs / RecipeWarningModal / recipe loading logic.

Signed-off-by: earayu <earayu@163.com>
…ion risk

@梅西 raised that pure title match could false-positive on user-imported
recipes with the same title. Tighten by requiring both `title` and
`description` to match the bundled entry.

Both fields are stable identifiers of bundled ApeMind workflows
(authored by 梅西, unlikely to collide with random user imports). User
edits that change description still see the warning, which is the
intended security behavior for modified recipes.

Signed-off-by: earayu <earayu@163.com>
@earayu earayu merged commit e8983d9 into main May 28, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant