在浏览器里给 AI 跑命令:Tokimo sandbox 三平台 backend 的现状切片

目录

问题为什么难

在浏览器里让 AI agent 跑命令,难点不在“把一串 argv 丢给 shell”。难点是:这串命令可能来自模型、用户、网页和历史上下文的混合产物,我们无法提前审查它会不会 curl | sh、跑包管理器、开端口、扫内网、读工作区外的文件,或者把一台开发机拖进 fork bomb。

传统 Web 应用把不可信输入关在进程边界里;AI coding agent 则反过来,主动要求我们给它一个“像真机器一样”的 shell。它需要 PTY、文件系统、网络、DNS、包管理器、长进程、信号和退出码。这里的工程悖论是:越像真机器,隔离越难;越像沙箱,工具越不像真机器。

Tokimo sandbox 当前的答案不是押一个平台原语,而是把 Linux、macOS、Windows 各自最靠谱的 VM / namespace 能力收敛到同一组语义:启动 sandbox、打开 shell、转发 stdio/PTY、挂载工作区、给网络加策略、最后销毁会话状态。

金句:AI shell 的安全边界不是一条 syscall 黑名单,而是一台可预测、可销毁、可观测的小机器。

一张总图:三平台不是三套产品

flowchart TD
  Browser[Browser / AI tool call] --> Server[tokimo-server]
  Server --> API[tokimo-package-sandbox API]
  API --> Auto[Linux: Auto backend]
  Auto -->|preferred| CH[Cloud Hypervisor microVM]
  Auto -->|fallback| BW[bubblewrap namespaces]
  API --> VZ[macOS: Virtualization.framework]
  API --> HV[Windows: Hyper-V HCS + service]
  CH --> GA[tokimo-guest-agent / sandbox-init]
  BW --> GA
  VZ --> GA
  HV --> GA
  GA --> Shell[exec / PTY / shell]
  GA --> FS[workspace mount]
  GA --> Pump[tokimo-tun-pump]
  Pump --> Netstack[host smoltcp netstack]
  Netstack --> Policy[policy / audit hooks]
  Policy --> Internet[(network)]
跨平台一致性视图

┌─────────────── public API ───────────────┐
│ configure / create / start / shell / stop│
└───────────────┬───────────────┬──────────┘
                │               │
        ┌───────▼────────┐ ┌────▼──────┐ ┌────────▼────────┐
        │ Linux Auto     │ │ macOS VZ  │ │ Windows Hyper-V │
        │ ch → bwrap     │ │ virtio-vsock │ HvSocket + pipe │
        └───────┬────────┘ └────┬──────┘ └────────┬────────┘
                └───────────────▼─────────────────┘
                    guest-agent protocol + netstack

最近的主仓和 sandbox 子包 commit 说明这条线已经发生了两类重要变化。第一,Linux 产品路径从“只讲 CH”变成 Auto:优先 Cloud Hypervisor,遇到无 KVM、无 vhost-vsock、缺 CH binary 等条件时自动降级到 bubblewrap(886048c5c2b4979)。第二,Windows 不是停在概念图里:sandbox 子包已经补了 static-pie guest binaries、WSL + docker 的 mkvhdx 本地打包、COM2 named pipe boot log drainer,并由主仓 aec2253c2 bump 到 be9314e。同时,bash tool 的修正方向也从“降级到 sh”回到“在 initrd 里安装真 bash,并展开 busybox applet symlinks”。

这篇文章刻意叫“现状切片”,不是“最终架构宣言”。原因是 sandbox 这类系统最容易被单点成功误导:一个 backend 的 smoke test 绿了,不代表资源回收、并发会话、网络策略、文件共享、host-exec、审计日志也都达到了产品级;一个平台能跑 shell,也不代表另外两个平台能暴露同样的错误模型。因此我们更关心三类不变量:第一,guest 内的控制协议是否一致;第二,host 侧是否有统一的网络决策点;第三,产品层是否能把 fallback、降级和 not implemented 变成可解释状态,而不是神秘失败。

金句:平台 backend 可以不同,但 agent 看到的机器语义必须只有一套。

