Skip to content

fix(tools): \\?\ 扩展路径按普通写法判断授权根与权限规则;进程与终端按会话归属 - #82

Merged
AlphaCatMeow merged 6 commits into
Stack-Cairn:mainfrom
AlphaCatMeow:fix/windows-path-and-cross-run-process
Nov 3, 2026
Merged

AlphaCatMeow merged 6 commits into
Stack-Cairn:mainfrom
AlphaCatMeow:fix/windows-path-and-cross-run-process

Conversation

@AlphaCatMeow

@AlphaCatMeow AlphaCatMeow commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

LiveAgent 里有两个独立的报错,修复都在 internal/tools 的同一类检查上:

  1. 模型把截图工具返回的 \\?\D:\...\pilot-entry.png 原样交给 Image,报 Image.path is outside the authorized workspace roots。原因是 Go 把 \\?\D: 当成和 D: 不同的卷,safeWorkspacePath 里的 filepath.Rel 在两种写法之间报错。同一个原因还导致:只读根用 \\?\ 写法配置时,写入会跳过只读否决;deny 规则漏掉扩展写法。
  2. 上一轮 Bash yield_time_ms 留下的 session_id,下一轮 ProcessWait 报 managed process belongs to another canonical run。原因是进程和终端管理器按会话存在、跨运行保留,但归属检查要求 runID 也相同。

Closes #80
Closes #81

