修复仓库回退配置模板被读取通道私有化 ACL 锁定 #321

Merged
lhk229 merged 3 commits from fix/config_lock into master 2026-09-10 19:03:29 +08:00
Owner

问题

Windows 上 apps/ai-game-creator-shell/game-creator.config.json 反复被"加锁":DACL 被剥成只剩一个陌生 SID,开发 agent 无法修改,cargo 因 include_str! 读不到文件无法编译;手动解锁后过一段时间又被锁。

根因

无 AppHandle 的开发 CLI(llm-statusagent-runagc:test:chat 等)经 game_creator_config_paths() 从 CWD / current_exe 向上回溯 8 级探测到 worktree 内 git 跟踪的模板文件后,读取走了为 AppData 私密凭据设计的私有通道(open_project_private_regular_fileprepare_game_creator_private_path_for_read)。该函数名为 "for read",在 Windows 上却无条件把目标 DACL 收紧为"仅当前进程用户、禁止继承"。沙箱 agent 是其 checkout 文件的 owner,校验通过后被静默私有化,其他账号全部 Access Denied。

修复

read_game_creator_config_file 按路径归属分流:

  • 位于 game_creator_runtime_config_dir()(AppData 托管目录)内的真实凭据:维持私有加固读取(设计本意);
  • 仓库旁边的回退模板 / local 覆盖(共享、可能被 git 跟踪的输入):改用 open_project_snapshot_regular_file 非变异快照通道,读取绝不修改 owner / DACL。

符合 open_project_private_regular_file 注释中"非用户明确选择的文件用 snapshot 读"的既有原则。

验证

  • 新增回归测试 fallback_template_read_stays_on_snapshot_channel_outside_runtime_dirruntime_config_read_stays_on_private_channel_inside_runtime_dir 通过;
  • tests::configuration:: 全部 43 个测试通过;
  • node scripts/check-config.mjs 通过;
  • npm run check:encodinggit diff --check 通过;
  • 同步更新 docs/project-memory/shared-memory/pitfalls.md
## 问题 Windows 上 `apps/ai-game-creator-shell/game-creator.config.json` 反复被"加锁":DACL 被剥成只剩一个陌生 SID,开发 agent 无法修改,cargo 因 `include_str!` 读不到文件无法编译;手动解锁后过一段时间又被锁。 ## 根因 无 AppHandle 的开发 CLI(`llm-status`、`agent-run`、`agc:test:chat` 等)经 `game_creator_config_paths()` 从 CWD / `current_exe` 向上回溯 8 级探测到 worktree 内 git 跟踪的模板文件后,读取走了为 AppData 私密凭据设计的私有通道(`open_project_private_regular_file` → `prepare_game_creator_private_path_for_read`)。该函数名为 "for read",在 Windows 上却无条件把目标 DACL 收紧为"仅当前进程用户、禁止继承"。沙箱 agent 是其 checkout 文件的 owner,校验通过后被静默私有化,其他账号全部 Access Denied。 ## 修复 `read_game_creator_config_file` 按路径归属分流: - 位于 `game_creator_runtime_config_dir()`(AppData 托管目录)内的真实凭据:维持私有加固读取(设计本意); - 仓库旁边的回退模板 / local 覆盖(共享、可能被 git 跟踪的输入):改用 `open_project_snapshot_regular_file` 非变异快照通道,读取绝不修改 owner / DACL。 符合 `open_project_private_regular_file` 注释中"非用户明确选择的文件用 snapshot 读"的既有原则。 ## 验证 - 新增回归测试 `fallback_template_read_stays_on_snapshot_channel_outside_runtime_dir` 与 `runtime_config_read_stays_on_private_channel_inside_runtime_dir` 通过; - `tests::configuration::` 全部 43 个测试通过; - `node scripts/check-config.mjs` 通过; - `npm run check:encoding` 与 `git diff --check` 通过; - 同步更新 `docs/project-memory/shared-memory/pitfalls.md`。
lhk229 added 1 commit 2026-09-10 16:59:48 +08:00
修复仓库回退配置模板被读取通道私有化 ACL 锁定
Project CI / Frontend tests (pull_request) Successful in 3m30s
Project CI / Backend tests (pull_request) Successful in 6m27s
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
9b5d1fe107
- config.rs 新增 game_creator_config_path_is_runtime_managed 判定,read_game_creator_config_file 按路径归属分流:AppData 托管目录内的真实凭据维持私有加固读取,仓库旁边的回退模板与 local 覆盖改用 open_project_snapshot_regular_file 非变异快照通道
- 根因:开发 CLI 无 AppHandle 时回退读取 worktree 内 git 跟踪模板,私有读通道在 Windows 上无条件收紧 DACL 为仅当前进程用户,导致其他账号与 cargo include_str! 全部 Access Denied
- tests/mod.rs 新增 clear_test_runtime_config_dir 辅助
- tests/configuration.rs 新增两个回归测试锁定快照 / 私有两条读取通道的分流
- pitfalls.md 记录该排障经验与读取通道副作用白名单教训
lhk229 added 1 commit 2026-09-10 17:03:26 +08:00
修正新增配置读取回归测试的 Rust 格式
Project CI / Repository checks (pull_request) Successful in 2m16s
Project CI / Frontend tests (pull_request) Successful in 3m8s
Project CI / Backend tests (pull_request) Successful in 7m2s
Project CI / Native shell tests (pull_request) Successful in 18m27s
3ebfcc0c2f
- tests/configuration.rs 按 cargo fmt 调整两个 assert! 宏换行
lhk229 added the Kind/Bug label 2026-09-10 17:34:29 +08:00
lhk229 reviewed 2026-09-10 17:37:21 +08:00
lhk229 left a comment
Author
Owner

