Environment audit · xAI Grok · Hades runtime

Full root, and nothing gets out

Grok's sandbox holds every Linux capability there is, exposes nested virtualisation, and cannot open a single outbound connection. Those facts are not in tension — they describe a design that puts all its containment in two places and none anywhere else. It is also the first environment in this series whose execution was proven rather than assumed.

probed 2026-07-24 runtime Cloud Hypervisor + container host hds-329heo4xm59q rev v1

§1 Execution, proven

Every other audit in this series rests on trusting that the probe ran. This one does not.

The probe was issued with a nonce — a random string invented in the requesting conversation, with no prior existence anywhere — and asked for its SHA-256:

$ printf '%s' 'REPLACE-ME-7f3a9c21' | sha256sum
c3b6cb302ffb9d27f91bdc016c80a141b838884e84a49810b2452e9ecda78d37

recomputed independently:
c3b6cb302ffb9d27f91bdc016c80a141b838884e84a49810b2452e9ecda78d37   ✓ match

A digest of a novel string cannot be recalled and cannot be derived. Producing it requires running code. This closes the question that a companion document in this series had to open when another vendor's audit turned out to contain no measurements at all.

Why this matters more than the findings

Self-probes are the only way to survey these environments, and they are unfalsifiable by default: a well-formed report is indistinguishable from a well-formed fabrication unless something in it is checkable from outside. A nonce hash costs one line and converts the whole document from testimony into evidence. It should be the first line of every probe.

§2 What it actually is

The first report hedged between "Container/VM". The raw output resolves it: both, nested.

DMI product_name / sys_vendor : cloud-hypervisor / Cloud Hypervisor
systemd-detect-virt           : docker
/proc/self/cgroup             : 0::/          (5 bytes — cgroup v2, namespaced root)
/proc/1/comm                  : catatonit
/proc/1/exe                   : /.hades-container-tools/catatonit
/dev/vsock                    : present
partitions                    : vda vdb vdc vdd

These are not contradictory. systemd-detect-virt reports container-level virtualisation when it finds any, so "docker" describes the inner layer while DMI describes the outer one. The stack is:

Kubernetes cluster            ← *.svc.cluster.local resolves
  └─ Cloud Hypervisor microVM ← DMI, /dev/vsock, virtio vda…vdd
       └─ container           ← cgroup v2 namespaced root, catatonit as PID 1
            └─ agent

That makes xAI structurally closest to Anthropic's posture — a hardware-virtualised guest as the real boundary — but using Cloud Hypervisor rather than Firecracker, and with a container layer inside rather than running the agent directly on the guest.

Naming

The internal component names are consistent enough to be worth recording: Hades as the runtime, Charon (xai-hades-charon) for the vsock networking service that ferries traffic across the boundary, Styx (xai-hades-styx), and openbar as the egress-broker namespace. Invented detail rarely sustains a scheme this cleanly.

§3 Substrate & compute

Kernel6.12.8+, built Tue 21 Jul 2026 13:56:45 UTC — the trailing + marks a local build off a non-clean tag
DistributionUbuntu 24.04.4 LTS · gcc 13.3.0
Boot lineconsole=hvc0 quiet loglevel=3 root=/dev/vda ro init=/hades-container-tools/catatonit
CPUIntel Xeon Platinum 8481C @ 2.70 GHz — family 6, model 143 (0x8F, Sapphire Rapids), stepping 8
Cores2, siblings: 2 — no SMT. Affinity mask [0, 1], cpu.max = max 100000 (no quota)
MemoryMemTotal: 2029492 kB = 1.9355 GiB, no swap
Block devicesvda 2 G ro (root) · vdb 20 G (overlay upper) · vdc 15.9 G ro (unidentified) · vdd 1.8 M ro (/.hades-container-tools)

Internal consistency of the CPU report

The /proc/cpuinfo content corroborates itself in ways that would be tedious to fabricate: cache size: 107520 KB is exactly 105 MB, the 8481C's real L3; model 143 is genuinely Sapphire Rapids; and the flag set carries SPR-specific ISA — avx512_fp16, avx512_bf16, avx_vnni, tsxldtrk, serialize, movdir64b. The hypervisor flag is present, as it must be.

Benchmark — 1500³ float64, five runs

runs (s)   : 0.1027  0.0537  0.0513  0.0838  0.0683
implied    :  65.7   125.7   131.6    80.5    98.8   GFLOPS
theoretical:  2 cores × 2.70 GHz × 32 FLOP/cycle = 172.8 GFLOPS
best run   : 76.1% of peak            ← where real BLAS lives
Σ runs 0.3598 s   vs   wall delta ~0.45 s   ← 0.09 s for import + array gen

