Files
codex-app-server/docs/chv-muse-p6-gap.md
T
host d4cef29cdb round-12: migrate P8 registries + P12 audit to AppStateStore
- app_state.py: host_state_db_path() (host-level DB, distinct from
  per-VM state_db_path); kv_get/kv_put/kv_delete on meta (namespaced
  keys; kv_put skips the no-secrets guard by design — registry docs
  hold user prompts, documented); audit_trail table (P12 record
  shape + hash chain, seq explicit); counts()/backup() gain
  audit_trail.
- swarm.py / presentation.py / browser_tasks.py: JSON-file backends
  replaced by KV rows (p8-swarm / p8-ledger / p8-browser-tasks);
  legacy JSON imported once on first load (renamed .migrated);
  corrupt row / unreadable store returns empty (fail-safe, same
  semantics as before; next successful save self-heals).
- audit_trail.py: StoreAuditTrail (same record shape, hash chain,
  filters, export as JSONL version, backed by audit_trail table);
  migrate_jsonl_to_store() moves a chain verbatim (broken chain
  refused, divergent seq refused, re-run idempotent). JSONL class
  kept for reading old files.
- P12 dict contracts now run against real P3/P5 code
  (ApprovalQueue.record -> build_approval_card ->
  card_matches_channel; CapabilityIssuer.issue -> describe ->
  PermissionRegistry.record_grant).
- P6->P3 local protocol test: real PrivsepClient + real worker
  server + real git status; worker git argv carries the real P3
  helper + useHttpPath; no token in argv/env/audit. runuser + HTTP
  pull stay real-machine-only (see gap docs).
- Gap docs updated: P8 migration done, guest-side browser follow-up
  explicitly left for the real-machine track (no task_id in the
  browser protocol yet; lifecycle needs a real VM to verify);
  P6 wiring + P12 migration marked done; production-readiness.md
  gains §20 (round-12 integration).
- Tests: new cloud/tests/test_chv_muse_round12.py, 24/24 OK;
  test_chv_muse_p8.py ledger corrupt test rewritten for the KV
  backend (+ legacy-import test). P7+P8: 95+1 tests OK.
2026-11-07 00:36:25 +00:00

164 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 对齐 Muse P6 差距表:privsep worker + 确定性安全检查层
依据:`~/workspace/research_notes/meta-muse-ai-architecture-20261101-0755/report.md`
(官方以 Meta security.muse.ai 2026-09-08 为准;第三方拆解只作参考,
逆向细节不得写成官方架构)。Muse 模型相关句(官方):
- 内置连接器的执行分离:单元内的连接器 CLI 只解析参数、打开调用者
已有权访问的文件、通过 Unix socket 传递类型化参数与文件描述符;
真正业务逻辑由 systemd 沙箱化的 privsep worker 执行;每个 worker
由 cgroup 标识、有明确凭证白名单(日历 worker 不能仅改参数就索取
邮件凭证)。
- 纵深防御分层:模型层训练识别提示注入;Harness 层外部数据进入模型
上下文时标注为“不受信任输入”;独立分类器层多组提示注入检测分类器
并行运行,覆盖经文件与工具调用进入模型上下文的全部外部数据;
确定性边界层(运行时单元限制系统访问、privsep 限制哪些代码可见
哪些凭证、authd 执行 ACL、Sentinel 评估每个动作与全部网络出口)。
- hatch-safety:独立模型与分类器检查进出核心模型推理的请求/响应。
本轨范围只限 P6:privsep worker(先做 Gitea 同步一个连接器)+
确定性安全检查层(标注 + 审计 + 规则版注入检测)。不碰 P1(常驻 VM、
气球、vsock)、不碰 P2(nspawn 单元、broker、浏览器子代理)、不碰
P3(authd/sentinel 本体——只读、不重写、不改接口)、不碰 P4(浏览器
完全对齐)、不碰 P5(Sentinel 纵深、eBPF 污点)。分支
`feat-chv-muse-p6`(从 `feat-chv-cover100` 分出,基线 HEAD `06c3e17`)。
## 1. 现状 vs Muse
| 项 | 现状(实测/代码为准) | Muse 目标 | 本轨做法 |
|---|---|---|---|
| 连接器执行位置 | Gitea 同步逻辑直接跑在 agent 可达的进程里:宿主执行器经 SSH 调客内 `chv-sync`(bash),P2 的 broker 再以 chv 身份直跑 `chv-sync` 子进程。`chv-sync` 一个进程里既解析参数、又读凭证、又跑 git、又碰网络——没有执行分离 | 单元内 CLI 只解析参数、传类型化参数与文件描述符;真正业务逻辑由 systemd 沙箱化的 privsep worker 执行;每个 worker 由 cgroup 标识 | 客内新增 `chv-gitea-worker` 守护进程(systemd 单元,见 §2):Gitea 同步的 git 业务逻辑(clone/pull/push/status 的 git 调用)只在 worker 里跑;agent 侧(broker/`chv-sync`)只剩薄 CLI——解析参数、发类型化 JSON 请求、走 Unix socket、拿类型化响应。worker 崩溃/超时只返回类型化失败,不拖垮 agent(失败分类与审计) |
| worker 身份与凭证白名单 | 无 worker 概念,无白名单。`chv-sync` 直接读落盘的 `.git-credentials`(旧路径)或走 P3 helper(新路径),调用方改参数就能让它对任意仓库跑 git | 每个 worker 由 cgroup 标识、有明确凭证白名单(改参数也索取不到别的凭证) | worker 注册表只认一个 worker:`gitea`,凭证白名单只含 `GITEA_TOKEN`,操作只含 pull/push/status/ping,仓库只限 `~/workspace` 下。白名单三处 enforce:① 客户端(`PrivsepClient`)发请求前校验,越权请求不出门;② worker 服务端再校验一次(不信任客户端);③ 凭证获取本身不经过 worker——git 经 P3 的 credential helper 向 authd 按次取凭证,authd 按 {连接器, 主机, 仓库, 操作} 绑定放行,worker 改参数也拿不到别的凭证。worker 进程本身绝不持有 token 值(argv/env/日志/审计里都没有) |
| worker 的网络出口 | `chv-sync` 的 git 直连 Gitea;出口只靠 Tinyproxy 白名单 + 宿主防火墙(第三棒落地中),无每动作审批 | Sentinel 评估每个动作与全部网络出口 | worker 的 git 只配 P3 helper + 只连配置的 Gitea 远端;每次取凭证前 authd 已先过 Sentinel 评估(P3 机制,本轨不重复造);worker 的 systemd 单元再叠加 `RestrictAddressFamilies` 与网络命名空间收紧。网络层强制(eBPF 拦截)是 P5 的范围,本轨不宣称 |
| 外部数据进模型上下文 | 无标注。Gitea 拉回的提交信息、文件名、diff 内容经 `chv-sync` 输出原样回到 agent 回合,codex 自己拼上下文,我们这边不区分“用户写的”与“远端拉回的” | Harness 层:外部数据进入模型上下文时标注为“不受信任输入” | `cloud/chv/safety.py`:`UntrustedData` 携带来源标记(`source`,如 `gitea:<repo>`)随数据走;worker 的 git 输出在返回前先过扫描 + 打上来源标记;`PrivsepClient` 把标记带回调用方。注意诚实边界:codex 在客内自己拼上下文,我们的标注能保证的是“经我们这条连接器链路回来的数据都带标记”,不是“codex 一定会读标记”——后者须 codex 侧配合,不在本轨 |
| 提示注入检测 | 无 | 独立分类器层:多组提示注入检测分类器并行,覆盖经文件与工具调用进入上下文的全部外部数据 | 确定性规则版 `InjectionScanner`(不是模型分类器,不冒充):指令性文本规则(中英)、命令/URL 注入模式、可疑拼接模式;分级 `block`/`flag`/`allow`。`block` 只给高置信模式(shell 执行链、直接指令覆盖);`flag` 给中置信(指令性文本、可疑 URL),命中即标记复核、不自动执行后续写操作;`allow` 其余。规则覆盖边界单列在 §3.3 |
| 请求/响应审计(hatch-safety) | 执行器 `_emit` 只审 VM 生命周期事件(VM 名/路径/URL/kind,不含 key 值);进出模型推理的请求/响应无审计 | hatch-safety 检查进出核心模型推理的请求/响应 | `safety.audit_inference_request/response`:在执行器派发边界(`_run_guest`,任务 → 客内 codex;结果回)记审计——只记任务 id、kind、内容指纹(SHA-256 前 16 位)、长度、扫描 verdict;密钥与 token 值绝不进审计(先过 `redact_secrets` 再记,审计失败 best-effort、不中断任务,沿用 `_emit` 既定语义) |
| 与 P3 的接线 | P3 已做完(a90cf78):authd 替身凭证 + Sentinel 最小审批 | privsep worker 的凭证只走 authd 替身;worker 出口只走 Sentinel 审批 | worker 的 git 一律配 `-c credential.helper=/usr/local/bin/git-credential-chv-authd -c credential.useHttpPath=true`(P3 的 helper 路径与 P3 修过的 path 坑),不用 `.git-credentials`;helper 缺失时 worker 直接拒绝(fail-closed),不回落读 token。Sentinel 评估发生在 authd 放凭证前(P3 机制),worker 不另造决策器。本轨不 import P3 的模块(基线 06c3e17 里没有 P3 代码):接线走已定的文件/socket 协议,合入时由集成轨接通;本轨测试用替身(fake)实现同一协议 |
## 2. 改了什么(本轨交付物)
- `cloud/chv/privsep.py`(新,叶子模块,标准库):worker 注册表
(本期只 `gitea`)+ 类型化请求/响应(`WorkerRequest` /
`WorkerResponse`,JSON 行,≤64KB)+ `PrivsepClient`(Unix socket
客户端:超时、worker 崩溃 → 类型化失败、响应校验、返回前过安全
扫描)+ 客户端白名单校验(越权请求不出门)+ cgroup 标识读取
(`/proc/<pid>/cgroup`,审计用)。
- `cloud/chv/safety.py`(新,叶子模块,标准库):`UntrustedData`
来源标记 + `InjectionScanner`(规则版,中英指令模式、命令/URL
注入模式、可疑拼接,分级 block/flag/allow)+ `redact_secrets`
(token/key/口令模式脱敏)+ `audit_inference_request/response`
(只记指纹与 verdict,不记值;失败 best-effort)。
- `chv-guest/chv-gitea-worker.py`(新,标准库守护进程):Unix socket
`/run/chv-gitea/worker.sock`(0700 目录、0600 socket);SO_PEERCRED
只放行 root(broker 走 root 连);操作白名单 pull/push/status/
ping;仓库只限 `~/workspace` 下(realpath + commonpath);git 以
chv 身份跑(`runuser -u chv`,文件归属不变),argv 里只配
credential helper、绝无 token;输出先过 `safety` 扫描 + 来源标记
再返回;超时 300 秒;异常只断本连接。
- `chv-guest/chv-gitea-worker.service`(新):systemd 单元(root 运行,
单元外;`ProtectSystem=strict`、`PrivateTmp`、`NoNewPrivileges`、
`CapabilityBoundingSet=` 空、`RestrictAddressFamilies=AF_UNIX AF_INET
AF_INET6`、`SystemCallFilter=@system-service`、读写路径只限
`/home/chv/workspace` 与 `/run/chv-gitea`)。
- `chv-guest/chv-sync-worker-client.py`(新,标准库薄客户端):
`chv-sync pull/push/status` 在 worker socket 存在时改走 worker
(发类型化请求、原样回显 worker 的 stdout/stderr、按 worker 的
returncode 退出);socket 不存在时走旧的直接路径 + stderr 警告
(过渡兼容,见诚实边界第 4 条)。
- `chv-guest/chv-sync.sh`:pull/push/status 三处改走上述客户端
(有 socket 时);boot 不动(cloud-init 时的一次性 clone,仍按
P3 后的 helper 路径走)。
- `cloud/chv/executor.py`:`_run_guest` 前后加推理边界审计钩子
(best-effort,不改结果、不中断任务)。
- `cloud/tests/test_chv_muse_p6.py`(新):见 §3 的覆盖清单。
- 镜像集成(只留接入说明,不改 `chv-guest/build.sh`,与 P2/P3 同款
规矩,合并时由集成轨带进镜像):
`--copy-in chv-guest/chv-gitea-worker.py:/usr/local/bin/`、
`--copy-in chv-guest/chv-sync-worker-client.py:/usr/local/bin/`、
`--copy-in chv-guest/chv-gitea-worker.service:/etc/systemd/system/`、
`--copy-in cloud/chv/privsep.py:/usr/local/lib/chv-gitea/cloud/chv/`、
`--copy-in cloud/chv/safety.py:/usr/local/lib/chv-gitea/cloud/chv/`、
`--copy-in cloud/__init__.py:/usr/local/lib/chv-gitea/cloud/`、
`--copy-in cloud/chv/__init__.py:/usr/local/lib/chv-gitea/cloud/chv/`、
`--run-command 'chmod +x /usr/local/bin/chv-gitea-worker /usr/local/bin/chv-sync-worker-client'`、
`--run-command 'systemctl enable chv-gitea-worker || true'`、
`99-chv-sync.cfg` 的 runcmd 在 `chv-sync boot` 之前加
`systemctl start chv-gitea-worker || true`。
另:worker 依赖 P3 的 helper(`/usr/local/bin/git-credential-chv-authd`
+ authd socket `/run/chv-authd/authd.sock`),P3 的接入说明里的
copy-in 须一并落地,否则 worker 的 git 拿不到凭证、直接拒绝。
## 3. 测试覆盖与诚实边界
### 3.1 测试覆盖(`cloud/tests/test_chv_muse_p6.py`,全覆盖以下每一项)
- privsep 协议:未知操作/未知连接器/仓库越界(`..`、workspace 外)
被拒;响应格式不对被拒;超时与 worker 崩溃(socket 半路关闭)
→ 类型化失败 `WorkerRefusal`(不是裸 socket 异常、不拖垮调用方)。
- 白名单:客户端越权请求(未知操作、未知连接器)不出门;服务端
再拒一次;worker 的 git argv 里只有 helper 配置、绝无 token 值。
- worker 真 socket 回路:pull/push/status/ping 全通(假 git runner,
不跑真 git、不联网);git 输出先扫描 + 打来源标记再返回。
- 安全检查层:每条规则至少一个 block 用例、一个 flag 用例、一个
allow 用例(含中英);真实 git 输出样式(commit 信息、URL、正常
英文)不误报;审计记录里扫不到任何测试密钥值;脱敏覆盖全部密钥
模式;`check_context_input` 的 block 即阻断、flag 即标记复核。
- 推理边界审计:请求/响应审计只含指纹与 verdict;审计回调失败不
中断任务。
### 3.2 架构测试约束
新模块须过 `test_no_chv_function_over_50_lines`(函数 ≤50 行)与
`test_public_api_fully_annotated_and_no_any`(公共 API 全注解、无 Any)。
### 3.3 确定性检查层能拦什么、不能拦什么(单列)
能拦:字面指令覆盖(中英常见写法)、shell 执行链(`curl|sh`、
`$(...)`、反引号命令替换、`;` 拼接危险命令)、可疑 URL(非 https、
IP 字面量、userinfo 含口令、`data:`/`javascript:`)、把指令藏在
分隔线后的可疑拼接。不能拦:改写过的指令(同义词替换、拆字、编码、
Base64、零宽字符)、藏在图片/附件/二进制里的指令、需要理解上下文
才能判定的社会工程式诱导、模型自身的幻觉与误读——这些要模型分类器
(hatch-safety 同款)才有机会拦,规则版拦不住,不得宣称已覆盖。
规则是确定性的:同一输入永远同一 verdict,可复现、可测试;误报率
靠 allow 用例钉住,新增规则须先补用例。
### 3.4 诚实边界(本轮不宣称的东西)
1. **本轨不 import P3 的模块**:基线 06c3e17 里没有 P3 的 authd/
sentinel 代码,本轨的接线走文件/socket 协议(helper 路径、
authd socket 路径、Sentinel 评估发生在 authd 放凭证前)。本轨
测试用替身实现同一协议;与 P3 代码的真正接通发生在合入时,
须由集成轨跑通 worker→helper→authd→git 的全链路。
**已接通(round-12,本地部分)**:`cloud/tests/
test_chv_muse_round12.py`(WorkerProtocolTest)用真
PrivsepClient + 真 worker server + 真 git status 跑通,worker
的 git argv 含真实 P3 helper 与 useHttpPath、argv/env/审计
均不含 token;runuser 包装与 HTTP 拉取(需真实 gitea.tose.top
+ Sentinel 放行)留给真机轨。
2. **worker 跑在客内 root**:worker 守护进程以 root 身份跑在单元外
(与 P3 authd 同款选择),git 子进程再以 chv 身份跑。root worker
本身不受单元隔离保护——它的隔离来自 systemd 沙箱 + socket 白名单
+ 不持凭证;客内 root 万能仍是 P2 的边界,不是本轨能修的。
3. **网络层强制不是本轨**:worker 的出口收紧靠 Sentinel 评估
(authd 放凭证前)+ 只配 helper 的 git + systemd 沙箱;eBPF
cgroup 拦截 + LSM 污点传播是 P5 的范围。不得把本轨写成“出口已
全部经 Sentinel 强制”。
4. **过渡兼容留了旧路径**:worker socket 不存在时 `chv-sync`
pull/push/status 仍走旧的直接路径(只多一句 stderr 警告),为
的是旧镜像不被一次改断。旧镜像退役后应删掉该回落,改为无 worker
即 fail-closed;删除前不得声称“旧路径已关闭”。
5. **无真机验证**:本轮全部是代码 + 本地单测(含 worker 真 socket
回路 + 假 git runner)。真机待验证清单见 §4。
## 4. 真机待验证清单(等测试机空闲,排队在 round-11 之后)
- worker 在 lite12 镜像里的启动时序(须在 broker/`chv-sync` 调用
之前就绪;与 P3 authd 的启动顺序关系)。
- worker 的 systemd 沙箱在真机上生效(`systemctl status`、cgroup
路径、受限调用被挡的表现)。
- broker → worker → helper → authd → git 全链路在真机上跑通
pull/push(真 Gitea、真 token 替身)。
- worker 崩溃后 agent 侧只收到类型化失败、任务不炸(杀 worker
进程复现)。
- 安全扫描在真机 git 输出上的误报率抽查(真实 commit 信息样本)。