从零搭建 Agent Harness 系列(二十)最小权限收口:符号链接、bash 关进工作区与 Grant 审计
系列十九把 Grant 收到「工作区 + 参数摘要」,文件路径做了词法沙箱,破坏性 bash 直接 Deny,授权落到 .claw/grants.json。当时还留下三件事:
1 | 符号链接跟随后仍可能逃出工作区 |
本文对应 go-tiny-claw 三次提交:
1 | f90da50 feat: 在持久化 Grant 中记录审批请求与时间 |
一、词法沙箱挡不住符号链接
ResolveWithinWorkspace 上一篇只做字符串:Abs、Clean、Rel。workDir/link.txt → /etc/passwd 的相对名仍在区内,read_file 又是 RiskSafe,打开时内核会再 follow 一次。
所以要两道检查:
1 | 词法 Rel(root, resolved) 挡住 ../outside |
工作区自己也可能是链接。macOS 上 /var 常指向 /private/var。比较真实落点时,两边都要用解开后的根,否则区内路径会被误判成逃逸。
目标文件还不存在时(write_file 先解析、再 MkdirAll),不能对整条路径 EvalSymlinks。从父目录往上走到第一个存在的祖先,再拼回后面的相对段:
1 | subdir/a.go 且 subdir 不存在 |
返回值仍是词法路径,不是 follow 之后的真实路径。调用方写入的是用户给的那个名字;测试也比较 Abs(Join(workDir, ...))。已经确认目标在区内,Open(resolved) 再 follow 一次是安全的。
错误要拆开:真正越界报「路径超出工作区」;权限不够、符号链接环报「解析符号链接失败」。不要把 follow 失败一律收成越界。
文件工具的调用点不用改,它们已经走 ResolveWithinWorkspace。
二、cmd.Dir 不是沙箱
bash 设了 cmd.Dir = workDir,只决定起始目录。cat /etc/passwd 和 cd .. 仍然能跑。
再加一道启发式:CommandEscapesWorkspace 看命令文本,不解析 shell。
1 | strings.Fields 切 token |
foo/bar、cat a.go、go test ./... 放行。./... 里没有名为 .. 的路径段。
判定放在 internal/sandbox,和破坏性命令同一层。Policy 只做映射:破坏性 或 逃逸都是 PolicyDeny,不要问用户「要不要允许 cat /etc/passwd」。Execute 在 CommandContext 之前再拒一次,换掉 Policy 也执行不了。
对外文案统一成「命令试图访问工作区外的路径」。sandbox 回答出没出界,不按绝对路径 / .. / cd 分三种给用户。
这不是 OS 隔离。cat $(echo /etc/passwd)、加引号的绝对路径,Fields 拆不开。v1 只挡明文。
三、Grant 要能回看批了什么
匹配键仍然是:
1 | SessionID + WorkDir + ToolName + ArgumentDigest |
审计字段不进钥匙。Has 只问「这条授权在不在」,不按 RequestID 查找。
AllowSession 落盘时补上:
1 | Grant{ |
FileGrantStore 整份 Grant 序列化进 grants.json,重新加载后这三项还在。测试里用第二次 NewFileGrantStore 读回,确认不是只活在内存里。
还没记的是「谁批的」。AllowOnce / Deny 也不落盘,只有会话级允许会留下记录。完整审计日志是下一档,不是改匹配键。
四、测试要证明什么
符号链接:
- 指向区内文件的 link 解析成功,返回词法路径
- 指向区外文件的 link 失败,文案是超出工作区
- 父目录是区外 link、目标文件还不存在,同样失败
a → b → a的环报解析符号链接失败,不报越界
bash:
go test ./...、ls -la、cat a.go通过cat /etc/passwd、cd ..、cd /tmp、ls ../../失败,文案含「工作区外」- Policy / Gate 对逃逸命令直接 Deny,不叫 Handler
BashTool.Execute在真正bash -c之前就返回错误
Grant 审计:落盘后再 New,RequestID、Decision、ApprovedAt 还在。
五、阶段八收到哪里
路线图里的最小权限,现在是:
1 | Grant 认工作区和参数摘要 |
还不是完整隔离:
1 | bash 仍是启发式,不是 bwrap / sandbox-exec |
工作区内的 write_file 仍询问,这是产品选择。参数级风险目前覆盖的是「明显破坏、明显逃逸直接拒绝」。
总结
十九做匹配和落盘,二十补硬边界的第二刀,并让磁盘上的 Grant 能回看:
1 | 路径先 Rel,再 follow |
阶段八可以收口。下一阶段是路线图里的 Provider 可靠性与成本预算:超时、429、重试,以及单次 Run 的 Token 上限。