- 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.
15 KiB
对齐 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 拿不到凭证、直接拒绝。
- authd socket
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 诚实边界(本轮不宣称的东西)
- 本轨不 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 放行)留给真机轨。
- worker 跑在客内 root:worker 守护进程以 root 身份跑在单元外
(与 P3 authd 同款选择),git 子进程再以 chv 身份跑。root worker
本身不受单元隔离保护——它的隔离来自 systemd 沙箱 + socket 白名单
- 不持凭证;客内 root 万能仍是 P2 的边界,不是本轨能修的。
- 网络层强制不是本轨:worker 的出口收紧靠 Sentinel 评估 (authd 放凭证前)+ 只配 helper 的 git + systemd 沙箱;eBPF cgroup 拦截 + LSM 污点传播是 P5 的范围。不得把本轨写成“出口已 全部经 Sentinel 强制”。
- 过渡兼容留了旧路径:worker socket 不存在时
chv-syncpull/push/status 仍走旧的直接路径(只多一句 stderr 警告),为 的是旧镜像不被一次改断。旧镜像退役后应删掉该回落,改为无 worker 即 fail-closed;删除前不得声称“旧路径已关闭”。 - 无真机验证:本轮全部是代码 + 本地单测(含 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 信息样本)。