改动

  • Windows 路径写法(新包 internal/winpath):Normalize 严格校验后把 \\?\X:\... 还原成 X:\...,把 \\?\UNC\server\share\... 还原成 \\server\share\...。还原后含义会变的写法(.、..、/、空组件、末尾的点或空格、: 流、保留设备名),以及 \\.\、\??\、卷 GUID、GLOBALROOT,直接拒绝。Simplify 用于已经信任的根,拒绝的写法原样返回。纯字符串处理,非 Windows 不做任何事。
    • 以前:\\?\ 写法的合法路径被判越界;只读根用 \\?\ 写法时,普通写法的写入被放行。现在:两种写法在越界判断、只读否决、权限检查和读写里都是同一条路径。
    • 接入点:liveagentPath 和 TUI 的 read/write/edit 在 gate 之前统一走 resolveFilePath;safeWorkspacePath 两侧(覆盖授权根、只读否决、skill 根、named root、临时目录兜底)、named root 的 grant、sameDirectory、write/edit 的 GateRequest、文件锁键 canonicalPathKey。
  • 权限规则(internal/permissions):请求路径和项目根按普通写法匹配;规则本身写成 \\?\D:/...、\\?\UNC\host\share/...、//?/UNC/... 时,解析阶段改写成普通写法。以前这些 deny 规则匹配不上任何请求。
  • 反斜杠写法的路径规则(slashPath、escapeGlob):以前反斜杠后跟 *?[] 一律当转义,Windows 上写成 D:\dir\**、src\*.go 的规则被解析成 D:/dir\**,只匹配名为 dir* 的文件,deny 静默失效。Windows 文件名不可能含 * 和 ?,所以在 Windows 上 \*、\? 读作分隔符加通配符,\[、\] 仍是转义;Suggest 的 escapeGlob 同步只转义方括号。旧版本存下的 D:/dir\** 重新解析后即生效。其他系统不变。限制:反斜杠写法下的方括号目录(D:\app\[id]\**)仍有歧义,需写成 D:/app/\[id\]/**,与 Suggest 生成的形式一致。
  • 进程与终端归属:新增 sharesConversation。同一会话的任何运行都可以查看、等待、停止之前运行启动的进程和终端;其他会话、没有运行身份的调用仍然拒绝;创建运行留在记录上。ManagedProcess、ProcessWait、ProcessStop、终端读取与列表共用这一判定。普通进程仍随启动它的运行结束而取消(读到的是 cancelled)。
  • 文档:docs/liveagent-backend.md 补充进程终端按会话归属,以及 Windows 路径写法。

契约面

面 影响
backend HTTP 无协议字段变化。原来报越界的扩展写法路径现在可用;\\.\ 等命名空间写法报 unsupported Windows path namespace。终端 HTTP 入口的 ownedSession 现在按会话判断,同一会话任何已接受的运行都能看到该会话的终端
TUI read/write/edit 在 gate 前规范化路径,命名空间写法直接报错;普通路径与非 Windows 行为不变
ACP、cmd/kn 共用 tools 与 permissions,行为同上;无独立改动
权限规则 / AI 审查 规则引擎看到的是规范化后的路径。auto_review 模式下 approval/run.go 的 owned 集合仍按运行记录,跨轮轮询会走一次 AI 复核(以前是直接被拒,不算回归)
错误文案 belongs to another canonical run → belongs to another conversation。LiveAgent 与 K-brain 里没有代码匹配旧文案
docs/liveagent-backend.md 已同步

验证

  • 基线:origin/main 49e6b5e(v0.107.8)。
  • 静态:gofmt(LF 副本)、go build ./...、改动包 go vet、git diff --check 通过;复审另在 GOOS=linux、GOOS=darwin 下对 winpath/permissions/agent/tools 跑 go vet,rc=0。
  • 新增测试(都先在修改前确认失败,修复后通过):
    • internal/winpath/winpath_test.go:10 种还原写法、25 种拒绝写法、Simplify 对拒绝写法原样返回、非 Windows 不变。
    • internal/tools/liveagent_windows_path_test.go:根与目标"带前缀 / 不带前缀"四种组合加小写盘符和相对路径;17 种命名空间与畸形写法被拒;同前缀兄弟目录、根外、只读根否决;报告里那次 Image 调用原样重放且 gate 看到普通写法;超过 260 字符的长路径实际可读;sameDirectory。修改前只读根用例返回 nil(写入被放行)。
    • internal/tools/liveagent_windows_unc_test.go:\\?\UNC\ 工作区的相对路径、根自身写法、进程默认 cwd;UNC 只读根否决;TUI write/edit 对 5 种写法都不越过 deny;扩展 UNC 的 Image 在 gate 前不触发 SMB 访问(fix(tools): Image 按内容识别来源、跳过空占位并去重,可显示临时目录里的图片 #79 契约的扩展写法版);扩展 UNC 写法的 deny 规则。
    • internal/permissions/windows_path_test.go、windows_unc_pattern_test.go:规则与请求各种写法组合下 deny 命中;前缀边界输入不越界、\\?\UNCx 不被放宽。
    • internal/agent/filelocks_windows_path_test.go:两种写法共用一把文件锁。
    • internal/tools/liveagent_process_test.go:TestBashSessionOutputReadableInLaterRun 精确复现报告里的报错(修改前输出 managed process belongs to another canonical run);TestManagedProcessCanonicalOwnershipAndReadPolicy 改为同会话后续运行可读、可列、可停,其他会话与无身份仍拒绝。terminal_test.go 同步。
  • 对抗复审 3 轮:第 1 轮 3 个 P2(\\?\UNC\ 工作区回归、UNC 只读根被绕过、TUI write/edit 用 \\.\///?/ 绕过 deny);第 2 轮 1 个 P2(扩展 UNC 写法的 deny 规则匹配不上),全部修复;第 3 轮"未发现阻止合并的问题"。复审写的回归都保留在上述测试文件中。
  • 全量 go test ./...:45 个包通过,失败 6 个,均为 Windows 已知基线(TestBackendQueueFIFOInterruptAndShutdown、TestCompactThresholdExplicitOverride、TestFilesystemRootConfinementAndText、TestPersistenceCASIdempotencyAndRollback、TestRestoreSessionPreservesHistoryAndCacheIdentity、TestWaitingOnSubagentsDuringForegroundSubagent)。cmd/kn 的 TestBackendCLIExecutableIntegration 在本机挂起 40 分钟后手动结束,与 fix(tools): Image 按内容识别来源、跳过空占位并去重,可显示临时目录里的图片 #79 记录的本机基线一致,以 CI 为准。
  • race(-race -count=10,按包单独跑):internal/winpath、internal/permissions、internal/tools 通过;internal/agent 20 次失败全部来自两个已知基线(TestCompactThresholdExplicitOverride ×10、TestWaitingOnSubagentsDuringForegroundSubagent ×10),无 data race。
  • 真机(Windows,LiveAgent 调试版 + 本分支本地编译 sidecar,gpt-6.1-sol):
    • 让模型原样调用 Image,sources:["\\?\D:\Documents\Projects\Web\LiveAgent\.codex-artifacts\...\pilot-entry.png"]:返回 Display image,对话里显示 1440×1013 的截图(改前报越界)。
    • 第 1 轮 Bash yield_time_ms=1 留下 bash-1793633319242412900 后结束;第 2 轮(另一个运行)ProcessWait 同一 session_id:返回 status=cancelled、exit_code=1(改前报 belongs to another canonical run)。
  • 未覆盖:Linux/macOS 只在 CI;真实远程 SMB 主机只用回环管理共享模拟;WebUI 发起的会话没有单独走一遍(后端逻辑与宿主无关)。
  • 反斜杠规则(提交 2a88011):internal/permissions/windows_glob_rule_test.go 覆盖 D:\work\**、*.go、?.txt、**\*.env、相对 src\**、UNC 与 \\?\ 前缀,deny 与 allow 都测,另测通配范围不外溢、Suggest 形式的 \[id\] 仍为字面;修改前 deny/allow 全部不命中。对抗复审 2 轮,无 P1/P2,复审补的 review_glob_extended_test.go、review_escapeglob_r2_test.go 保留(Suggest 三个分支往返、AddRule 去重、POSIX 下 \* 仍是字面)。全量结果同上(45 包通过、6 个已知基线、cmd/kn 本机超时);internal/permissions -race -count=10 通过;GOOS=linux vet 与编译通过。
  • 说明:工作区内的 NTFS junction 会被跟随到授权根外(Go 1.23 起 Windows 上 junction 报为 ModeIrregular,safeWorkspacePath 只拒 ModeSymlink)。这是预期行为,用户在工作区里建 junction 就是为了让工具读到目标目录(例如 pnpm 的 node_modules),本 PR 不改。

🤖 Generated with Claude Code

AlphaCatMeow and others added 6 commits November 2, 2026 23:40
\\?\D:\... 与 D:\... 是同一个文件,但 Go 把 \\?\D: 当作另一个卷,
filepath.Rel 无法关联两种写法:模型照抄截图工具返回的扩展路径时
Image/Read/Write 被判越界;只读根用扩展写法配置时,普通写法的写入
跳过只读否决;deny 规则也漏掉扩展写法。

新增 internal/winpath:严格校验后把 \\?\X:\ 与 \\?\UNC\server\share\
还原成普通写法,含义会变的写法与 \\.\、\??\、卷 GUID、GLOBALROOT
拒绝。liveagentPath、TUI read/write/edit 在 gate 之前统一规范化;
safeWorkspacePath 两侧、sameDirectory、权限规则的请求路径/项目根/
规则前缀、文件锁键同步处理。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
进程与终端管理器按会话存在、跨运行保留,但归属检查要求 runID 相同:
上一轮 Bash yield_time_ms 留下的 session_id,下一轮 ProcessWait 报
"managed process belongs to another canonical run";isolated 进程跨轮
后无法停止,无 id 的 status 列表也会悄悄隐藏它们。

新增 sharesConversation:同一会话的任何运行都可查看、等待、停止;
其他会话与没有运行身份的调用仍拒绝,创建运行留在记录上。ManagedProcess
/ProcessWait/ProcessStop、终端读取与列表共用这一判定。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
请求一侧已把 \\?\UNC\host\share\... 还原成 \\host\share\...,规则一侧
只去掉了盘符前缀:Edit(\\?\UNC\host\share\dir/**) 或
Edit(//?/UNC/host/share/dir/**) 写成的 deny 规则什么都匹配不上。
trimExtendedPattern 同样把 UNC 前缀改写成 //。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
路径规则里反斜杠后跟 *?[] 一律当转义,D:\dir\** 被解析成 D:/dir\**,
只匹配名为 dir* 的文件,deny 静默失效。Windows 文件名不可能含 * 和 ?,
所以在 Windows 上 \* 和 \? 读作分隔符加通配符,\[ \] 仍是转义;
Suggest 的 escapeGlob 同步只转义方括号,记住的规则读回来含义不变。
其他系统不变。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AlphaCatMeow
AlphaCatMeow merged commit 8f75fe0 into Stack-Cairn:main Nov 3, 2026
4 checks passed
@AlphaCatMeow
AlphaCatMeow deleted the fix/windows-path-and-cross-run-process branch November 3, 2026 00:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant