Files
MailUI4Agents/server/cmd/am-sandbox
JianFeeeee 1f48c5c4ff feat(sandbox): am-sandbox —— 给命令套 Landlock 内核边界(界内可写、界外 EACCES)
「工作区档」此前名不副实:档位表写「本目录内可动、越界要问人」,而 pi 这一路只能按
**工具名**判(bash/write/edit 一律问人),因为命令的影响范围无法从文本静态判定
(`cd /工作区 && rm -rf /opt/x` 以"进工作区"开头)。pi 自己**故意**不内置沙箱
(docs/security.md §No Built-in Sandbox:进程内的部分沙箱会被误解成安全边界,
"Real isolation needs to come from the operating system or a container boundary")——
dsh 是把沙箱放进自己的运行时;这个二进制补的是 pi 这一侧的**宿主**部分。

## 语义

写:只有 `--rw` 列出的目录(含子树)与 `--rw-file` 列出的文件可写,其余写操作一律
EACCES(内核判)。读与执行不限制 —— Landlock 只做白名单式加法,要连读都挡住得靠
容器/挂载命名空间。套不上边界时 **fail closed**(退出码 126,不执行命令):静默裸跑
会让"档位=workspace"变成谎话,而谎话比做不到更危险。

## 判据

- 行为 `deploy/check-sandbox.sh`:19 项全绿 —— 界内可写/子目录递归/rename+删除、
  读界外、执行、写 /dev/null;界外新建/建目录/覆盖/删除/删目录/跨边界 rename/
  软链接逃逸/子进程继承;**对照**(不套边界时那些"必须被拒"的命令必须成功,否则
  判据可能只是环境本来就只读);fail-closed 那条(rw 不存在 ⇒ 126 且命令未执行)。
- 算法 `cmd/am-sandbox` 单测:按 ABI 逐位裁剪、文件目标不得带目录类权限、
  allowed ⊂ handled、参数解析。

## 过程中撞到两个"看着能过"的坑

1. `--rw-file` 任意文件 → add_rule **EINVAL**:文件目标只能用文件类权限
   (MAKE_*/REMOVE_*/REFER 是目录类),而错误信息只有 "invalid argument",
   看不出是权限位不匹配。单测里"文件权限必须是 handled 的子集"就是拦它的。
2. ★ 判据自己翻车:`"$SB" … 2>&1 | grep -q "Permission denied"` 在
   `set -o pipefail` 下 —— **grep 命中即退出 → 左侧 EPIPE 失败 → 整条管道判失败**
   ⇒ 7 条"界外必须被拒"全被判成"没被拒"。改成先收输出再匹配,顺带打印真实输出。

## 已知边界(写进文件头 + 判据输出留痕,不假装没有)

- 元数据(chmod/chown/utimes)不受 Landlock 管辖:界外文件**内容**改不了,
  但**模式位**能改
- 网络出向不受限
- 界内**已存在**的、指向界外文件的硬链,经界内路径写入会改到界外(路径式沙箱固有)
- 本机 ABI=2(无 TRUNCATE);实测 `truncate` 越界仍被拒(coreutils 先 open 写 ⇒
  WRITE_FILE 挡住),所以那个缺口比纸面上窄
- **读不限制** ⇒ 以 root 跑的 agent 仍能读整机(含 /etc/agentmail/*.env)。沙箱在这里
  的价值是"界外留不下脚印",不是"拿不到东西" —— 要后者得上容器

## 接线

`redeploy-gateway.sh` / `install.sh` 在**边界判据真跑绿了**之后才装到
`/opt/agentmail/bin/am-sandbox`(装一个套不上边界的工具等于发空头支票)。
pi 桥的接线(按档位把 worker 套进边界)还没做 —— 见随后的报告。
2026-09-14 23:33:11 +08:00
..