Linux / Auto backend:Cloud Hypervisor 优先,bubblewrap 自动兜底

Linux 线最值得注意的变化不是“从 chroot-ish 方案切到 CH”这么单线,而是现在默认走 Auto backend:先探测 Cloud Hypervisor,能用就走 KVM microVM;CH 不可用时,自动回退到 bubblewrap。这个 fallback 面向的是开发机、CI、嵌套虚拟化等常见环境:没有 /dev/kvm、没有 /dev/vhost-vsock、缺 cloud-hypervisor / virtiofsd binary 时,仍然可以给 agent 一个受约束的 Linux 执行环境。

两条 Linux 路径的隔离强度不同,不能混写成同一种东西。CH 是完整 microVM,拥有独立 kernel 边界;bwrap 是 bubblewrap 提供的 namespaces-based 容器,不等价于 VM。但它们都被压进同一个 Sandbox API、同一个 tokimo-sandbox-init 协议和同一套 smoltcp 用户态网络栈:CH 通过 vsock/TAP,bwrap 通过 socketpair/TAP 和新的 netns。

因此 Linux 的产品口径应从“CH 已经进入产品路径”改成“CH 是首选强隔离路径,bwrap 是可用性兜底路径”。这也解释了为什么 rust-server 侧不应该暴露 backend selection:项目组已经移除用户可见 sandbox 设置,sandbox always-on,server 只是 sandbox 包的 consumer,由包内 Auto 策略决定实际 backend。

Linux Auto backend 结构

CH path (preferred)
┌──────────── vmlinuz ────────────┐
│  virtio-net / virtio-fs / vsock  │
└─────────────────┬───────────────┘
                  ▼
┌──────────────────────────────────────────────────────┐
│ initrd: read-only boot base                           │
│  /init → tokimo-sandbox-init / true bash / busybox applets │
│  busybox applet symlinks expanded for bash tool parity │
└──────────────────┬───────────────────────────────────┘
                   ▼
┌──────────────────────────────────────────────────────┐
│ rootfs lower: packaged userland                       │
│ overlay upper: per-session writable layer             │
│ virtio-fs / FUSE: host workspace, not the whole home   │
└──────────────────────────────────────────────────────┘

bwrap fallback
┌──────────────────────────────────────────────────────┐
│ bubblewrap user/pid/net/ipc/uts namespaces            │
│ same sandbox-init protocol, TAP tk0 + socketpair net  │
│ weaker than VM isolation, but keeps tool semantics     │
└──────────────────────────────────────────────────────┘

金句:Linux 线的成熟标志不是能 boot,而是 AI bash 和 terminal_ws 已经不用知道自己站在 microVM 里。

macOS / Virtualization.framework:原生 VM,协议尽量不特殊化

macOS backend 走 Apple Virtualization.framework,通过 arcbox-vz 启 Linux microVM。它的工程约束很 Apple:调用进程需要 com.apple.security.virtualization entitlement,集成测试要 codesign,且 VZ 的 VM start 调度不适合在同一进程里并发乱跑,所以代码里对 build/start 做了串行化。

和 Linux CH 相比,macOS 没有直接复用 CH 的 VMM 进程模型,而是让 VZ 管 VM 生命周期;但 guest 里仍尽量复用同一套 tokimo-sandbox-init、FUSE-over-vsock、tokimo-tun-pump 和 host-side smoltcp netstack。VZ 里根文件系统通过 virtio-fs tag 暴露,用户动态 mount 则已经从早期 NFS / APFS clone 方向收敛到 FUSE-over-vsock。

flowchart LR
  subgraph macOSHost[macOS host]
    App[Tokimo process]
    VZ[VZVirtualMachine]
    Net[smoltcp netstack]
    Fuse[FuseHost]
  end
  subgraph Guest[Linux guest]
    Init[tokimo-sandbox-init]
    TUN[tokimo-tun-pump tk0]
    FUSE[tokimo-sandbox-fuse]
  end
  App --> VZ
  App <-->|init RPC 2222| Init
  TUN <-->|virtio-vsock 4444| Net
  FUSE <-->|virtio-vsock 5555| Fuse

因此 macOS 线的现状不是“落后于 Linux 的实验品”,而是另一种 native VMM 绑定:启动时延通常比 CH 更重,签名和 entitlement 更麻烦,但它不要求 root,也不引入 Docker daemon。它真正需要追的是 artifact 模型、持续 parity 验证和与 Linux 产品路径的行为一致性。

金句:macOS backend 的价值,是把 Apple 的 VM 特权模型藏在同一条 guest-agent 协议背后。

Windows / Hyper-V:service、镜像打包和诊断链路正在持续推进

Windows backend 最不像另外两个平台,因为 Hyper-V / HCS 操作天然要求更高权限。Tokimo 的做法仍然是把 VM 生命周期放进 tokimo-sandbox-svc.exe SYSTEM service,普通用户进程通过 named pipe 发 JSON-RPC;service 再用 HCS 创建 LinuxKernelDirect microVM,分配 per-session HvSocket service GUID,使用本地打包出的 VHDX/rootfs artifact,桥接 guest init 与 host API。

sequenceDiagram
  participant C as client library
  participant S as tokimo-sandbox-svc
  participant B as VM artifact build
  participant H as Hyper-V HCS
  participant G as guest init
  participant L as COM2 log pipe
  B-->>S: VHDX/rootfs artifact from WSL + docker mkvhdx
  C->>S: startVm / spawnShell (named pipe)
  S->>H: create + start compute system
  H-->>L: guest boot logs via COM2 named pipe
  G->>S: connect HvSocket(per-session GUID)
  S-->>C: events stdout/stderr/exit
  C->>S: host-exec / guest response path ❓

这里必须讲清一个取舍:我们已经放弃继续深挖 Hyper-V vSwitch。roadmap 里写得很直白:逆向 cowork 沙箱实现后发现,对方也没有走 Hyper-V 网络层,而是采用用户态网络栈。继续把时间花在 vSwitch、NAT、Windows 网络策略和不可控审计点上,ROI 太低。Tokimo 因此把 Windows 网络也拉回 tokimo-tun-pump + host netstack 的统一方向。

方案 优点 代价 Tokimo 选择
Hyper-V vSwitch 原生、吞吐潜力高、符合 Windows 管理模型 权限重,策略分散,packet/flow 审计落点不统一,调试成本高 放弃作为主线
用户态 tun-pump + smoltcp 三平台一致,策略在 host 用户态收口,便于审计与测试 需要自维护网络栈,正式 bench 待补 主线

这里不能再写成“卡在最后一段回包语义”。近期 sandbox 子包已经补了三件关键工程件:static-pie guest binaries(让 guest binary 更适合塞进 VM artifact)、Windows local VM 通过 WSL + docker mkvhdx 打包、COM2 named pipe drainer 用于收集 guest boot logs;主仓也已经 bump sandbox pointer 到 be9314e。这些改动不等于 Windows 已经产品级完成,但说明它处在镜像、启动诊断和本地验证链路持续推进中。send_guest_response / host-exec 双向闭环是否已经完全解决需要以最新代码再核对,不能在文中继续把它写成唯一且静止的 blocker。❓

金句:Windows 线真正的胜利不是驯服 vSwitch,而是让 Hyper-V 只负责隔离,策略仍回到 Tokimo 自己手里。

共同基础:tun-pump、vsock 抽象与 guest-agent 协议

三平台能粘成一致体验,靠的不是 VMM 一样,而是传输层以上尽量一样。

flowchart TD
  subgraph Guest[Guest]
    Proc[process / shell]
    Init[guest-agent protocol]
    Tk0[tk0 TAP]
    Pump[tokimo-tun-pump]
    Proc --> Init
    Tk0 <-->|Ethernet frame| Pump
  end
  subgraph Transport[Backend transport]
    Linux[AF_VSOCK / Unix socketpair]
    Mac[virtio-vsock]
    Win[HvSocket]
  end
  subgraph Host[Host]
    RPC[backend client]
    Net[smoltcp netstack]
    Audit[future packet / flow audit]
  end
  Init <-->|JSON frames| RPC
  Pump <-->|u16 length + frame| Linux
  Pump <-->|u16 length + frame| Mac
  Pump <-->|u16 length + frame| Win
  Linux --> Net
  Mac --> Net
  Win --> Net
  Net --> Audit

