R12 复测 2 钉死:即使 worker active,chv 客户端也永远连不上 socket——worker root + 目录 /run/chv-gitea 0700 root + socket 0600 root + allowed_uids=(0,) 三处锁死;chv ping 一律 EACCES (rc=2),root ping rc=0,privsep_worker_call 恒为 0,全部同步 走回落。 修法:三处配套改,只改一处仍锁死—— 1. chv-gitea-worker.py:DEFAULT_ALLOWED_UIDS=(0, 1000)(root + chv;round-11 authd 同款;环境变量 CHV_GITEA_WORKER_ALLOWED_UIDS 可覆盖);socket 用 group 权限 (目录 0750、socket 0660、组为第一个非 root 放行 uid 的主组, _socket_group_gid/_set_group_perms 与 authd 同款实现;gid 解析不出时回落 root-only 0700/0600,绝不放给全员)。 拒绝 0666 探针口径。 2. build.sh tmpfiles.d:d /run/chv-gitea 改为 0750 root 1000,与 新方案一致。gid 写数字 1000 而不用组名 chv——tmpfiles-setup 跑在 07:23:24,chv 用户由 cloud-init 在 07:23:40 才建,组名 当时解析不出;1000 是 chv 的 uid/gid(seed users 块未指定 uid/gid,cloud-init 取首个空闲 1000;authd 同款假设)。 worker 启动时再按 passwd 重新推导 gid 并重设权限,tmpfiles 行只是开机存在性保证。审计日志仍 0600 root,不动。 3. 服务单元 ReadWritePaths 不动(/run/chv-gitea 仍在列,存在性 由 tmpfiles 保证)。 注意:单元内 agent 同样映射为 chv uid 1000,故也能走到 socket, 但只能走类型化协议——操作白名单、仓库 confinement、安全扫描三 处仍在,worker 不持凭证(见 worker 模块注释)。 测试:WorkerSocketGroupPermsTest 新增 7 项——默认白名单含 (0,1000)、环境变量覆盖、gid 可推导时 0750/0660 + 组正确、显式 socket_group 时同样组权限、只有 root 时回落 0700/0600、gid 推导 函数四档、tmpfiles 行 0750 root 1000;WorkerUnitTmpfilesTest 的 /run/chv-gitea 期望改 0750。P6/round12/P2/P4 四模块 205 项全过。
16 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(目录 0750、socket 0660、组为 chv 主组, round-12 修复轮第三轮缺口 ②,round-11 authd 同款口径,拒绝 0666);SO_PEERCRED 只放行 root 与 chv(chv-sync 薄客户端以 chv 身份连);操作白名单 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)。round-12 修复轮第三轮 (缺口 ①):加ConditionPathExists=/home/chv/workspace(开机那次 启动在工作区出现前干净跳过、不 226)+ 新单元chv-guest/chv-gitea-worker.path(PathExists=/home/chv/workspace, 工作区一出现即触发启动;两者缺一不可——Condition 本身绝不重试)。 runcmd 只启动 .path 单元,不直接 start 服务;StartLimit 不放宽, tmpfiles.d 不补工作区行(见服务单元注释)。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 强制”。
- 过渡兼容留了旧路径(仍是 fail-open,round-12 修复轮第二轮
项 2 钉死):worker socket 不存在时
chv-syncpull/push/status 仍走旧的直接路径,为的是旧镜像不被一次改断;但回落不再静默—— 每次回落都在 stderr 警告(“worker 不在…privsep 未生效”)+ 追加 一行 JSON 审计(~/.chv-sync-fallback-audit.log)。旧镜像退役 后应删掉该回落,改为无 worker 即 fail-closed;删除前不得声称 “旧路径已关闭”。 - 无真机验证:本轮全部是代码 + 本地单测(含 worker 真 socket 回路 + 假 git runner)。真机待验证清单见 §4。
4. 真机待验证清单(等测试机空闲,排队在 round-11 之后)
- worker 在 lite12 镜像里的启动时序(须在 broker/
chv-sync调用 之前就绪;与 P3 authd 的启动顺序关系)。round-12 修复轮第三轮 (缺口 ①)已改 Condition + .path 触发,待真机复测 3 验证 5 台 VM 0 failed。 - worker 的 systemd 沙箱在真机上生效(
systemctl status、cgroup 路径、受限调用被挡的表现)。 - broker → worker → helper → authd → git 全链路在真机上跑通 pull/push(真 Gitea、真 token 替身)。round-12 修复轮第三轮 (缺口 ②)已改 socket 组权限 + allowed_uids 含 chv,待真机复测 3 验证 chv ping rc=0 且 privsep_worker_call > 0。
- worker 崩溃后 agent 侧只收到类型化失败、任务不炸(杀 worker 进程复现)。
- 安全扫描在真机 git 输出上的误报率抽查(真实 commit 信息样本)。