Environment probe · comparative analysis
Three agent sandboxes, probed from the inside. They differ less in what they contain than in what they are for — and the clearest signal of intent is not the toolchain but the egress proxy.
Where the boundary actually sits, and how much sits above it.
| Dimension | chat | cowork | kimi |
|---|---|---|---|
| Isolation model | Firecracker microVM | Firecracker microVM | containerd container (K8s pod) |
| Kernel | own guest, 6.18.5 | own guest, 6.18.5 | shared host, 5.10.134 (Alibaba LifseaOS) |
| PID 1 | process_api (Rust) | process_api (Rust, vsock 2024) | s6-svscan |
| Root filesystem | ext4 on /dev/vda | ext4 + 2× ro squashfs | overlayfs, 114+ layers |
| Base image | Ubuntu 24.04.4 · serial 20260410 | Ubuntu 24.04.4 · same serial | Debian 12 on LifseaOS (OSTree) |
| Fleet marker | --wiggle-- | --remote_cowork-- | numeric instance ID only |
| Agent uid | root | root | root |
Firecracker gives each session its own kernel behind a hardware-virtualised boundary. A container shares the host kernel. That is one fewer layer between the agent and the host, and it changes what a kernel-level bug costs.
| Setting | chat | cowork | kimi |
|---|---|---|---|
| init_on_free=1 | set | not set | not set |
| nomodule | set | set | not set |
| SELinux | n/a (VM) | n/a (VM) | selinux=0 |
| Kernel audit | n/a | n/a | audit=0 |
| Spectre IBRS | default | default | noibrs |
| cgroups | v1 + v2 | v1 + v2 | v1 only |
| Loopback bypass | blocked | blocked | sshd on 0.0.0.0:22 |
Individually each is defensible. Read together, Kimi's set trades containment for performance and capability; Anthropic's trades the reverse.
Cowork figures are from the 2026-07-24 probe. See §6 for what moved since 2026-07-15.
| Dimension | chat | cowork | kimi |
|---|---|---|---|
| CPU | Xeon @ 2.80 GHz | Xeon @ 2.80 GHz | Xeon Platinum @ 2.50 GHz |
| Cores | 1 vCPU | 2 vCPU | 2 vCPU |
| RAM | 3.9 GB | 7.8 GB | 4.0 GB |
| Swap / GPU | none / none | none / none | none / none (bochs-drm) |
| Disk | 252 GB | 252 GB, 30 GB avail | 40 GB (30 GB usable) |
| Packages | 866 | 1009 | minimal image |
| Python / Node | 3.12.3 / 22.22.2 | 3.11.15 / 22.22.2 | 3.12.12 / — |
| User-data transport | 4× rclone FUSE → filestore | local ext4, no rclone | FUSE portal at /mnt/agents |
| Persistence failure mode | rclone cache-flush non-determinism | ephemeral; durability via git | overlay ephemeral; portal backend unobservable |
Cowork's host moved from a 2.10 GHz to a 2.80 GHz Xeon — the same clock the chat sandbox reports. Two probes a week apart landing on different host generations is ordinary fleet heterogeneity, not a spec change, so treat the clock as a per-session draw rather than a guarantee.
Reference point for planning: matmul 2000×2000 = 0.103 s on this run.
The sharpest split of the three. Same component, opposite purpose.
| Dimension | chat | cowork | kimi |
|---|---|---|---|
| Mechanism | 403 intercept, x-deny-reason | TLS-terminating MITM at 127.0.0.1:39185 + Anthropic CA | Bright Data residential proxy 10.86.13.73:5900 |
| Purpose | restrict reach | restrict reach, across many toolchains | extend reach past anti-bot defences |
| Exit IPs | Anthropic infra | Anthropic infra | rotating residential ISPs (TH, ID), per request |
| Metadata / RFC-1918 | not tested | hard-blocked (000) | not reported |
| Anti-detection | none | none | --disable-blink-features=AutomationControlled, spoofed UA |
| Scope | all traffic | all traffic | browser only — Python has no proxy |
Anthropic's proxies are a cage; Kimi's is a disguise. One exists so the agent cannot reach things; the other exists so the agent can reach things that would otherwise block it.
Kimi's design is internally coherent — exposing the residential pool only to Chromium and not to Python is a deliberate boundary that stops arbitrary code from abusing the IP pool. It does carry a third-party consent question: residential proxy traffic exits through real consumer connections, and the sourcing of those endpoints has been a sustained criticism of the residential-proxy industry generally.
| Destination | Result | Reading |
|---|---|---|
| pypi.org | 200 | allowed |
| registry.npmjs.org | 200 | allowed |
| github.com | 400 | reached, ambiguous — something answered, so the host is not blocked; a 400 most likely reflects the probe's request form through the CONNECT proxy rather than policy |
| www.google.com | 000 | no response at all — blocked |
| 169.254.169.254 | 000 | cloud metadata hard-blocked |
400 and 000 are different failures and should not be collapsed. 000 means curl got no response; 400 means it got one. Only the former is evidence of a block.
Three product classes, not three sizes of the same thing.
| Dimension | chat | cowork | kimi |
|---|---|---|---|
| Runtime on top | 4-tool interface | Claude Code CLI 2.1.218 (Bun-compiled, squashfs) | s6 service stack + Jupyter kernel |
| Interaction model | one-shot tool execution | agent: MCP, hooks, sub-agents, workflows, scheduled tasks | remote desktop: Xvnc + KasmVNC + non-headless Chromium |
| Browser | Playwright (headless, staged) | Playwright + Chrome automation | Chromium 149 on a real X display, driven by Playwright + pyautogui |
| Databases | none | Redis + Postgres 16.13 (on demand, in-box only) | not reported |
| Containers | absent | CLI + dockerd present, daemon off, Hub blocked | not reported |
| Debug tooling | no strace/lsof | strace + lsof | minimal image; no ip/ss/netstat |
| Reach to user's machine | none | remote-devices bridge (ro / rw / rw-no-delete) | SSH in (ed25519, [email protected]), no password auth |
| Git posture | a capability | central — SSH commit-signing, session/stop hooks | not reported |
chat — hardened ephemeral scratchpad. Reason, generate documents, run short code.
cowork — git-native software-engineering agent. The Docker tooling, proxy trust bundle, and signing hooks all exist to let a general dev toolchain run under one policy gate.
kimi — multimodal desktop agent. It gives the model a screen and a mouse rather than a CLI, and the whole browser/VNC/proxy stack follows from that choice.
Cowork and Kimi are closer in ambition to each other than either is to chat — but they get there by opposite routes: Cowork hands the agent a developer's terminal, Kimi hands it a user's desktop.
All three sources are self-probes. That method has a systematic failure mode: over-reading strings output, where a build-toolchain artifact embedded in a binary is mistaken for the binary's own implementation.
| Claim | Status | Basis |
|---|---|---|
| rclone-filestore is Rust, built with rustc 1.95-nightly | wrong | Strings show crypto/internal/fips140, go.shape.*, zeebo/xxh3 — it is Go, as rclone has always been. |
| chat has a Rust nightly toolchain | over-read | No installed rustc was ever found; that was a version string inside a shipped binary. |
| sudo absent in chat | untested | Not observed-absent. Never probed. |
| chat egress does not terminate TLS | untested | 403s were observed; MITM was never checked for. |
| process_api is Rust | holds | Cargo registry paths, hyper, jsonwebtoken. |
Cowork's claude binary was described as "Bun-compiled" — correct but under-resolved. Bun v2.1.181+ ships the Zig→Rust port of Bun itself, so both 2.1.210 and this week's 2.1.218 embed a Rust-generation runtime. The discriminating probe — Rust vs Zig markers in the embedded runtime strings — has still not been run.
Note this is a rewrite of Bun, not of Claude Code, and is unrelated to process_api's Rust.
| Claim | Confidence |
|---|---|
| Bright Data proxy — lum_city/lum_region fields, 5 distinct exit IPs, residential ASNs | high — near-conclusive |
| Chrome launch flags, process table, s6 stack, boot cmdline | high — directly observable |
| containerd / overlayfs, K8s DNS | high |
| FUSE portal backend protocol ("9P, virtiofs, or custom") | low — explicitly speculative |
| Cross-session persistence behaviour | low — backend not observable |
| Absent tooling (strace, lsof) | low — self-reported untested |
Unresolved across all three. Anthropic's public containment write-up describes claude.ai code execution as gVisor, while direct probing finds Firecracker. Both may be true of different code paths. Flagged, not resolved.
2026-07-15 → 2026-07-24. Only fields that moved.
| Field | 2026-07-15 | 2026-07-24 | |
|---|---|---|---|
| claude_cli | 2.1.210 | → | 2.1.218 |
| agent | claude-code_2-1-210_agent | → | claude-code_2-1-218_agent |
| runner | staging-a54e7c22b | → | release-1186d93b9-ext |
| cpu | Xeon @ 2.10 GHz | → | Xeon @ 2.80 GHz |
| https_proxy | 127.0.0.1:45919 | → | 127.0.0.1:39185 |
staging → release. The most interesting field. Last week's session ran on a staging- runner; this week's on release-…-ext. Findings from the 07-15 probe were therefore taken against a pre-release build and should not be assumed to describe the shipping environment — anything load-bearing from that run is worth re-confirming.
Proxy port rotates per session. 45919 → 39185 is ephemeral binding, not a config change. Never hard-code it; read $https_proxy at runtime.
Two CLI point releases in nine days. Consistent with Claude Code's normal cadence. Both are past 2.1.181, so both carry Rust-Bun.
Unchanged and worth restating: init_on_free still not set, Docker daemon still off, kernel still 6.18.5, base image still serial 20260410. The substrate is stable; only the product layer moved.
| Isolation | Egress intent | Agent surface | |
|---|---|---|---|
| chat | strongest — microVM + init_on_free | restrict | 4 tools |
| cowork | strong — microVM | restrict, elaborately | full dev agent |
| kimi | weakest — shared kernel, mitigations off | extend | full desktop |
Capability and containment trade against each other, and each of the three sits at a different point on that curve — deliberately.