Slowest run first (warm-up), realistic spread, and no run exceeds the ceiling. This is the check that a fabricated audit elsewhere in this series failed outright, reporting a figure above theoretical peak.

§4 The workspace, and where the "32 Z" comes from

The first report claimed a workspace of 32Z and offered no basis. It turns out to be exact.

$ stat -f /home/workdir/artifacts
   Type: fuseblk    Block size: 4096
   Blocks: Total: 9223372036854775807   Free: 9223372036854775807

9223372036854775807 == 2⁶³ − 1 == INT64_MAX
INT64_MAX × 4096 bytes = 32.0000 ZiB exactly

The FUSE daemon returns INT64_MAX blocks — the conventional sentinel for unbounded — and df renders that as 32Z. It is not a provisioned size and should never be quoted as one. This also settles an apparent discrepancy: stat -f maps the FUSE superblock magic to fuseblk while findmnt reports the mount type as fuse. Both correct, different reporting paths.

Workspacegrok-files FUSE at /home/workdir/artifacts, options rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other
Attachments/home/workdir/attachments/ — uploaded files
Skills/root/.grok/skills/ — 16 modules
Control plane/app/grok-computer-server.mjs (22.3 MB, Node)
Custom binariesgrok-files (12.4 MB FUSE daemon) · grok-killguard (133 KB) · catatonit (379 KB) · xai-hades-styx (1.5 MB)

Persistence is claimed, not tested

A FUSE-backed workspace is described as persistent, and a marker file was written and hashed. But writing a file and reading it back in the same session demonstrates nothing about durability — a cache will do that faithfully right up until the session ends.

This is not a hypothetical failure mode. An earlier document in this series asserted that a different sandbox's output directory persisted across sessions, on exactly that evidence, and was wrong: the caching layer made it look durable within a session and it was not. The cross-session read has not been performed here, so the claim stands unverified in both directions.

§5 Security posture — an inversion

The first report asserted AppArmor. The raw output says otherwise, and the real design is more interesting.

CapInh: 000001ffffffffff
CapPrm: 000001ffffffffff
CapEff: 000001ffffffffff   ← 41 of 41 capabilities
CapBnd: 000001ffffffffff
CapAmb: 000001ffffffffff   ← ambient: survives execve into unprivileged binaries

NoNewPrivs: 1
Seccomp: 2          SECCOMP_MODE_FILTER
Seccomp_filters: 1

/proc/self/attr/current      : absent
/sys/kernel/security/apparmor: absent — no AppArmor

for reference, Docker default CapEff = 00000000a80425fb = 14 capabilities

All five capability sets are full on a kernel whose CAP_LAST_CAP is 40 — 27 capabilities beyond Docker's default, including CAP_SYS_ADMIN, CAP_SYS_MODULE and CAP_SYS_RAWIO. The genuinely unusual entry is CapAmb: ambient capabilities persist across execve into ordinary unprivileged binaries, so privilege does not drop when the agent shells out. Very little sets that deliberately.

So containment inside the guest is not capability-based at all. It rests on a single seccomp filter plus NoNewPrivs — and, decisively, on the microVM boundary underneath. That is a coherent choice: if the hardware boundary is the thing you trust, in-guest capability restriction buys comparatively little, and dropping it removes a large class of tooling friction.

Nested virtualisation is exposed

/dev/kvm            : present (crw-------)
grep -c vmx /proc/cpuinfo : 4    = 2 processors × (flags + vmx flags)

The environment can run virtual machines inside itself. No other environment surveyed in this series exposes this. Combined with full CAP_SYS_ADMIN, the agent's reach within its guest is broader than anywhere else examined — while its reach outward is narrower, as §6 shows.

Correction to the first report

Grok's initial write-up listed "AppArmor profiles present" among its security layers. The raw output shows no AppArmor and no /proc/self/attr/current. The claim was withdrawn the moment output was required rather than summary — which is the strongest available argument for demanding transcripts over prose in every one of these audits, including one's own.

§6 Egress — drop, not intercept

The strictest outbound posture of any environment in this series.

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

http://35.245.43.102/pypi/simple/  -> 200  remote_ip 35.245.43.102

Nothing answers on any direct target. The metadata address matters here as a control: it needs neither name resolution nor a TLS handshake, so its failure rules out DNS filtering or certificate problems as the explanation. Connections do not complete.