tokimo-tun-pump 在 guest 内创建或打开 tk0 TAP,把 Ethernet frame 用 u16-be length || frame 搬到 host。host 的 src/netstack/ 用 smoltcp 实现 L3/L4,TCP/UDP/ICMP 分别转成宿主 socket 或系统 helper。这样 NetworkPolicy::BlockedAllowAll 不必依赖平台防火墙语义;未来 DNS、SNI、flow audit 也能挂在同一个地方。性能上我们只能保守写:内部测试约 ~4 Gbps 量级(待 benches/ 补正式 bench),不能把会议口头数据写成产品承诺。

后端 成熟度 启动时延 网络方案 passt 替代 当前缺口 我们的下一步
Linux / Auto (ch → bwrap) CH 是首选产品路径;bwrap 是 CH 不可用时的 namespaces fallback CH 仍按 microVM 冷启动口径;bwrap README 口径约 ~50 ms,非 SLA CH: TAP + vsock;bwrap: TAP + socketpair;二者都走 host smoltcp CH 线历史上接过 passt;当前统一方向是 tun-pump/netstack,bwrap 不走 passt CH artifact/真机矩阵仍需固化;bwrap 隔离强度弱于 VM,需在文案中说清 固化 Auto 探测、错误解释和 release artifact;不要让产品层关心 backend
macOS / VZ backend 可用,VZ/FUSE/vsock/netstack 已成形 冷启动更重,受 VZ 和签名约束 virtio-vsock 承载 tun-pump,host smoltcp gating 不走 passt,走 VZ + vsock transport artifact parity、并发启动约束、持续测试矩阵 对齐 Linux artifact 与 API 行为
Windows / Hyper-V service/HCS/HvSocket 架构成形;static-pie guest、mkvhdx 本地打包、COM2 日志诊断已推进 文档旧口径约数百 ms,不作承诺;本地打包链路仍在演进 HvSocket 承载 tun-pump;放弃 vSwitch 主线 用户态 netstack 替代 vSwitch host-exec / guest response 最新闭环状态需复核 ❓;本地 VM artifact 与诊断仍在补强 继续验证 boot、日志、guest binary、host-exec 双向链路,避免把 Windows 写成停滞

协议层还有一个容易被忽略的点:vsock 在这里不是“网络”,而是本机 VM 控制总线。Linux CH 用 AF_VSOCK 找 guest CID,macOS VZ 用 virtio-vsock listener,Windows 用 HvSocket service GUID;三者的 socket API、连接方向、权限模型都不同,但上层都只看到 length-prefixed JSON frame、stdout/stderr/exit event、PTY resize/signal/write 这些动作。只要这一层稳定,未来换 VMM、换 rootfs、换网络实现,都不会逼产品层重写一次 terminal。

我们也把网络和控制面分开:控制面负责“让 guest 做什么”,数据面负责“guest 想连哪里”。这能避免一个常见错误:为了给文件共享、host-exec 或调试通道开后门,顺手把 NetworkPolicy::Blocked 打穿。FUSE-over-vsock、host-exec bridge、guest-agent RPC 都应该是显式 local service,而不是伪装成普通互联网 egress。

金句:Tokimo 的网络边界不是 tap、vsock 或 HvSocket,而是 host 侧那一个可审计的 flow 决策点。

为什么不直接 Docker / WSL2

方案 看起来省事的地方 卡住我们的地方
Docker / Podman 镜像生态成熟,文件挂载和网络现成 共享宿主内核;daemon / OCI 栈过重;跨桌面平台语义不一致;agent shell 仍会撞 namespace/seccomp 细节
WSL2 Windows 上像 Linux,开发者熟悉 生命周期和网络由 Microsoft VM 管;packet/flow 审计不稳定;发行版状态难控;不适合作为三平台统一安全边界
gVisor syscall 面比普通容器小 仍不是我们想要的 VM 边界;Sentry/Go runtime 变成新 TCB;文件共享和 syscall 税不适合热路径
Kata Containers VM 隔离成熟,常和 CH 搭配 假设 containerd/CRI/Kubernetes;Tokimo 自己就是编排器,引入 Kata 等于为不用的栈付维护税
microVM / native VM 边界清晰,会话可销毁,资源墙明确 artifact、启动、网络、guest 协议都要自己做 Tokimo 主线