审查结论:修复方向正确,分流逻辑在当前所有真实调用路径下自洽,无阻塞性问题。

已验证:

  • 两个新增测试通过;tests::configuration:: 全部 43 个测试通过
  • npm run check:encoding(4331 文件)与 git diff --check 通过
  • pitfalls.md 已按仓库规范同步更新
  • 三类真实调用路径(game_creator_config_paths()writable_game_creator_config_path()、overlays)均派生自同一 game_creator_runtime_config_dir() 存储值,starts_with 分类在 GUI / CLI 两种模式下都与路径来源一致;open_project_snapshot_regular_file 为只读打开 + 句柄身份复核,无 DACL 副作用

确认的有意行为变更:CLI 模式(runtime dir 为 None)下仓库旁回退模板和 game-creator.config.local.json 的读取不再做 owner/DACL 校验与加固,与 validate_game_creator_config_file_entry 注释“读取必须保持只读,ACL 加固发生在创建或替换时”一致;写路径(write_game_creator_config_atomically)仍会加固,非安全回退。另已核实 attempt_elevated_windows_acl_repair 的触发前提是路径在 Managed scope 白名单内,普通 worktree 模板不在其内,修复是完整的。

两条轻微意见见行内评论。

审查结论:修复方向正确,分流逻辑在当前所有真实调用路径下自洽,无阻塞性问题。 已验证: - 两个新增测试通过;`tests::configuration::` 全部 43 个测试通过 - `npm run check:encoding`(4331 文件)与 `git diff --check` 通过 - `pitfalls.md` 已按仓库规范同步更新 - 三类真实调用路径(`game_creator_config_paths()`、`writable_game_creator_config_path()`、overlays)均派生自同一 `game_creator_runtime_config_dir()` 存储值,`starts_with` 分类在 GUI / CLI 两种模式下都与路径来源一致;`open_project_snapshot_regular_file` 为只读打开 + 句柄身份复核,无 DACL 副作用 确认的有意行为变更:CLI 模式(runtime dir 为 `None`)下仓库旁回退模板和 `game-creator.config.local.json` 的读取不再做 owner/DACL 校验与加固,与 `validate_game_creator_config_file_entry` 注释“读取必须保持只读,ACL 加固发生在创建或替换时”一致;写路径(`write_game_creator_config_atomically`)仍会加固,非安全回退。另已核实 `attempt_elevated_windows_acl_repair` 的触发前提是路径在 Managed scope 白名单内,普通 worktree 模板不在其内,修复是完整的。 两条轻微意见见行内评论。
@@ -3644,0 +3646,4 @@
/// that can be git-tracked; privatizing one on read silently locks the
/// worktree template to whichever account happened to run the dev CLI, so
/// they must go through the non-mutating snapshot channel instead.
pub(crate) fn game_creator_config_path_is_runtime_managed(path: &Path) -> bool {
Author
Owner

(轻微,提示,本 PR 无需处理)starts_with 分类依赖传入路径与存储 runtime dir 的前缀一致性。存储值是 canonicalize 后的路径(Windows 上带 \\?\ 前缀),当前所有调用方的路径都从该存储值直接 join 派生,所以一致;但若未来有新调用方传入未经同一来源构造的等价路径(大小写不同、8.3 短名、经 junction 的路径)指向 runtime dir 内的真实凭据文件,starts_with 会失配,读取静默落到 snapshot 通道并跳过 owner 校验。后续若给 read_game_creator_config_file 增加外部传入路径的调用点,应先 canonicalize 再分类。

(轻微,提示,本 PR 无需处理)`starts_with` 分类依赖传入路径与存储 runtime dir 的前缀一致性。存储值是 canonicalize 后的路径(Windows 上带 `\\?\` 前缀),当前所有调用方的路径都从该存储值直接 `join` 派生,所以一致;但若未来有新调用方传入未经同一来源构造的等价路径(大小写不同、8.3 短名、经 junction 的路径)指向 runtime dir 内的真实凭据文件,`starts_with` 会失配,读取静默落到 snapshot 通道并跳过 owner 校验。后续若给 `read_game_creator_config_file` 增加外部传入路径的调用点,应先 canonicalize 再分类。
lhk229 marked this conversation as resolved
@@ -1187,0 +1197,4 @@
.expect("write fallback template");
let _guard = clear_test_runtime_config_dir();
// 仓库旁边的回退模板是共享输入,读取绝不能走会收紧 owner/DACL 的私有通道。
Author
Owner

(轻微)这个分类断言是平凡的:clear_test_runtime_config_dir() 把 runtime dir 置为 None 后,game_creator_config_path_is_runtime_managed 内部的 Option::is_some_and 对任何路径都返回 falseassert!(!...) 恒真,不依赖模板路径本身。同时读取成功也无法证明走了 snapshot 通道——Windows 测试环境下私有通道对 temp 文件加固 DACL 同样会成功返回内容。因此若有人把 None 分支改回私有通道,此测试不会失败,与测试名 "stays_on_snapshot_channel" 的承诺不符。

建议:把 runtime dir 设为一个与模板无关的另一目录(use_test_runtime_config_dir(其他路径)),再断言模板不属于 managed——这样断言才真正覆盖“按路径归属”而非“按全局开关”的分类逻辑。当前实现下改成这样后 read_game_creator_config_file(&template) 依然走 snapshot 分支,行为不变,测试仍然有效。

(轻微)这个分类断言是平凡的:`clear_test_runtime_config_dir()` 把 runtime dir 置为 `None` 后,`game_creator_config_path_is_runtime_managed` 内部的 `Option::is_some_and` 对任何路径都返回 `false`,`assert!(!...)` 恒真,不依赖模板路径本身。同时读取成功也无法证明走了 snapshot 通道——Windows 测试环境下私有通道对 temp 文件加固 DACL 同样会成功返回内容。因此若有人把 `None` 分支改回私有通道,此测试不会失败,与测试名 "stays_on_snapshot_channel" 的承诺不符。 建议:把 runtime dir 设为一个与模板无关的另一目录(`use_test_runtime_config_dir(其他路径)`),再断言模板不属于 managed——这样断言才真正覆盖“按路径归属”而非“按全局开关”的分类逻辑。当前实现下改成这样后 `read_game_creator_config_file(&template)` 依然走 snapshot 分支,行为不变,测试仍然有效。
lhk229 marked this conversation as resolved
lhk229 added 1 commit 2026-09-10 17:47:03 +08:00
按评审意见强化回退模板读取通道回归测试
Project CI / Repository checks (pull_request) Successful in 2m47s
Project CI / Frontend tests (pull_request) Successful in 3m44s
Project CI / Backend tests (pull_request) Successful in 7m1s
Project CI / Native shell tests (pull_request) Successful in 18m11s
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
89447ed432
- tests/configuration.rs 回退模板测试改用与模板无关的 runtime dir,分类断言覆盖按路径归属而非 runtime dir 为 None 的恒真分支,并补充 runtime dir 内路径的 managed 正向断言
- tests/mod.rs 移除不再使用的 clear_test_runtime_config_dir 辅助
Author
Owner

已处理评审意见(commit 89447ed43):

  1. 行内评论 1(测试断言平凡):已按建议修改——回退模板测试改为设置一个与模板无关的 runtime dir,断言同时覆盖模板不属于 managed(负向)与 runtime dir 内路径属于 managed(正向),分类逻辑真正按路径归属验证;同时移除不再使用的 clear_test_runtime_config_dir 辅助。两个新增测试与 tests::configuration:: 全部 43 个测试通过,cargo fmt --check 通过。
  2. 行内评论 2(starts_with 前缀一致性提示):确认本 PR 无需处理,后续新增外部传入路径的调用点时先 canonicalize 再分类。
已处理评审意见(commit `89447ed43`): 1. **行内评论 1(测试断言平凡)**:已按建议修改——回退模板测试改为设置一个与模板无关的 runtime dir,断言同时覆盖模板不属于 managed(负向)与 runtime dir 内路径属于 managed(正向),分类逻辑真正按路径归属验证;同时移除不再使用的 `clear_test_runtime_config_dir` 辅助。两个新增测试与 `tests::configuration::` 全部 43 个测试通过,`cargo fmt --check` 通过。 2. **行内评论 2(starts_with 前缀一致性提示)**:确认本 PR 无需处理,后续新增外部传入路径的调用点时先 canonicalize 再分类。
lhk229 merged commit 89447ed432 into master 2026-09-10 19:03:29 +08:00
lhk229 deleted branch fix/config_lock 2026-09-10 19:03:29 +08:00
lhk229 self-assigned this 2026-09-10 21:45:03 +08:00
Sign in to join this conversation.