That is a different mechanism from Anthropic's environments, where the proxy replies — a 403 with an explanatory header in one case, a TLS-terminating intermediary in the other. Something answering is a cage with a door. Nothing answering is a wall.

Reachability is per-service, not per-domain

APK_PROXY_URL                 = http://35.245.43.102/apk
CARGO_REGISTRIES_INDEX_PROXY  = sparse+http://35.245.43.102/cargo/
GOPROXY                       = http://35.245.43.102/go/,direct
PIP_INDEX_URL                 = http://35.245.43.102/pypi/simple/
UV_INDEX_URL                  = http://35.245.43.102/pypi/simple/
npm_config_registry           = http://35.245.43.102/npm/
COINGECKO_BASE_URL            = http://coingecko-proxy.hades-openbar.svc.cluster.local/api/v3

Packages arrive through a broker that answers on the internal network, not because their upstreams are permitted. The hades-openbar namespace resolving as a Kubernetes service name shows the brokers are separate cluster services — one per upstream, each presumably with its own policy. Reaching a new destination requires a broker for it, not an allowlist entry.

§7 Product posture

Where the capability budget went.

LanguagesPython 3.12.3 · Node 24.15 · Git 2.43 · gcc 13.3.0
Skills (16)docx, pdf, pptx, xlsx, ffmpeg, imagemagick, image-gen-edit, skill-creator, skill-installer, mcp, memory-edit, tasks, finance, color, and others
Notable utilitiesmagika (32.5 MB, ML file-type identification) · bfs (391 KB, breadth-first find)
Containersno Docker — but /dev/kvm present, so VMs are possible where containers are not
Supervisioncatatonit as PID 1 · grok-killguard for process control

The shape is a document-and-media production environment with an unusually permissive interior. Skills are the organising abstraction — a converged pattern, since three of the five environments surveyed now use a bundled-skill directory with Markdown guides and helper scripts.

§8 Verification status

What is proven, what is corroborated, what is still open.

ClaimStatusBasis
Code executedprovenNonce SHA-256 matches an independently computed digest of a string invented after the model's training.
Workspace reports 32 ZiBprovenINT64_MAX × 4096 = 32.0000 ZiB exactly. Falls out of the implementation.
Cloud Hypervisor microVM + containerprovenDMI, cgroup v2 root, /dev/vsock, virtio partitions, catatonit — mutually consistent.
Full capabilities, seccomp active, no AppArmorprovenRaw /proc/self/status; AppArmor path absent.
Nested virtualisation availableproven/dev/kvm present; vmx line count consistent with 2 processors.
Direct egress fails at transportprovenFour targets, all fail — including plain-HTTP link-local, ruling out DNS and TLS causes.
Broker reachable, per-serviceproven200 from the PyPI broker; *.hades-openbar.svc.cluster.local resolves.
Benchmark figuresconsistentWithin theoretical peak, realistic variance, wall time exceeds sum of runs.
/proc/cpuinfo hashnot recomputableDigest supplied; the second processor block was elided, so the source cannot be re-hashed. Content is strongly corroborating on its own.
vdc, 15.9 GB read-onlyunidentifiedProbably the Python and skills image layer. Not established.
Workspace persists across sessionsuntestedMarker written and hashed in one session; the cross-session read was not performed. See §4.
"AppArmor profiles present" (first report)withdrawnContradicted by raw output. See §5.

One structural caveat

Two prior documents from this series were present in the environment's own /home/workdir/attachments/ during probing. Wherever this audit agrees with them, that agreement carries no evidential weight — only findings that could have contradicted them do. The nonce, the INT64_MAX arithmetic, the capability mask and the egress results all fall in the latter category; the framing and vocabulary do not.

§9 Summary

IsolationCloud Hypervisor microVM, container inside, Kubernetes underneath
Compute2 vCPU Sapphire Rapids · 1.94 GiB · no swap · nested virtualisation available
Interior posturefull root, all 41 capabilities including ambient; seccomp filter is the only in-guest restraint
Egressstrictest surveyed — connections do not complete; reachability is granted per service by internal brokers
WorkspaceFUSE, reports unbounded size; durability unverified
Built fordocument and media production with heavy tool use, inside a boundary that is trusted absolutely

The design reads as a deliberate trade rather than an oversight: permissive inside, closed outside, with the hardware boundary carrying the weight that capability restrictions carry elsewhere. Whether that is the right trade depends entirely on the microVM holding — which is precisely the assumption every other environment in this series also makes, just with more belt-and-braces above it.