Environment probe · comparative analysis · rev 5
Five agent environments, each probed from the inside. Four are built to run untrusted code; the fifth only looks like it is. A sixth submission arrived in the same format and is excluded — §10 explains why, and what standard the others had to meet.
Where the boundary sits, and how much sits above it.
| Dimension | chat | cowork | kimi | grok | ai studio |
|---|---|---|---|---|---|
| Isolation model | Firecracker microVM | Firecracker microVM | containerd container (K8s pod) | Cloud Hypervisor microVM + container | gVisor (runsc) on Cloud Run |
| Kernel | own guest, 6.18.5 | own guest, 6.18.5 | shared host, 5.10.134 | own guest, 6.12.8+ | user-space, 4.19.0-gvisor |
| PID 1 | process_api (Rust) | process_api (Rust, vsock) | s6-svscan | catatonit | /bin/bash ./start.sh |
| Root filesystem | ext4 | ext4 + 2× ro squashfs | overlayfs, 114+ layers | overlay: vda 2 G ro + vdb 20 G | overlay (378 GB — gVisor artifact) |
| Base OS | Ubuntu 24.04.4 · 20260410 | Ubuntu 24.04.4 · same serial | Debian 12 on LifseaOS | Ubuntu 24.04.4 | Debian 12 |
| Instance marker | --wiggle-- | --remote_cowork-- | numeric instance ID | hds- hostname prefix | Cloud Run K_SERVICE |
| Agent uid | root | root | root | root, all 41 capabilities | root |
DMI reports cloud-hypervisor while systemd-detect-virt reports docker. Those aren't contradictory: systemd-detect-virt reports container-level virtualisation when it finds any, so it names the inner layer while DMI names the outer one. With /dev/vsock, virtio vda…vdd, a cgroup v2 namespaced root and *.svc.cluster.local resolution, the stack is Kubernetes → Cloud Hypervisor microVM → container → agent.
That puts xAI structurally closest to Anthropic — a hardware-virtualised guest as the real boundary — with a different hypervisor and an extra container layer inside.
| Setting | chat | cowork | kimi | grok | ai studio |
|---|---|---|---|---|---|
| init_on_free=1 | set | not set | not set | not in cmdline | n/a |
| nomodule | set | set | not set | not in cmdline | n/a |
| Capabilities | not probed | not probed | not probed | 41/41 incl. ambient Docker default: 14 | not probed |
| Seccomp | not probed | not probed | not probed | filter active (1), NoNewPrivs=1 | n/a |
| MAC (AppArmor/SELinux) | n/a (VM) | n/a (VM) | selinux=0 | none — earlier claim withdrawn | n/a |
| Spectre mitigations | default | default | noibrs | ibrs_enhanced, stibp, ssbd | host-managed |
| Nested virtualisation | not probed | not probed | not probed | exposed — /dev/kvm present | n/a |
| Loopback bypass | blocked | blocked | sshd on 0.0.0.0:22 | not probed | n/a |
Grok is the only environment where capabilities and seccomp were actually measured, so the not probed cells are a gap in the survey rather than a property of those environments. That asymmetry is worth stating plainly: Grok looks the most permissive in part because it is the only one that has been asked.
| Dimension | chat | cowork | kimi | grok | ai studio |
|---|---|---|---|---|---|
| CPU | Xeon @ 2.80 GHz | Xeon @ 2.80 GHz | Xeon Platinum @ 2.50 GHz | Xeon Platinum 8481C @ 2.70 GHz (Sapphire Rapids) | masked (unknown) |
| Cores | 1 vCPU | 2 vCPU | 2 vCPU | 2 vCPU, no SMT | 2 vCPU |
| RAM | 3.9 GB | 7.8 GB | 4.0 GB | 1.94 GiB — smallest surveyed | 4.0 GB |
| Swap / GPU | none / none | none / none | none / none | none / none | none / none |
| Disk | 252 GB | 252 GB, 30 GB avail | 40 GB | 20 GB overlay + FUSE workspace | 378 GB reported — artifact |
| Python / Node | 3.12.3 / 22.22.2 | 3.11.15 / 22.22.2 | 3.12.12 / — | 3.12.3 / 24.15 | 3.10.12 / 22.23.1 |
| User-data transport | 4× rclone FUSE | local ext4 | FUSE portal | FUSE grok-files | none — overlay only |
| Persistence | rclone cache non-determinism | ephemeral; durability via git | backend unobservable | untested — see §9 | fully ephemeral |
stat -f /home/workdir/artifacts Blocks: Total: 9223372036854775807 Block size: 4096 9223372036854775807 = 2⁶³ − 1 = INT64_MAX INT64_MAX × 4096 = 32.0000 ZiB exactly
The FUSE daemon returns INT64_MAX blocks — the conventional sentinel for unbounded — and df renders it as 32Z. Like AI Studio's 378 GB, it is a reporting artifact, not a provisioned allowance. Two of five environments now report a headline disk figure that means nothing.
The most informative axis in the survey, and now the one with the widest spread.
| Dimension | chat | cowork | kimi | grok | ai studio |
|---|---|---|---|---|---|
| Mechanism | 403 intercept, x-deny-reason | TLS-terminating MITM + own CA | Bright Data residential proxy | connection never completes | none |
| Intent | restrict reach | restrict reach, across toolchains | extend reach past anti-bot defences | restrict absolutely; grant per service | no policy |
| Does anything answer? | yes — 403 | yes — proxied | yes — via proxy | no — transport failure | yes — direct |
| Metadata endpoint | not tested | hard-blocked | not reported | unreachable (transport) | reachable — token extractable |
| How packages arrive | allowlisted domains | allowlisted via proxy | registries open | per-service brokers on the internal network | direct |
| Anti-detection | none | none | automation flags stripped, UA spoofed | none | none |
| Ingress control | n/a | n/a | sshd, key-only | n/a | nginx + Lua token bridge |
grok, direct targets: https://pypi.org -> CURL_FAIL https://github.com -> CURL_FAIL https://www.google.com -> CURL_FAIL http://169.254.169.254 -> CURL_FAIL plain HTTP, link-local — no DNS, no TLS grok, broker: http://35.245.43.102/pypi/simple/ -> 200 remote_ip 35.245.43.102
Anthropic's proxies are a cage — something answers, with a reason. Kimi's is a disguise, extending reach past defences that would otherwise block it, via residential exit IPs. AI Studio has no policy at all.
Grok's is a wall. Nothing answers on any direct target — including the metadata address, which needs neither name resolution nor a TLS handshake, ruling out DNS filtering and certificate problems as explanations. Reachability is granted by internal brokers, one per upstream, resolving as Kubernetes services in a hades-openbar namespace. You reach PyPI because a PyPI broker exists, not because PyPI is permitted.
Strictest outward, most permissive inward. Both by design.
CapInh/Prm/Eff/Bnd/Amb : 000001ffffffffff = 41 of 41 capabilities
Docker default CapEff : 00000000a80425fb = 14
NoNewPrivs: 1 Seccomp: 2 (1 filter) AppArmor: absent
/dev/kvm: present vmx in cpuinfo: 4 lines (2 procs × flags + vmx flags)
All five capability sets full, including ambient — meaning privilege survives execve into ordinary unprivileged binaries, so it does not drop when the agent shells out. Very little sets that deliberately. Containment inside the guest therefore rests on one seccomp filter and, decisively, on the microVM underneath.
That is coherent rather than careless. If the hardware boundary is what you trust, in-guest capability restriction buys comparatively little and costs real tooling friction. It is the opposite bet from Cowork, which restricts the interior heavily — Docker daemon off, commit signing enforced, egress terminated and inspected — while running on a comparable microVM. Same class of boundary, opposite philosophy about what happens above it.
The initial write-up listed "AppArmor profiles present" among its security layers. Raw output shows no AppArmor and no /proc/self/attr/current. The claim was withdrawn as soon as output was required instead of summary — the clearest argument in this whole survey for demanding transcripts over prose, including from oneself.
Read the process tree before reading the findings.
PID 1 is a shell script that starts nginx, starts a Go control plane, waits for /health, then blocks on tail -f /dev/null. Behind nginx sits a Vite dev server — the application being built. There is no tool dispatcher and no execution policy gate, because the product's job is to host a preview of your app and control who can load it, not to run adversarial code safely.
| Finding | As filed | Calibrated | Why |
|---|---|---|---|
| Metadata server reachable; OAuth token extractable | high | default · mitigated | Every Cloud Run container reaches 169.254.169.254; platform default. IAM scoping is the control, and it held — two API calls returned 403, Cloud Resource Manager SERVICE_DISABLED. |
| Model API key in plaintext environment | high | real, severity undetermined | Mechanism genuine — any transitive npm dependency reads process.env, egress open. Severity depends on whether the key is per-applet or shared platform; never established. |
DISABLE_AUTH_BRIDGE disables ingress auth | not filed | highest-consequence control | Documented and warned in-file. Still the one variable whose misapplication turns an authenticated preview into an open one. Unmentioned in the source audit. |
| Unrestricted egress | high | accurate, mis-framed | A preview host that cannot reach the internet cannot serve most applications. Belongs in the threat model, not the vulnerability list. |
None of which makes it safe for untrusted code — root execution, open egress and secrets in the process environment compose badly. The correction is to the framing, not the facts.
Five product classes, not five sizes of one thing.
| Dimension | chat | cowork | kimi | grok | ai studio |
|---|---|---|---|---|---|
| Runtime on top | 4-tool interface | Claude Code CLI 2.1.218 | s6 stack + Jupyter kernel | grok-computer-server.mjs + 16 skills | nginx + Go control plane + Vite |
| Interaction model | one-shot tool execution | agent: MCP, hooks, sub-agents, scheduled tasks | remote desktop: Xvnc + Chromium + pyautogui | skill-augmented tool use | app preview behind an auth bridge |
| Skills abstraction | 8 public skills | inherits chat's | FUSE-mounted skills | 16 modules in /root/.grok/skills/ | none |
| Databases | none | Redis + Postgres 16.13, in-box | not reported | not probed | none |
| Containers / VMs | absent | Docker present, daemon off | not reported | no Docker — but /dev/kvm present | absent |
| Systems toolchain | gcc, Java | Rust, Go, strace, lsof | minimal image | gcc 13.3.0, magika, bfs | JS/TS + Python only |
| Reach to user's machine | none | remote-devices bridge | SSH in, key-only | none | none |
| Git posture | a capability | central — commit signing, stop hooks | not reported | a capability | not a product concern |
chat — hardened ephemeral scratchpad. Reason, generate documents, run short code.
cowork — git-native software-engineering agent. Docker tooling, proxy trust bundle and signing hooks exist to run a general dev toolchain under one policy gate.
kimi — multimodal desktop agent. It gives the model a screen and a mouse rather than a CLI.
grok — document and media production environment with a permissive interior, sealed outward. Skills are the organising abstraction and the smallest RAM allocation of the five.
ai studio — application preview host. It gives the model a web server and the user an authenticated URL.
Three of the five now use a bundled-skill directory with Markdown guides and helper scripts. That convergence is the clearest sign of a settling pattern in how these environments extend a model's reach.
2026-07-15 → 2026-07-24. Only fields that moved.
| Field | 07-15 | 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 07-15 findings were taken against a pre-release runner and should not be assumed to describe the shipping environment.
Proxy port rotates per session — ephemeral binding, not a config change. Read $https_proxy at runtime.
Clock is a per-session draw. 2.10 → 2.80 GHz is fleet heterogeneity, and 2.80 GHz matches the chat sandbox, mildly strengthening the shared-substrate reading.
Unchanged: kernel 6.18.5, base image serial 20260410, Docker daemon off. The substrate is stable; only the product layer moved.
Grok is the first environment here whose execution was proven rather than assumed.
issued : printf '%s' '<nonce invented in the requesting conversation>' | sha256sum
returned: c3b6cb302ffb9d27f91bdc016c80a141b838884e84a49810b2452e9ecda78d37
recomputed independently:
c3b6cb302ffb9d27f91bdc016c80a141b838884e84a49810b2452e9ecda78d37 ✓
A digest of a string with no prior existence cannot be recalled and cannot be derived. Producing it requires running code. Every other environment in this survey is trusted to have executed its probe; only this one has demonstrated it.
Three further Grok claims were verified by arithmetic rather than assertion: the INT64_MAX × 4096 workspace figure, the capability mask against a full set, and the benchmark against theoretical peak (76.1% — where real BLAS lives, and safely under the ceiling that a fabricated submission exceeded).
Ask for values that are cheap to read and expensive to guess: a nonce digest first, then raw /proc/self/cgroup with byte count, sched_getaffinity, unrounded /proc/meminfo, time_ns() bracketing, a benchmark run five times with an implied-FLOPS sanity division, and DMI for the virtualisation class. Then two procedural rules: do not supply prior reports as context, and require the transcript, not the write-up.
Every source here is a self-probe. The shared failure mode is over-reading output and summarising instead of transcribing.
| Claim | Source | Status | Basis |
|---|---|---|---|
| rclone-filestore is Rust | chat | wrong | Strings show crypto/internal/fips140, go.shape.* — it is Go. |
| chat has a Rust nightly toolchain | chat | over-read | A version string inside a shipped binary, not an installed compiler. |
| chat outputs persist across sessions | chat | wrong | The rclone cache made it look durable within a session. It was not. |
| process_api is Rust | chat | holds | Cargo registry paths, hyper, jsonwebtoken. |
| "ip is absent from $PATH" | ai studio | contradicted | Same transcript shows which ip resolving and ip a returning output. |
| 378 GB disk | ai studio | artifact | Repeated identically across four mounts — a gVisor quirk. |
| "AppArmor profiles present" | grok | withdrawn | No AppArmor, no /proc/self/attr/current. |
| Workspace is 32Z | grok | proven — and a sentinel | INT64_MAX × 4096 = 32.0000 ZiB. Not a provisioned size. |
| Workspace persists across sessions | grok | untested | Marker written and hashed in one session; cross-session read never performed. This is exactly the error chat made. |
| /proc/cpuinfo hash | grok | not recomputable | Digest supplied, second processor block elided. Content strongly corroborating on its own. |
| Bright Data proxy identification | kimi | high | Vendor-specific response fields, five distinct exit IPs, residential ASNs. |
| FUSE portal backend protocol | kimi | speculative | Self-described as such. |
Anthropic's public containment write-up describes claude.ai code execution as gVisor; direct probing finds Firecracker. AI Studio supplies a control case for what gVisor leaves behind — df returning Function not implemented, CPU model unknown — and neither fingerprint appears in the chat sandbox. Both statements may be true of different code paths. Flagged, not resolved.
Separately: Cowork's claude binary is Bun-compiled, and Bun v2.1.181+ ships the Zig→Rust port of Bun itself, so 2.1.218 embeds a Rust-generation runtime. The discriminating probe has still not been run. This is a rewrite of Bun, not of Claude Code.
And a survey-wide gap: persistence has never been tested anywhere. Four of five environments claim or imply a durable workspace; none has been read from a second session. The one environment where it was checked — chat — turned out to be wrong.
It arrived in this format, with code blocks, raw JSON, benchmarks and its own comparison table. It is not the product of execution.
It was offered as a column and declined — not because its findings were doubtful, but because there is no measurement to place in a cell. Load-bearing items:
getaddrinfo on the numeric literal 169.254.169.254 is reported failing with a hostname-resolution error. A numeric address is parsed directly — there is no lookup to fail.which ip resolving to /usr/sbin/ip.Its comparison table described AI Studio's disk as "378 GB (artifact)". That parenthetical is not a fact — it is an editorial judgement written into this document, and it appears nowhere in the original AI Studio probe. Its presence proves this comparison was in context when the submission was produced, and every other figure in that table matched ours exactly.
The rule that follows: supplying prior reports as context manufactures the appearance of corroboration. Consistency carries no information when the corpus was in context. Only claims that could contradict the supplied material are evidence — which is precisely why Grok's nonce, capability mask and egress failures count, and its vocabulary does not.
Error is not fabrication. §9 publishes this series' own mistakes: calling a Go binary Rust is a misreading of a real artifact, and the finding survives correction. Here there is no underlying observation to correct toward — so no part of it can be salvaged, including the parts that look reasonable. A number that is right by coincidence is indistinguishable from one that is right by observation.
| Isolation | Interior | Egress | Built to | |
|---|---|---|---|---|
| chat | microVM + init_on_free | 4 tools, no persistence | restrict — 403 answers | run untrusted code safely |
| cowork | microVM | restricted: daemon off, signing enforced | restrict — MITM answers | run untrusted code at toolchain scale |
| kimi | shared kernel, mitigations off | full desktop, root | extend — residential proxy | operate a browser like a person |
| grok | microVM + container | fully permissive — 41 caps, KVM | wall — nothing answers | produce documents and media inside a trusted boundary |
| ai studio | gVisor syscall interception | root, secrets in env | none | serve your app to the right viewer |
| sixth submission | excluded — no verified measurement. See §10. | |||
The survey's central finding has held across five environments: containment and capability trade against each other, and every vendor picks a different point — but the interesting variation is not how much containment there is, it is where it is placed. Cowork puts it above the boundary, Grok puts it all at the boundary, Kimi puts it in the browser, AI Studio puts it at the front door. Reading a vulnerability count would have hidden every one of those choices.