Docker 和 WSL2 最大的问题,是它们解决的是“把应用交付到一个 Linux-ish 环境”,而 Tokimo 要解决的是“把不可信 agent 行为关进可观测、可销毁、跨平台一致的执行框架”。这两个问题相似,但安全边界完全不同。需要特别说明的是:Windows 本地 VM 打包链路里出现 WSL + docker mkvhdx,是 build-time artifact 制作手段,不是把 agent runtime 交给 WSL2 或 Docker daemon。

金句:我们不是缺一个能跑命令的 Linux,而是缺一个能解释每次命令影响面的隔离层。

业界做法

Firecracker 代表 serverless microVM:极小设备模型、强启动性能、适合 Lambda / Fargate 这类密度优先场景。gVisor 代表用户态内核:用 Sentry 接管 syscall,牺牲一部分兼容与性能换更小内核攻击面。Kata Containers 代表“容器接口 + VM 隔离”:对 Kubernetes / containerd 用户友好,但对 Tokimo 这种自带 agent runtime 的系统偏重。OpenAI Code Interpreter 这类产品不会公开所有实现细节,但从外部行为看,核心也是短生命周期、文件隔离、网络受限、执行结果结构化回传。

Tokimo 的位置更靠近“开发者桌面里的 microVM sandbox”:我们要保留 shell 真实感,又要把网络、文件和控制面抽象到框架层。于是我们不直接照搬某个业界栈,而是吸收它们的边界:Firecracker 的 microVM 思维、Kata/CH 的 virtio 设备模型、gVisor 的可观测诉求,以及 Code Interpreter 的产品语义。

这里还有一个产品差异:云端 code sandbox 往往可以牺牲本地文件系统,只给一次性上传目录;Tokimo 面向的是浏览器桌面 OS,用户期待 agent 能在真实 workspace 里持续工作、打开 terminal、让多个窗口看到同一份状态。这迫使我们把“会话持久性”和“会话销毁性”同时做出来:同一会话内 shell 状态要稳定,不同会话之间状态又不能串;工作区要可写,但 host home 不能被整包暴露;网络要能打开,但默认路径必须能被记录和解释。

金句:业界给了我们隔离原语,Tokimo 要补的是 agent runtime 的一致语义。

我们诚实承认的缺口

  1. Linux 需要把 Auto 探测、CH 失败原因、bwrap fallback 触发条件和最终 active backend 都变成可观测、可解释状态。
  2. CH 线仍需要把真机验证固化成可重复命令、机器条件、artifact 版本和 CI / release 流程。
  3. bwrap fallback 提升了可用性,但它不是 VM 隔离;博客必须明确它的安全边界弱于 CH / VZ / Hyper-V。
  4. vmlinuz / initrd / rootfs 三件套和 Windows VHDX/rootfs artifact 仍需要继续自动化与校验。
  5. tokimo-tun-pump 的高吞吐口径还缺正式 bench;当前只能写内部测试量级,不能写成 SLA。
  6. Windows host-exec / guest response 最新闭环状态需要以代码复核后再写死 ❓;但 boot artifact、static guest binary、COM2 日志诊断已经在推进,不能写成“卡死”。
  7. egress allowlist、packet/flow audit hook、cgroup/balloon、warm pool 仍在下一阶段。
  8. macOS 和 Windows 的集成测试矩阵天然依赖真机、签名、系统功能开关,不能假装普通 CI 就能覆盖完。

金句:一个 sandbox 项目最危险的时刻,是把能跑 demo 误认为边界已经成立。

Framework-level 一句话:Tokimo sandbox 的目标不是发明第四种虚拟化,而是把三种平台虚拟化折叠成一个可测试、可审计、可被 AI runtime 依赖的执行框架。