Files
codex-app-server/docs/chv-production-readiness.md
T
host baf26beb78 feat(chv): 安全审计修复 High/Medium/Low(堆叠于 #239)
审计报告 ~/workspace/chv-security-audit/REPORT.md(No-Go,
Critical 0 / High 7 / Medium 8 / Low 5)代码层修复,mock
测试覆盖,真机动态验证一律未做、只列清单(见
docs/chv-production-readiness.md §11)。

High:
- H1 egress 代码门:代理地址校验、直连显式允许+审计、
  每 VM 独立 tap/mac/ip 校验、net 字段白名单;宿主防火墙
  列为部署清单,代码门+防火墙缺一不可。
- H2 网关 key 移出 argv:provisioned seed 盘客内文件
  /etc/chv/guest-env(0600)投递,传输层密钥进 argv 直接
  ValueError;keyscan 模式密钥投递 fail-closed。
- H3 任务盘删除:stop/recycle/cleanup 删 disk.raw(保留期
  0)+ 残留盘 GC,删失败审计+脏标记。
- H4 默认值改 provisioned:keyscan 只作显式 opt-in。
- H5 seed.iso 生成即 0600;宿主私钥 seed 盘建好后即删;
  崩溃残留扫描 cleanup_stale_secrets。
- H6 state_dir 默认出 /tmp(~/.local/share/.../chv-vms,
  preflight 保 0700);api-socket 组/其它可写即 fail-fast。
- H7 tap 隔离校验:net.json 登记+stop 释放,共享即拒绝审计。
Medium:M1 shlex 远程转义;M2 清理失败即拒起(脏标记);
M3 配额默认 50GB + 快照数/总量/保留期上限;M4 SHA256 必填
+ BUILDINFO versions 实测记录(事前 lockfile 列清单);
M5 host key/ preflight/失败审计补齐;M6 reuse 须声明单租户
项目;M7 容量计数只认连通 socket;M8 sshd 加固进 build.sh
断言。Low:L1 链接不跟随;L2 路径白名单;L3 失败审计;
L4 双变量警告+字段白名单;L5 预热盘/任务盘对应关系定义。
旧测试:实验室默认值与生产默认值区分标注;行为已变的旧
测试按新语义更新(容量死 socket、快照白名单、磁盘路径
白名单、网关 argv、reuse 租户声明)。
测试:新 test_chv_secfix 39 项;旧 117 项 + 2 项新增断言 =
158 项;worker 相关 129 项;前端 vitest 9 项;cloud 全量
1588 项(skipped 4)。
2026-10-31 03:02:10 +00:00

428 lines
26 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.
# Cloud Hypervisor 执行器生产就绪清单
> 状态图例:✅ 代码已完成 · 🧪 mock 测试已覆盖(无真 VM) ·
> ⏳ 需 KVM 宿主实测 · 🏗️ 需镜像构建 · 📈 需压测 · 🔒 需安全审计
>
> 本清单是 PR 的一部分,不是事后补的口号。凡是标 ⏳/🏗️/📈/🔒
> 的项,在本轨内一律不声称已验证、已建成、已通过。
## 0. 交付点卡在哪一关(必读,先于逐项清单)
- **CI 门禁(未过,交付点 = PR OPEN)**:CI 必须通过才能合并。
此前全仓 CI 因 GitHub 账单问题 0 步骤失败;若账单未恢复、
CI 仍失败,本 PR 只到 OPEN,不合并,更不为变绿改 CI 配置
或绕过检查。
- **真 VM 门禁(未过,执行器默认关闭)**:生产启用(把真实任务
路由到 cloud-hypervisor、部署到生产主机)必须先有带 /dev/kvm
的宿主并完成真实 VM 启动、快照恢复、任务端到端验证。
当前事实:生产主机 124.222.165.170 本身就是 CVM
(systemd-detect-virt=kvm,DMI product=CVM),/dev/kvm 不存在、
无 vmx/svm 标志、无 kvm 模块、未装 cloud-hypervisor,无嵌套
虚拟化透传;本 VM 容器内同样无 /dev/kvm。
故真 VM、快照恢复、压测在本轨内一律不声称已验证;执行器
默认关闭(server 默认仍是 docker,worker 默认仍是 local,
CHV 只在任务创建时显式选 `executor=cloud-hypervisor` 且有
CHV worker 认领时才跑),不进入生产任务路由。
- **还差什么才能真正上生产**(按顺序):① 用户侧开通带 /dev/kvm
的裸金属或开启嵌套虚拟化的实例;② 在该宿主上构建并发布
chv-guest 镜像(见 §4);③ 在该宿主上跑通真实 VM 启动、
快照恢复、任务端到端、预热池与压测;④ 安全审计通过(§6);
⑤ CI 账单恢复且本 PR CI 变绿后,按当时 main 头复核 diff 再合并、
再部署。缺任何一项,都不把 CHV 推上生产。
## 1. 单任务单 VM 隔离
- ✅🧪 一个任务一个 VM:dispatch/run/goal 均走 wake → 跑 → finally
shutdown,同一任务重复 wake 先关旧 VM;VM 名、api-socket、
每任务磁盘路径均按任务派生,互不相同(测试断言)。
- ✅🧪 每任务磁盘独立:copy 模式复制基础镜像为每任务磁盘;
reuse 模式仅单租户实验,preflight 拦截(须显式实验二次确认并审计放行,feat-chv-fix 起代码真正拦截;此前“代码与文档双写”的说法是错的,代码当时并不拦截)。
- ✅🧪 租户无共享路径:执行器不挂载宿主根、宿主代码目录或含
密钥目录;客机代码在镜像内 `/app`,不在宿主共享挂载上。
- ✅🧪 密钥不进镜像/代码/prompt:SSH argv 只有 key 文件路径;
模型 key 由客机 cloud-init 预置的环境变量名注入;测试断言
命令中不含哨兵密钥值。feat-chv-secfix 起网关密钥不再走
SSH env 前缀(旧路径值进 argv 已废除),改走 provisioned
seed 盘客内文件(/etc/chv/guest-env,0600),keyscan 模式
下密钥投递 fail-closed;审计 detail 里绝无密钥值。
- ✅🧪 SSH host key 按 VM 钉死(优化轨,#216 的 no + /dev/null
已废除):每 VM 在 state_dir/<vm>/known_hosts 有独立文件,
首次启动经 ssh-keyscan 在解析出的 SSH 目标上捕获 key 写入该文件,
此后 SSH 一律 StrictHostKeyChecking=yes + 该文件校验;
非 root 客机用户(CHV_SSH_USER,显式空串直接拒绝)与端口
(CHV_SSH_PORT,1-65535)为显式配置。mock 只验证捕获→
写文件→钉死选项的管线,不验证首次密钥真实性。
- ✅🧪 Host key 预置方案(补缺口轨,替代 TOFU 为生产路径):
宿主在启动前为每 VM 生成一次性 ed25519 host key 对,
公钥立即写进该 VM 的 known_hosts 钉死,私钥只进 per-VM
cloud-init seed(随 VM 关闭/回收而删),客机镜像硬性
要求见 chv-guest/README.md §5(cloud-init NoCloud、
首启先应用 seed 再起 sshd、不得无条件重生成 host key)。
ssh-keyscan 首次捕获只保留为弱回落(须显式设
CHV_HOST_KEY_MODE=keyscan),生产默认值已是 provisioned
(feat-chv-secfix,H4,此前默认值 keyscan 是反的);TOFU 首次
真实性仍待预置 key 方案在真机上验证,安全审计时须单独立项确认。
- 🔒 需安全审计:VM 逃逸面(virtio 设备、tap 桥接、SSH 配置)、
客机 root 提权面、租户间东西向隔离的宿主网络配置,须由
安全审计逐项确认,代码自查不能代替审计。
## 2. 快速启动:预热快照池 + 镜像缓存
- ✅🧪 快照/恢复接口:snapshot_vm / restore_vm(restore 后显式
resume,见 §3),WarmPool 接口与状态机(cold → prewarming →
warm → assigned → running → recycled/destroyed)。
- ✅🧪 冷启动回落:池空时 acquire 返回 cold,调用方回落 wake_vm
冷启动,绝不降级 Docker/Local。
- ✅🧪 预热条目一次性:acquire 消耗、release 一律销毁,绝不跨
任务复用脏 VM。
- ✅🧪 预热池 registry 与孤儿清理(优化轨):池状态落
state_dir/warm-pool.json(原子写),worker 重启后载入的
条目一律当孤儿、只能 reconcile_orphans() 清理,绝不重新
发给任务;registry 损坏时 quarantine 成 .corrupt 并从空池
开始;registry 之外的残留 socket 仍由 cleanup_stale_vms()
扫描处理。
- ⏳ 需 KVM 宿主实测:预热池的真实冷启动耗时、resume 耗时、
池大小与命中率、快照来源一致性,均需真机数据,mock 数字
不能当性能结论。
- 📈 需压测:多 VM 并行预热/分配/销毁的宿主 CPU/内存/磁盘
压力、快照存储增长,均未测。
## 3. 完整生命周期
- ✅🧪 create / boot:API 模式 wake_vm(vmm.ping → vm.create →
vm.boot → SSH 就绪)。
- ✅🧪 pause-resume(sleep-wake):pause_vm / resume_vm(ch-remote)。
- ✅🧪 snapshot-restore:restore_vm(restore + 显式 resume +
SSH 就绪),恢复后默认 paused 的坑已在代码与测试里钉住。
- ✅🧪 hibernate:CHV 无原生 hibernate 端点,本轨用 pause +
snapshot + shutdown 等价实现,文档与代码双写“等价实现”,
不声称接了原生端点;唤醒走 restore_vm。
- ✅🧪 shutdown-destroy / recycle:shutdown_vm(ch-remote
shutdown + 杀进程 + 删 socket)、recycle_vm(保留
task_checkpoints/task_attempts,丢失 guest /tmp 与未拷出
的 worktree 临时物,绝不声称恢复临时文件)。
feat-chv-secfix 起 stop 即删任务 disk.raw(保留期 0,
需取证须在 shutdown 前经 SSH 拷出),残留盘由
gc_stale_disks 扫描清理,删失败记审计 + 脏标记。
- ✅🧪 execute 只读已运行 VM(优化轨):execute() 先查 VM
是否在运行(登记活进程或 api-socket 存在),未运行报
ChvFailure(kind=vm_not_running),不为一次 status 起 VM,
也不向不存在的端口发 SSH 再拿连接失败冒充任务状态。
- ✅🧪 长任务中途死亡判 interrupted(优化轨):客内命令抛
传输异常、或非 goal 结果已 failed 且登记 VMM 进程已死时,
_run_with_vm 改判 interrupted(断点可续跑)而非笼统
failed;VM 还活着时的异常/失败原样处理。中途心跳未做:
host.py 单次 SSH 长回合没有客内进度信号可采,另起一路
SSH 轮询只能证明 VM 活着、不能证明任务在进展,评估后
认为风险大于收益,宁可只做死亡分类。
- ⏳ 需 KVM 宿主实测:上述全部生命周期在真机上的状态转换、
快照恢复后 guest 状态一致性、休眠后文件/worktree 保留,
均未在真机验证。
## 4. 完整开发环境(客机镜像)
- ✅ 客机镜像内容清单:`chv-guest/README.md` 按
`cloud/docker/Dockerfile` 能力对齐(Python 3.12、git、
pandoc/poppler/latex、python-docx/openpyxl/pptx/PyMuPDF/
Pillow、codex 0.164.0-alpha.9 成套、Playwright + Chromium、
openssh-server、cloud-init)。
- ✅ 构建脚本实做(补缺口轨):`chv-guest/build.sh`(参数校验、
KVM 宿主检查、下载 Ubuntu 云镜像、qemu-img 转 raw、
virt-customize 装清单、拷入 /app 代码、virt-get-kernel、
BUILDINFO.json 生成与 tag 校验;--dry-run 只打印命令)。
- 🏗️ 需镜像构建:镜像本身未构建、未发布;BUILDINFO 版本核对、
浏览器版本钉死、中文 LaTeX 单独一版,均待构建时做。
- ⏳ 需 KVM 宿主实测:镜像在真 VM 里启动、codex/Playwright/
git/文档工具链逐项跑通,均未验证。
## 5. 资源与成本
- ✅🧪 每 VM 配额:CPU/内存进 VmConfig(CHV_CPU/CHV_MEM),
磁盘配额门(CHV_DISK_GB,默认 50 GB 总量预算、超限拒绝,
feat-chv-secfix 起默认开启,此前未设即不查),快照上限
(每 VM 数量默认 10、总量默认 20 GB、保留期默认 30 天,
超量最旧优先剪枝),宿主容量门
(CHV_MAX_VMS,超限明确拒绝排队,不降级;残留 socket
须真连通才计数,死 socket 不再占满容量)。
- ✅ 超时与最大轮次:SSH 单次超时(CHV_TASK_TIMEOUT)+
worker task_timeout 外层护栏;goal 最大轮次 100(与 Local
同款),执行器内部不重试(重试是 worker max_retries 语义)。
- ✅ 成本复用:不另起一套计费;worker 现有 quota/成本语义
(per-user USD 配额、quota 看门狗、成本写库)仍是唯一口径,
执行器只把 host.py 用量原样返回。
- ✅ 空闲休眠省成本:hibernate_vm(pause + snapshot +
shutdown)+ WarmPool 释放即销毁,空闲 VM 不占 vCPU/内存
(快照只占磁盘,见 §2 压测项)。
- ✅ autoscaler:按 executor 过滤队列深度(已有)+ 本轨透传
`--executor` 给拉起的 worker(否则 CHV 池永远饿死)。
- 📈 需压测:配额下的真实资源占用、超限拒绝的排队行为、
成本与配额在长任务下的实际表现,均未测。
## 6. 网络与治理
- ✅🧪 受控 egress:默认 fail-closed(CHV_EGRESS_PROXY 未设且
未显式允许直连时 preflight 直接失败);客机命令加 env
代理前缀,出口只走白名单代理(Tinyproxy 思路复用)。
feat-chv-secfix 措辞纠正:env 前缀只是代码门,不是网络层
隔离——客内 root 可 unset/裸 socket 绕过;真正的隔离是
“代码门 + 宿主防火墙缺一不可”(见 §11 部署清单),
代码不再把 fail-closed 写成已隔离。
- ⏳🔒 需宿主网络配置 + 安全审计:tap 桥接是否真的只路由到
白名单代理、VM 能否绕过代理直连外网,是宿主网络配置问题,
执行器代码不能自证,必须在 KVM 宿主上实测并审计。
代码已做:每 VM 独立 tap/mac/ip 校验(共享即拒绝并审计)、
net 条目字段白名单、egress 代理地址校验。
- ✅ 审批门:任务 approval 经 `_approval_cli_args` 进客内 host.py
顶层参数,VM 内 agent 不能自改;delete/deploy/spend 类动作
仍走 server 的 approval/merge_gate,不由 VM 内 agent 自批
(本轨不改 approval/merge_gate 语义,只复用)。
- ✅ 工具与系统调用范围收敛:VM 边界本身就是系统调用边界
(客机独立内核);plan 任务走 read-only 沙箱参数;最大步数
(goal 100 轮)、时间(task_timeout)、重试(worker 1 次)
超限即停并审计(vm_failed 带 kind)。
## 7. 可观测与审计
- ✅🧪 VM 事件审计:create/boot/ready/pause/resume/snapshot/
restore/shutdown/destroy/failed/recycled/warm_pool_acquire/
release 均经 audit_callback 发出;worker 接线后转 AuthStore
审计(_audit 本身 best-effort)。feat-chv-secfix 起补齐:
host_key_generated/host_key_captured/host_key_mismatch、
preflight_failed、wake 前置失败(容量/配额/net 冲突)、
vm_disk_deleted/vm_disk_delete_failed/vm_disk_gc、
host_secret_residue_cleaned、net_config_dual、快照剪枝;
审计回调失败记宿主 error 日志,不再双重静默。
- ✅🧪 失败分类:ChvFailure 带 kind(kvm_missing/binary_missing/
kernel_missing/image_missing/ssh_key_missing/cpu_flag_missing/
egress_not_configured/api_timeout/guest_not_ready/vm_create_failed/
vm_boot_failed/host_capacity_exceeded/disk_quota_exceeded),
不笼统 failed。
- ✅ 断点对齐(补缺口轨已接线):VM 快照/休眠与现有
task_checkpoints/task_attempts 语义对齐(recycle 保留两者,
丢失临时文件);worker 通用分支对任何提供
goal_resume_breakpoint_task 的执行器调用它(Local 仍走
原路径,网关任务仍被 loud fail 守卫拦住);WarmPool
reconcile_orphans 已由 worker 启动与关闭时显式调用,
失败不炸主流程但进审计;VM 中途死亡改判 interrupted
后走同一断点路径,不再落 failed 死信(见 §3 与新增测试)。
- ⏳ 需 KVM 宿主实测:审计事件在真机任务 timeline 里的完整
呈现、失败分类与真实故障的对应关系,均未验证。
## 8. 多 VM 并发与容量
- ✅🧪 worker 注册:`_executor_name()` 返回 cloud-hypervisor,
worker register/heartbeat 已上报 executor + capacity(现有
机制复用,无需新表)。
- ✅🧪 多 VM 并行:`_vms` 登记 + 锁保护,wake 按任务名登记,
不同任务互不覆盖;容量门在 wake 前检查。
- ✅🧪 残留 VM 清理:cleanup_stale_vms 处理已死/已关/socket
残留三种情况,只清 chv- 前缀 socket;feat-chv-secfix 起
清理前先跑残留盘 GC 与残留密钥扫描(不依赖 socket 是否
存在,覆盖 worker 崩溃后 socket 已删、文件仍在的情形)。
- ✅🧪 容量不足明确拒绝:kind=host_capacity_exceeded,调用方
排队/拒绝,绝不降级 Docker/Local(测试断言 launcher 未调用)。
- 📈 需压测:多 VM 并行下的宿主容量、VM 启动并发上限、残留
清理在高压下的表现,均未测。
## 9. 默认关闭与生产启用条件(重申)
- ✅ 默认关闭:server 默认执行器仍是 docker,worker 默认 local;
CHV 只在显式选 executor 且有 CHV worker 时才跑;无 CHV_*
配置时 preflight 直接失败。
- ⏳ 生产启用前置(全部未完成):带 /dev/kvm 的宿主开通 →
chv-guest 镜像构建发布 → 真实 VM 启动/快照恢复/任务端到端
验证 → 压测 → 安全审计 → CI 变绿后按当时 main 头复核 diff
再合并部署。缺一不可。
## 10. 审核修复轨 feat-chv-fix(堆叠于 #230 之上)
- ✅🧪 P0 已修:seed 磁盘(CIDATA ISO,第二块只读盘)挂进
VmConfig;restore 不回落 keyscan(provisioned 下复用已钉
死 known_hosts、缺失 fail-fast,快照内 host key 不重预置);
reuse 由 preflight 拦截(显式实验二次确认 + 审计放行);
SSH 目标选客机 IP 方案(显式 host > net 静态 ip > 显式
转发宣称下的回环,三者皆无 fail-fast;vsock 与纯 NAT 方案
未选,理由见 config.py)。
- ✅🧪 P1 已修或有结论:网关 extra_env 经 SSH env 前缀注入,
worker 守卫收窄到 Local/CHV;取消/清理/看门狗接 CHV 通用
路径;WarmPool acquire 内 resume(但 worker dispatch 前查
池仍未接线,预热收益仍不是已交付特性);容量门改持久化
计数(socket 联合计数);磁盘配额改总量+余量、已存在磁盘
校验重拷;API 客户端按 Content-Length 验完整性;快照
file:// 目标验存在且非空再 shutdown;execute 死端口归类
vm_not_running。
- ⚠️ 仍未接线/未验证(不得声称已交付):worker dispatch 前查
WarmPool;CHV + 网关真实任务;真机 wake/boot/SSH(含 seed
注入与 NAT/客机 IP 实测);快照恢复端到端;压测;安全审计;
CI 账单恢复。另:P0-2 审计原说法“restore 须把 seed 接入
VmConfig”已纠正——restore 不走 vm.create,seed 无从挂盘,
正确不变量是复用钉死 known_hosts。
- ✅🧪 P2 已做(范围已恢复为全做,上一条收窄令作废):
test_chv_gaps.py 的 grep 文本断言改成真实 Worker._run_attempt
行为断言;stop_vm 删除失败记 vm_key_cleanup_failed 审计
(同名复用旧 key 的彻底根治在本轨之前列为遗留——
feat-chv-secfix 起已根治:清理失败写脏标记,同名 VM
拒绝启动,见 §11 M2);配置校验时机统一到 preflight(CPU/内存非法
进 preflight,kind=config_invalid);build.sh 与 README 口径
对齐(章节顺序 §5 host key → §6 对接、BUILDINFO contents
纠正为名字列表无逐项版本、Node.js 不装写实);第二启动路径
清理(build_vm_command 降级为私有 _build_vm_command,手动
等价写法保留;Docker goal 内联解析顶部加指向正典
parse_goal_result 的分叉说明 + 同步检查清单);长函数只做
小提取(preflight 的 reuse/CPU/内存校验块抽成
_preflight_config_checks,语义不变;config.py 字段解析与
其余长函数深耦合,本轨如实停、不硬拆)。
## 11. 安全审计修复轨 feat-chv-secfix(堆叠于 #239 之上)
> 审计报告:`~/workspace/chv-security-audit/REPORT.md`(只读静态
> 审计,verdict No-Go,Critical 0 / High 7 / Medium 8 / Low 5)。
> 本节逐项给结论:代码已做 = 本轨代码 + mock 测试已覆盖;
> 真机未验证 = 代码改了,但动态实效仍须 KVM 宿主验证,
> 绝不把静态通过写成动态已验。审计 verdict 本身仍是 No-Go,
> 直到 §11 末的真机清单全部通过——本轨只修代码层,不改 verdict。
### 11.1 High(全部代码已做、真机未验证)
- H1 egress 网络层强制:代码能做的全部做了——egress 代理
地址校验(http/https + 主机名)、直连仍须显式允许 + 审计、
每 VM 独立 tap/mac/ip 校验(共享即拒绝并审计)、net 条目
字段白名单。代码做不到的宿主防火墙规则见 §11.4 部署清单,
文档明写“代码门 + 宿主防火墙缺一不可”;env 前缀不再被
写成已隔离。
- H2 网关 key 移出 argv:密钥子集(SUB2API_API_KEY)在
provisioned 模式下随 seed 写进客内 /etc/chv/guest-env
(0600 root),远程命令 source 该文件;传输层对密钥名
进 argv 直接 ValueError(fail-closed)。比 argv 安全的理由:
值既不在宿主进程表(ps /proc cmdline)里,也不在宿主子
进程环境(/proc environ)里,只活在 seed.iso(0600,随
VM 销毁而删)与客机内——客内 agent 本来就有该 key,修
的是“宿主暴露面”,不是“客内可见性”。与 Docker 线的差异:
Docker 是“-e 只传名、值由 CLI 子进程环境提供”(值仍在
宿主进程环境里),CHV 是“值只进 seed 客内文件”(宿主
进程环境里也没有),比 Docker 线更严。keyscan 模式无
seed 可投递,有密钥即 fail-closed(secret_env_requires_
provisioned),不再将就跑。残留清理:seed 目录与 seed.iso
随 stop 而删,崩溃残留由 cleanup_stale_secrets 扫描。
- H3 任务盘删除:stop/recycle/cleanup 路径删 disk.raw
(保留期 0,需取证须在 shutdown 前经 SSH 拷出),删失败
记 vm_disk_delete_failed 审计 + 脏标记;gc_stale_disks
扫描非 live VM 的残留盘并审计。
- H4 默认值改 provisioned:CHV_HOST_KEY_MODE 默认
provisioned,keyscan 只作显式 opt-in;旧测试的
CHV_SSH_FORWARD=1 等实验室宣称已明确标注为实验室路径,
不再写成生产默认。
- H5 seed.iso 权限与私钥清理:seed.iso 生成即 0600;宿主
私钥在 seed 盘建好后即删(只留公钥 + known_hosts),删
失败 fail-fast(VM 尚未启动);私钥 chmod 失败不再静默;
启动/清理时 cleanup_stale_secrets 扫描残留私钥与 seed。
- H6 state_dir 出 /tmp:默认
~/.local/share/codex-app-server/chv-vms,preflight 建目录
并保证 0700(失败即 state_dir_insecure);api-socket 组/
其它可写即 fail-fast(socket_permissions_insecure;仅禁
写位,VMM 默认 0755 类权限不误杀——VMM 默认值代码控制
不了,真机权限仍须按清单核对)。
- H7 tap 隔离校验:NET_JSON 透传前校验 tap/mac/ip 跨 VM
不重复(wake 成功后登记 net.json,stop 时释放预约),
共用即拒绝并审计;东西向隔离的宿主防火墙部分见 §11.4,
不写进代码假装已防。
### 11.2 Medium(每项有修复或有理由的结论)
- M1 SSH 远程命令转义:远程 token 全部 shlex.quote 后仍以
列表返回(ssh 拼接后远程解析结果与本地 list 语义一致),
prompt 里的 ;/$()/引号不再改写执行语义。边界写清:客内
已是 root,本项防的是语义篡改与审计失真,不是客内提权。
- M2 清理失败同名复用旧 key:彻底根治,选“清理失败即拒起”
(脏标记 + provision/wake 双重拒绝),不再写“已审计即可”。
- M3 磁盘配额默认与快照上限:CHV_DISK_GB 默认 50 GB
(未设不再不查,0/负数拒绝);快照每 VM 数量默认 10、
总量默认 20 GB(超帽最旧优先剪枝)、保留期默认 30 天。
- M4 供应链版本钉死:已钉死——云镜像 SHA256 必填(干跑验
“已设”、全量验哈希)、Ubuntu 版本与 codex 版本均为必填
环境变量;BUILDINFO 新增 versions(构建时从客机读回的
dpkg + pip freeze 实际版本,可审计、可复现比对)。未钉死
的清单:apt/pip 事前精确版本 lockfile(须在构建宿主上
生成一次、再固化进脚本,本轨无构建宿主、未编造版本号);
Playwright Chromium 二进制版本同样只随 playwright pip
版本记录,未单独钉死。
- M5 审计补齐:host_key_generated/host_key_captured/
host_key_mismatch(stderr 含钉死拒绝标记时立即 fail-fast,
不轮询成普通超时)、preflight_failed、wake 前置失败
(容量/配额/net 冲突)均独立审计;审计回调失败记宿主
error 日志,不再双重静默。worker 层 _audit 本身仍
best-effort(未改,见 §7 既定语义)。
- M6 reuse 实验标志单租户校验:代码能校验的已校验——
实验放行须同时声明 CHV_SINGLE_TENANT_PROJECT,否则
preflight 拒绝(reuse_tenant_undeclared);不能校验的
部分明写:代码无法验证数据库里是否真的只有一个租户,
声明 + 审计就是本项的机制,不假装已校验。
- M7 容量计数残留 socket 误伤:只有真能连通的 socket 才
计数,空文件/死 socket 不计入 CHV_MAX_VMS;竞态窗口
写清(刚启动未 listen 的 VMM 会被判死,容量门是护栏
不是分布式锁)。
- M8 sshd 加固进构建:build.sh 写入
/etc/ssh/sshd_config.d/99-chv.conf(PasswordAuthentication no、
PermitRootLogin prohibit-password、ChallengeResponseAuthentication no、
KbdInteractiveAuthentication no、PubkeyAuthentication yes),
并经 sshd -t + grep 断言;README 声称与构建一致。
### 11.3 Low(顺手做)
- L1 磁盘拷贝符号链接:目标是链接时先删链接本身再拷贝,
不跟随写入目标文件(TOCTOU 窗口已在 disk.py 写清,
state_dir 0700 是前提)。
- L2 磁盘/快照路径白名单:task_disk/seed_disk 须在
state_dir 内(reuse 的基础镜像本身除外);file:// 快照
须在 state_dir 或 CHV_SNAPSHOT_DIR 内,远端协议须显式
CHV_ALLOW_REMOTE_SNAPSHOT=1。
- L3 失败审计:preflight/容量/配额/net 冲突失败均发
vm_failed 审计(见 M5)。
- L4 CHV_NET 与 CHV_NET_JSON 双变量:双设时发
net_config_dual 警告审计(不硬拒,CHV_NET 是手动直接
启动命令的透传值);NET_JSON 条目字段白名单
(tap/mac/ip/mask),未知字段直接拒绝。
- L5 WarmPool 预热盘与任务盘对应关系:定义已写进
pool.acquire 文档——预热盘是干净快照盘,不是任务盘;
将来接线时任务盘仍须按任务重新供给或校验快照来源与
任务的对应关系;接线本身仍未做,不假装已接。
### 11.4 宿主防火墙部署清单(代码做不到的部分,缺一不可)
- 每 VM 独立 tap + 独立 mac + 独立静态 ip(运维分配,
代码在 wake 时校验冲突,但分配本身是运维责任);
- tap 桥接默认 DROP,只放行到 egress 代理的流量
(iptables/nftables:默认 DROP + 仅代理地址/端口 ACCEPT,
不给 tap 直连外网的 NAT,不把 tap 桥接到宿主内网网段);
- 东西向隔离:VM 间默认禁通(每 VM 独立 tap + 桥接隔离,
或每 tap 独立网段 + 转发禁令),ARP spoof 场景下 SSH
钉死仍是第二道门,不是唯一门;
- 宿主进程隔离:worker 用户最小化、VMM api-socket 所在
state_dir 0700 且仅 worker 用户可访、宿主启用 hidepid
收紧 /proc 可见性、进程表采集不记录完整 cmdline;
- 部署后核对:state_dir 权限、socket 实际权限、
tap 规则实测(见 §11.5 否定测试)。
### 11.5 仍需真机验证的清单(本轨一律未做,不声称已验)
1. 否定测试:客内 unset 代理后 curl 外网必须不通;
VM A 扫 VM B / spoof B 的 ip 后 SSH 必须被钉死拒绝;
2. provisioned 首启时序:seed 先应用再起 sshd、不重生成
host key;sshd -T 实际输出与密码登录关闭;
3. 宿主 SSH argv 实测:真机 ps 下网关 key 不可见;
4. 长跑/崩溃注入:残留盘总量、快照增长、残留 socket 与
seed/私钥残留的实际表现、GC 覆盖;
5. 压测:配额与容量门在并发下的实际表现;
6. 渗透类:任何以“拿到宿主 shell / 读到别租户数据”为
目标的真实攻击尝试——本轨一律未做,静态通过不等于
已证明不可逃逸;
7. 审计 verdict:以上全部通过后,才能把 verdict 从 No-Go
改为“有条件启用(默认关闭 + 灰度单租户/受信项目)”,
不直接全量多租户。
### 11.6 本轨明确不做
- VM 控制按钮、预热池管理页、成本/配额新页面(前端零改动);
- 真机动态渗透、压测(无 KVM,做不了,只列清单);
- WarmPool dispatch 前查池接线(仍未接线,见 L5 定义)。