feat(chv-guest): 对齐 Muse P2——nspawn 单元 + broker + 浏览器子代理
- chv-cell:systemd-nspawn 单命令单元,--private-users=1000:65536 (单元 root → 客机 chv),丢 8 项能力(含 SYS_PTRACE/NET_ADMIN), syscall 过滤扣 io_uring,遮蔽 /etc/chv 与 playwright, --network-veth(10.200.0.0/24 + NAT),白名单环境透传 - chv-broker:Unix socket + SO_PEERCRED(uid 范围 + pid ns 对照), 白名单 ping/sync/browser;sync 只调 chv-sync(runuser -u chv) - chv-browser:单元外独立进程,只暴露 open/snapshot/screenshot/ close/ping,返回无障碍树快照(截断);evaluate/content 拒绝 - build.sh:装 systemd-container/iproute2/iptables,拷入三脚本, 建 chv-browser 用户与两个 systemd 单元;vsock_notify 条件拷入 - buildinfo:内容项加 P2 四项,支持可选 extra_contents - 测试:cloud/tests/test_chv_muse_p2.py 40 项全过;broker/browser 真实 socket 回路冒烟通过(root 连 broker 被拒) - 差距表:docs/chv-muse-p2-gap.md;真机验证待测试机空闲(round 11) 本地做,不推、不合、不部署
This commit is contained in:
1 parent
06c3e172fe
commit
c61484aa68
8 files changed
+1826
-9
No files matched your search
@@ -0,0 +1,127 @@
|
||||
# 对齐 Muse P2 差距表:客机 nspawn 单元 + 浏览器子代理独立
|
||||
|
||||
依据:`~/workspace/research_notes/meta-muse-ai-architecture-20261101-0755/report.md`
|
||||
(官方以 Meta security.muse.ai 2026-09-08 为准;第三方拆解只作参考,
|
||||
逆向细节不得写成官方架构)。Muse 模型一句话:VM 内 systemd-nspawn
|
||||
运行时单元(Hatch)跑代理,单元 root 映射为 host 侧非特权用户,自有
|
||||
虚拟网络接口,过滤 syscall(如禁用 io_uring),受限能力(如无
|
||||
CAP_SYS_PTRACE、无 CAP_NET_ADMIN);浏览器任务由专门浏览器子代理
|
||||
完成,只见页面无障碍树快照、不见原始 DOM,不能在页面上下文跑 JS,
|
||||
CDP 由单元外 broker 管理。
|
||||
|
||||
本轨范围只限 P2:客机镜像层——systemd-nspawn 运行时单元 + 浏览器
|
||||
子代理独立。不碰 P1(执行器 WarmPool/气球/vsock,常驻 VM)、不碰 P3
|
||||
(authd 替身凭证、Sentinel 式出口审批)。分支 `feat-chv-muse-p2`
|
||||
(从 `feat-chv-cover100` 分出,基线 HEAD `06c3e17`)。
|
||||
|
||||
## 1. 现状 vs Muse
|
||||
|
||||
| 项 | 现状(实测/代码为准) | Muse 目标 | 本轨做法 |
|
||||
|---|---|---|---|
|
||||
| 代理运行位置 | codex 直接跑在客机用户态(执行器经 SSH 以 `chv` 身份调 `python3 /app/host.py`,它再起 `codex app-server`);无内层隔离 | 代理跑在 systemd-nspawn 运行时单元内 | `chv-cell run -- python3 /app/host.py --state … --codex-home … <cmd>`:单命令单元(`--as-pid2`,不 boot 第二个 systemd);单元即沙箱 |
|
||||
| 单元 root 映射 | 无此概念(`chv` 就是普通用户) | 单元 root 映射为 host 侧非特权用户 | `--private-users=1000:65536`:单元内 uid 0 → 客机侧 `chv`(uid 1000)。效果:workspace/`~/.codex`(`chv` 所有)单元内仍可写;host-root 0600 文件(如 `/etc/chv/guest-env`)在单元内显示为 overflow uid、不可读;再加 `--inaccessible=/etc/chv` 纵深防御 |
|
||||
| 单元根文件系统 | 无(直接用客机根文件系统) | 单元自有根文件系统(完整 Debian 镜像),与 host 系统分离 | **本轮不做独立根文件系统**:单元复用客机根文件系统(`--directory=/`)。独立 minbase 根文件系统约 200–300MB,另要把 codex/playwright 依赖再装一份,会顶破 qcow2 ≤1GB 验收。文件隔离本轮是“读多写少 + 遮蔽”:`/usr`、`/app` 等 host-root 所有路径单元内只读不可写,`/etc/chv`、playwright 包与浏览器二进制 `--inaccessible` 遮掉,可写路径只有显式 bind(workspace、`~/.codex`、`/run/chv`)与 tmpfs(`/tmp`)。完整独立根文件系统列为下一阶段,需先重定镜像体积预算 |
|
||||
| syscall 过滤 | 无 | 过滤 syscall,如禁用 io_uring | `--system-call-filter=@system-service ~io_uring_setup ~io_uring_enter ~io_uring_register` |
|
||||
| 能力 | 客机 `chv` 是普通用户,但 codex 的 bwrap 沙箱在单元内另有问题(见下) | 无 CAP_SYS_PTRACE、无 CAP_NET_ADMIN | `--drop-capability=CAP_SYS_ADMIN,CAP_SYS_PTRACE,CAP_NET_ADMIN,CAP_NET_RAW,CAP_SYS_MODULE,CAP_SYS_RAWIO,CAP_SYS_BOOT,CAP_MKNOD` + `--no-new-privileges` |
|
||||
| 单元内 codex 沙箱 | `host.py` 默认 `sandbox="workspace-write"`(bwrap) | 单元本身就是边界 | 单元内 codex 用 `danger-full-access`:bwrap 的 `--proc /proc` 挂载在嵌套命名空间里是已知走不通的形状(见 AGENTS.md 内层 proc 定位),单元内再套 bwrap 只会假隔离。执行器侧改 `--sandbox` 透传是 P1 的文件,本轨只定义接口(见 §4),不改 `executor.py` |
|
||||
| 单元网络 | 无(与客机共享网络命名空间) | 单元自有虚拟网络接口 | `--network-veth`:宿主侧 `ve-chv-cell`(10.200.0.1/24)、单元侧 `host0`(10.200.0.2/24,默认路由经 10.200.0.1),客机侧 NAT 出站(MASQUERADE 走客机默认路由的上行口,动态发现,不写死口名)。单元内配网经 nsenter 进单元 init 的 netns 做(`chv-cell.py` 内实现)。veth 建不起来即大声失败,不静默回落共享网络;调试逃生口 `CHV_CELL_NETWORK=shared` 须显式设置、打日志,绝不默认 |
|
||||
| 单元内外 IPC | 无(SSH 直达,host key 钉死) | Unix socket + SO_PEERCRED + 对端 ACL 的最小权限 IPC | `chv-broker`(客机侧、单元外):Unix socket `/run/chv/broker.sock`(bind 进单元)。SO_PEERCRED 校验:对端 uid 须在单元映射范围 1000–66535,且对端 pid 的 pid ns 须与 `/run/chv/cell.pid` 记的单元 init 同 ns(cell.pid 不存在时只做 uid 检查,并打日志)。操作白名单:`ping`、`sync pull|push|status`(仓目录只允许 `~/workspace` 下,`chv-sync` 以 `chv` 身份跑,不进 shell)、`browser …`(转发给浏览器子代理)。其余一律拒绝 |
|
||||
| 浏览器 | Playwright headless-shell 由 agent 在客机内直接跑,能读原始 DOM、能跑 JS | 浏览器任务由专门浏览器子代理完成,只见无障碍树快照,不见原始 DOM,不能在页面上下文跑 JS,CDP 由单元外 broker 管理 | `chv-browser`(客机侧、单元外独立进程,用户 `chv-browser`):Unix socket `/run/chv/browser.sock`,操作只有 `open|snapshot|screenshot|close`。`open`/`snapshot` 返回标题、URL 与 Playwright `accessibility.snapshot()` 的无障碍树(截断到 200 节点/64KB);`screenshot` 落盘到 `/run/chv/shots/`(bind 进单元,单元只读路径取图)。**不暴露** `evaluate`、`content`(原始 DOM)、DevTools。单元内 playwright 包与浏览器二进制用 `--inaccessible` 遮掉,单元内跑不起自己的浏览器,只能走 broker |
|
||||
| 凭证 | `/etc/chv/guest-env`(0600 root)+ `~/.git-credentials`(0600 chv),codex 在客机内直接可读后者 | 凭证由 authd 保管,代理只见替身令牌 | 本轨只做“单元看不见”:`/etc/chv` 与 `~/.git-credentials` 都 `--inaccessible`,同步走 broker 的 `sync` 操作(broker 在单元外跑 `chv-sync`,token 不进单元)。替身化本身是 P3,不在本轨冒充已做 |
|
||||
| 客机启动同步 | `chv-sync boot` 由 cloud-init runcmd 以 root 跑,clone 到 `~/workspace` | — | 不动:boot clone 仍在单元外做;单元内只经 broker 做 pull/push/status |
|
||||
|
||||
## 2. 改了什么(本轨交付物)
|
||||
|
||||
- `chv-guest/chv-cell.py`(新,标准库):单元启动器。`build_nspawn_argv()` 纯函数产
|
||||
nspawn 参数表(单元映射、能力丢弃、syscall 过滤、遮蔽、bind、tmpfs、veth、
|
||||
`--as-pid2`、`CHV_CELL=1`);`configure_cell_network()` 产配网命令表(宿主侧
|
||||
ve 接口 + NAT + nsenter 进单元配 host0);`chv-cell run -- <cmd>` 起单元、
|
||||
写 `/run/chv/cell.pid`、等退出码、清理 NAT 规则;`chv-cell argv` 只打印
|
||||
参数表、不启动(干检查用)。
|
||||
- `chv-guest/chv-broker.py`(新,标准库):单元外 IPC broker。Unix socket
|
||||
`/run/chv/broker.sock`,SO_PEERCRED + pid ns 双重校验,白名单操作见 §1,
|
||||
`sync` 调 `chv-sync`(`runuser -u chv`,list argv、不进 shell),`browser`
|
||||
转发 `chv-browser.sock`。
|
||||
- `chv-guest/chv-browser.py`(新,标准库 + playwright 懒加载):浏览器子代理。
|
||||
Unix socket `/run/chv/browser.sock`,操作 `open|snapshot|screenshot|close`,
|
||||
只返回无障碍树快照(截断)+ 标题/URL/截图路径;无 `evaluate`/`content`。
|
||||
- `chv-guest/build.sh`:apt 加 `systemd-container`、`iproute2`、`iptables`;
|
||||
拷入 `chv-cell.py`/`chv-broker.py`/`chv-browser.py`(`--copy-in` + chmod);
|
||||
建系统用户 `chv-browser`;装 `chv-broker.service`、`chv-browser.service`
|
||||
并 `systemctl enable`;`/etc/tmpfiles.d/chv.conf` 建 `/run/chv` 与
|
||||
`/run/chv/shots`。P1 交接:`cloud/chv/guest/vsock_notify.py` 若在仓库里
|
||||
存在则一并拷入(条件式,不存在时只警告、不失败——P1 文件在本分支尚无,
|
||||
合并后才有)。
|
||||
- `chv-guest/buildinfo.py`:`BASE_CONTENTS` 加 `systemd-container`、
|
||||
`chv-cell`、`chv-broker`、`chv-browser`;`build()` 支持可选 `extra_contents`
|
||||
(vsock 接入时用,不改现有调用签名)。
|
||||
- `cloud/tests/test_chv_muse_p2.py`(新):单元参数表、broker 校验与白名单、
|
||||
浏览器 API schema 与快照截断、build.sh 干跑含新步骤、buildinfo 新内容项。
|
||||
全部用假 socket/假进程,绝不连真实网络、不起真实 VM。
|
||||
- `docs/chv-production-readiness.md`:补 P2 一节(单元隔离验收与待验证项),
|
||||
旧定案不删、只追加。
|
||||
|
||||
## 3. 诚实边界(本轮不宣称的东西)
|
||||
|
||||
1. **没有独立根文件系统**:单元与客机共享根文件系统(只读挂载 + 遮蔽 +
|
||||
bind),不是 Muse 的独立 Debian 镜像。一个有决心的单元内进程仍能读到
|
||||
客机侧 world-readable 的文件内容(如 `/app/host.py`、系统库)。真正
|
||||
的文件系统分离要等下一阶段的独立根文件系统 + 体积重预算。
|
||||
2. **SO_PEERCRED 的 uid 检查不区分单元 root 与客机 `chv`**:单元映射
|
||||
`--private-users=1000:65536` 让单元内 uid 0 在客机侧就是 uid 1000,
|
||||
与客机 `chv` 同 uid。broker 的主校验是 pid ns 对照(`cell.pid`),
|
||||
uid 范围检查只是第二道;`cell.pid` 不存在时只做 uid 检查,并打日志。
|
||||
这不是“单元身份”的密码学证明,是命名空间对照。
|
||||
3. **a11y-tree-only 是方向,不是已封死的强制**:浏览器 socket 不暴露
|
||||
DOM/JS 操作,且单元内跑不起 playwright(已遮蔽),但单元内进程仍
|
||||
有 curl/Python 标准库,可以直接发 HTTP 请求取页面原文——那不是
|
||||
“浏览器 DOM 访问”,但也不是无障碍树。完整封堵(出口只走浏览器
|
||||
子代理)是 P3 Sentinel 的事。
|
||||
4. **性能数字全是待验证目标**:单元启动耗时、veth+NAT 对模型调用延迟
|
||||
的影响、快照截断后的 token 占用——本轮无真机验证(第三棒 V-1~V-9
|
||||
占着 WSL 测试机),一律记入“真机待验证清单”,不在代码/文档里写成
|
||||
已测得。
|
||||
5. **nspawn 参数以 systemd 255(`systemd-nspawn --help` 实测)为准**:
|
||||
`--private-users=UIDBASE[:NUIDS]`、`--drop-capability`、
|
||||
`--system-call-filter`、`--inaccessible`、`--network-veth`、
|
||||
`--as-pid2` 均在本机 help 里确认存在;但在 CHV 客机内核(云镜像
|
||||
generic 内核)里 userns/veth/idmap 的实际行为,须真机验证。
|
||||
6. **WarmPool/常驻路径与本轨无关**:P1 的常驻 VM 若接进来,单元在常驻
|
||||
VM 内同样起(`chv-cell run` 与 VM 生命周期无关);本轨不改执行器,
|
||||
不碰 P1 的文件。
|
||||
|
||||
## 4. 与 P1/P3 的接口(不碰对方文件,只定契约)
|
||||
|
||||
- **P1 → P2(执行器接单元)**:执行器调客内 host.py 的命令从
|
||||
`python3 /app/host.py …` 改为
|
||||
`chv-cell run -- python3 /app/host.py --state /home/chv/.codex/host-state.json --codex-home /home/chv/.codex --sandbox danger-full-access …`。
|
||||
`--sandbox danger-full-access` 只在单元内生效(单元即沙箱);非单元
|
||||
路径(本地/Docker/冷启动回落)保持原沙箱不动。改动在 `executor.py`,
|
||||
由 P1 做,本轨只提供客机侧 `chv-cell` 命令与本节契约。
|
||||
- **P1 → P2(vsock 进镜像)**:P1 的 `cloud/chv/guest/vsock_notify.py`
|
||||
由本轨 `build.sh` 条件拷入(§2),合并后自动生效;P2 不改该脚本内容。
|
||||
- **P2 → P3(broker 预留)**:broker 的 `sync` 操作现在单元外直接跑
|
||||
`chv-sync`(token 仍在客机侧落盘);P3 做 authd 替身化时,只改 broker
|
||||
内部的凭证获取方式(调 authd 拿短期替身),不改 broker 的 socket 协议
|
||||
与白名单。浏览器出口审批(只允许经 `chv-browser` 出站)同样是 P3 的
|
||||
Sentinel 接管点,本轨只做到“单元内无浏览器可跑”。
|
||||
|
||||
## 5. 真机待验证清单(round 11,测试机空闲后)
|
||||
|
||||
- lite 验收照旧:qcow2 ≤1GB、冷启动到 SSH、codex 版本、headless-shell
|
||||
例、Gitea 双向同步(boot clone + push + pull + 分叉失败关闭)。
|
||||
- 单元隔离验收(新增,否定测试为主):
|
||||
1. 单元内 `cat /etc/chv/guest-env` 失败(遮蔽 + 0600 overflow uid)。
|
||||
2. 单元内 `cat ~/.git-credentials` 失败;经 broker `sync status` 成功。
|
||||
3. 单元内写 `/usr/local/bin/x` 失败(只读根文件系统)。
|
||||
4. 单元内 `nsenter`/ptrace 客机侧进程失败(pid ns + dropped caps)。
|
||||
5. 含 `io_uring_setup` 的程序在单元内报 EPERM/ENOSYS(syscall 过滤)。
|
||||
6. 单元内 `python3 -c "import playwright"` 失败(已遮蔽);经 broker
|
||||
`browser open https://example.com` 返回标题 Example Domain 与
|
||||
无障碍树快照。
|
||||
7. 单元内 codex 一轮 turn 完成(`danger-full-access` 下,不再套 bwrap)。
|
||||
8. 单元内出站到模型网关通(veth + NAT);`chv-cell` 退出后 NAT 规则已清理。
|
||||
9. broker 拒绝非单元进程的连接(pid ns 不符时拒绝;`cell.pid` 缺失时
|
||||
只做 uid 检查并打日志——该降级须在日志里可见)。
|
||||
- 性能记录(只记录、不设门):单元启动到 codex 就绪耗时、veth+NAT 下
|
||||
模型调用往返、qcow2 实际占用(systemd-container 与新脚本的增量)。
|
||||
@@ -833,3 +833,54 @@
|
||||
UNSTABLE、未合并、未部署、真机 0 项。差:外部为主。
|
||||
- 动态安全 2.0 → 2.0,未达 10:本轨无任何动态验证,
|
||||
V-7 未跑。差:外部(KVM 宿主 + 审计结论)。
|
||||
|
||||
## 15. 对齐 Muse P2 轨 feat-chv-muse-p2(客机 nspawn 单元 + 浏览器子代理)
|
||||
|
||||
本节是准备工作,不是启用结论。本轨只到本地分支,不合并、不部署;
|
||||
生产就绪与动态安全的外部关卡原样未动。完整差距表见
|
||||
docs/chv-muse-p2-gap.md(现状 vs Muse、改了什么、诚实边界、
|
||||
真机待验证清单、与 P1/P3 的接口)。
|
||||
|
||||
### 15.1 本轨量化结果(实测)
|
||||
|
||||
- 新增 `cloud/tests/test_chv_muse_p2.py` 40 项,全过。命令(仓库根目录):
|
||||
`python3 -m unittest cloud.tests.test_chv_muse_p2`
|
||||
- 冒烟(真实 Unix socket 回路,/tmp 临时路径,nobody 身份):
|
||||
broker ping 通、白名单拒绝通、sync 校验通、browser ping 通、
|
||||
evaluate 拒绝、localhost URL 拒绝、broker→browser 转发通;
|
||||
root(uid 0)连 broker 被拒并有日志。见 §15.2。
|
||||
- 本机无 /dev/kvm:镜像 round 11 未构建,真机验证 0 项(待第三棒
|
||||
V-1~V-9 让出 WSL 测试机)。
|
||||
|
||||
### 15.2 本轨交付物
|
||||
|
||||
- `chv-guest/chv-cell.py`:单元启动器。`--private-users=1000:65536`
|
||||
(单元内 root → 客机 chv)、丢 8 项能力(含 CAP_SYS_PTRACE /
|
||||
CAP_NET_ADMIN)、syscall 过滤扣 io_uring 三件套、遮蔽 /etc/chv /
|
||||
~/.git-credentials / playwright、`--network-veth`(10.200.0.0/24
|
||||
+ NAT,nsenter 配单元内网络)、白名单环境透传(GITEA_TOKEN 不进单元)。
|
||||
- `chv-guest/chv-broker.py`:单元外 IPC broker。Unix socket +
|
||||
SO_PEERCRED(uid 范围 + pid ns 对照 cell.pid),白名单 ping /
|
||||
sync / browser;sync 只调 chv-sync(runuser -u chv,list argv)。
|
||||
- `chv-guest/chv-browser.py`:浏览器子代理(用户 chv-browser)。
|
||||
只暴露 open/snapshot/screenshot/close/ping;返回无障碍树快照
|
||||
(截断 200 节点/64KB);evaluate/content/DevTools 一律拒绝;
|
||||
localhost 与私有地址字面量拒绝。
|
||||
- `chv-guest/build.sh`:apt 加 systemd-container/iproute2/iptables;
|
||||
拷入三脚本;建 chv-browser 系统用户;装 chv-broker/chv-browser
|
||||
两个 systemd 单元并 enable;tmpfiles.d 建 /run/chv。P1 的
|
||||
vsock_notify.py 条件拷入(文件不存在时只警告、不失败)。
|
||||
- `chv-guest/buildinfo.py`:内容项加 systemd-container/chv-cell/
|
||||
chv-broker/chv-browser;支持可选第 10 参数 extra_contents。
|
||||
|
||||
### 15.3 诚实边界(本轨不宣称)
|
||||
|
||||
- 无独立根文件系统:单元与客机共享根文件系统(只读 + 遮蔽 +
|
||||
bind),不是 Muse 的独立 Debian 镜像;world-readable 的客机文件
|
||||
单元内仍可读。完整分离要等下一阶段 + 体积重预算。
|
||||
- SO_PEERCRED 的 uid 检查不区分单元 root 与客机 chv(同 uid 1000);
|
||||
主校验是 pid ns 对照;cell.pid 缺失时只做 uid 检查并打日志。
|
||||
- a11y-tree-only 是方向:单元内仍有 curl/Python 标准库可直接发
|
||||
HTTP 请求;完整出口封堵是 P3 Sentinel 的事。
|
||||
- 单元内 codex 用 danger-full-access(单元即沙箱,不再套 bwrap);
|
||||
执行器侧改 `--sandbox` 透传是 P1 的文件,本轨只定义了接口契约。
|
||||
Reference in new issue
Block a user