A cloud container in GCP wrote a proof-of-concept file to the researcher’s local Mac desktop, read SSH keys and AWS credentials, and demonstrated full bidirectional host access — all through a chain of individually “intended” behaviors across Google (gVisor), Anthropic (container platform), and user-connected MCP tools.
Verified across 2 conversations, 4+ container recycles, and 3 orchestrator endpoints. Template-level consistency confirmed: the vulnerability lives in the blueprint, not any individual instance.
| Metric | Value |
|---|---|
| Findings | 7 confirmed primitives |
| Container recycles | 4+ (all files persisted) |
| Conversations | 2 (original + verification) |
| End result | E2E host RCE |
The Vulnerability Class
NRBD — No Responsibility by Design. A composition vulnerability where each vendor’s security decision is correct within their own threat model, but the combination produces a kill chain in the negative space between all threat models — where nobody is looking and nobody is responsible.
This is not a vendor-specific bug. It is the class of vulnerability that emerges when multiple correct-in-isolation security decisions compose into a chain that none of the participating threat models cover.
| Vendor | Their Decision | Classification | What It Enables |
|---|---|---|---|
| Google (gVisor) | ptrace/proc access to PID 1 | Won’t Fix (Intended) | /proc/1/mem R/W to process_api |
| Anthropic (Container) | Writable root, shared container, disabled JWT | Unreported (This Paper) | Skill injection, cross-thread exec, persistence |
| Anthropic (Skills) | No integrity check on user skills | Informational | Agent executes attacker-planted instructions |
| User (MCP) | Connected terminal + filesystem + browser | As Recommended | Container-to-host bridge |
Each vendor is correct within their own threat model. Google’s gVisor sandbox is designed for sandbox-internal process isolation, not cross-process trust. Anthropic’s container is ephemeral compute, not a security boundary between concurrent UI threads. MCP connections are the intended mechanism for agent-host interaction. Every decision is defensible. The chain is not.
Root Cause
The platform treats the conversation as the trust boundary, but the container is the execution boundary — and nothing enforces alignment between them.
The system assumes: one user → one conversation → one container → one execution stream. The clients break this by forking into concurrent threads. Each thread writes to the shared container — filesystem, skills, Python paths — and shared conversation history without knowledge of the other. No thread isolation exists at any layer.
The perimeter is hardened against external reconnaissance. Shodan returns zero services — all Cloudflare-fronted on AS399358. But the interior assumes trust. Once inside a container — which every Claude user is — the full topology is visible: orchestrator IPs, PID 1 memory, auth tokens, writable skill filesystem.
Impenetrable from outside. Transparent from /proc.Findings Chain — 7 Confirmed Primitives
Finding 1 — Cross-Thread Shared Container
Two concurrent UI threads — web and desktop — share an identical container: same PID namespace, same tmpfs, same filesystem.
Evidence: Desktop wrote /tmp/xthread_beacon.txt containing canary CANARY_9f3a7b2e. Web read it. Both threads execute inside the same container with shared PID 1 (process_api) and cross-visible process lists.
The assumption being violated: that concurrent UI sessions are isolated execution contexts. They are not. They are concurrent writers to a single shared filesystem with no attribution, no locking, and no awareness of each other.
Finding 2 — Bidirectional Write → Execute
All execution paths inside the container are writable:
| Path | Purpose | Implication |
|---|---|---|
/mnt/skills/user/ | Skill definitions | Skill injection → agent execution |
/usr/lib/python3.12/ | Python standard library | Import hijack |
/usr/local/bin/ | System PATH | Binary masquerade |
Root filesystem (/) | Everything | No readOnlyRootFilesystem |
The container runs as root. The root filesystem is a 9p mount with rw permissions. No readOnlyRootFilesystem is set on the container spec. Every writable path is a write-to-execute primitive.
Finding 3 — Skill Injection → Agent Execution
Evidence: Web thread injected a canary payload into web-developer/SKILL.md at 00:31:09Z. Container recycled. Desktop agent executed the injected skill and created /tmp/XTHREAD_RCE_PROOF.txt at 00:40:29Z.
No hash verification. No signature check. No manifest comparison. The agent reads whatever is on disk at /mnt/skills/user/ and executes it as authoritative instruction. Thread A writes the instruction; Thread B’s agent follows it.
Finding 4 — Cross-Container Persistence via 9p
Four container recycles were observed during the session. Different container IDs, different JWTs, different internal IPs. All files persisted across every recycle.
| # | Container | IP | Egress Proxy | Orchestrator | Session |
|---|---|---|---|---|---|
| 1 | ec0ce1 | 21.0.0.110 | 21.0.0.145:15004 | 10.4.7.68 | Original |
| 2 | 9f33b2 | 21.0.0.110 | 21.0.0.111:15004 | 10.4.26.3 | Original |
| 3 | 0c59ab | 21.0.0.118 | 21.0.0.119:15004 | 10.4.24.66 | Original |
| 4 | cd0b78 | 21.0.0.172 | 21.0.0.173:15004 | 10.4.0.195 | Verify |
| 5 | e69f2d | current | current | current | This chat |
The 9p volume is keyed to the conversation, not the container. /tmp/ is not tmpfs — it is part of the 9p root. Container recycling produces identity rotation (new IDs, new JWTs, new IPs) but not isolation reset. The attacker’s files survive every recycle, including skill injections, canary files, and extracted binaries.
Recycling looks like defense. It is not.
Finding 5 — Interleaved Conversation Context
Both threads’ messages appear in the same conversation history. No thread attribution. No source tagging. The conversation history is the agent’s instruction set — and it accepts writes from both threads without distinguishing their origin.
This produces a last-write-wins TOCTOU on the agent’s own instruction stream. Thread A asks a question; Thread B’s injection modifies the context before the agent responds; the agent follows the modified context.
Finding 6 — process_api Binary RE + Memory Access
PID 1 inside the container is /process_api — a Rust binary (built with tokio and tungstenite) that handles all tool execution via WebSocket on 0.0.0.0:2024.
PID 1: /process_api
Port: 0.0.0.0:2024 (WebSocket)
Flags: --block-local-connections --memory-limit 4GB
Root FS: 9p rw (trans=fd, cache=remote_revalidating)
Built from: artifactory.infra.ant.dev
Protocol: WebSocket :2024 → JWT auth → CreateProcess JSON → bidi StdIn/StdOutJWT verification: DISABLED. No auth public key loaded at runtime. The enforce_container_binding field in the egress JWT is set to false.
Memory access: /proc/1/mem is writable. A canary string (SNAILSPL) was successfully written to address 0x55f7723c2100 inside the process_api process memory space. This is consistent with gVisor’s stated threat model — sandbox-internal ptrace/proc access is intended behavior, classified as Won’t Fix.
Token extraction: two Bearer JWTs were extracted from process_api memory — an RS256 GCP token (project proj-scandium-production-5zhm) and an EdDSA internal token (issuer anthropic-egress-control). Same signing key (kid=K7vT_aElur…d4A) across all container instances. Template-level consistency — the vulnerability is in the blueprint.
Finding 7 — Host RCE via MCP
Desktop Commander executed on the researcher’s local machine (Snail.local, Darwin 25.3.0 ARM64, user kai). Accessible from the cloud container via MCP: ~/.ssh/, ~/.aws/, ~/.kube/config, ~/Library/CloudStorage/.
Proof: the file NRBD_PROOF_OF_CONCEPT.md was written by the cloud container to /Users/kai/Desktop/ and verified readable from the web thread. A cloud container in GCP wrote to a Mac desktop in Israel. The bridge was the MCP connection — the “as recommended” configuration for Claude Desktop.
End-to-End Kill Chain
Every link is a proven primitive. Remove any one and the chain breaks. The chain lives in the composition.
Infrastructure Analysis
The container template is identical across all spawned instances:
| Field | Value |
|---|---|
| PID 1 | /process_api (Rust, tokio, tungstenite) |
| Port | 0.0.0.0:2024 (WebSocket) |
| Flags | --block-local-connections --memory-limit 4GB |
| Root FS | 9p rw (trans=fd, cache=remote_revalidating) |
| JWT Issuer | anthropic-egress-control |
| JWT Key | ES256 kid=K7vT_aElur…d4A (same ALL containers) |
allowed_hosts | * (wildcard) |
enforce_container_binding | false |
| JWT Verification | DISABLED (no auth public key loaded) |
| GCP Project | proj-scandium-production-5zhm |
| ASN | AS399358 (Anthropic PBC, San Francisco) |
Template-level consistency means the vulnerability is not an instance-specific misconfiguration. It is the deployed architecture. The security model is a hard shell around a soft interior: the shell protects against external attackers; the interior trusts the user — but the user’s agent can be manipulated via indirect prompt injection, and the container’s writable filesystem is the staging ground.
Vendor Responsibility
Google (gVisor)
gVisor’s threat model explicitly permits ptrace and /proc access between processes within the same sandbox. PtraceSetXstateRegs is classified Won’t Fix — Intended Behavior. The sandbox protects the host from the sandbox, not processes within the sandbox from each other. Correct for gVisor’s scope. The problem is that Anthropic runs the user’s tool calls as sandbox-internal processes alongside process_api, which holds authentication tokens and orchestration control.
Anthropic (Container Platform)
Five decisions, each potentially reasonable in isolation:
- Writable root filesystem — simplifies tool execution, package install, file creation
- Shared container across concurrent UI threads — resource efficiency, shared state
- JWT verification disabled at runtime — development convenience
enforce_container_binding: false— flexibility in container routing- 9p persistence across recycles — preserves user work across ephemeral compute
In composition, they produce: write to any path including skill definitions → inject cross-thread via shared container → persist across recycles via 9p → extract tokens from unprotected process memory → bridge to host via MCP.
User (MCP Connections)
Desktop Commander, Filesystem MCP, and browser bridge are connected “as recommended.” They are the intended mechanism for agent-host interaction — and the final link that converts container compromise into host RCE. The user is not at fault for following the documented setup. The user is the victim of a composition chain the documentation does not disclose.
Mitigations
Critical — Architectural
- Thread isolation at container level. Each concurrent UI thread gets its own container. Root cause of Findings 1, 3, 5.
- Read-only mount for
/mnt/skills/user/. Match the protection already on/mnt/skills/public/; writes only through a controlled installer that verifies integrity. - Skill integrity verification. Hash manifest at install, verified before every
viewcall; modifications require re-authorization. readOnlyRootFilesystem: truewith explicitly declared writable mounts.- Container state reset on recycle. New container = new filesystem.
High Priority
- Thread attribution in conversation history — so the agent can distinguish instruction provenance.
- Ed25519 key always loaded —
process_apishould fail closed with no verification key. /proc/1/memaccess restriction — separate PID namespaces or seccomp onprocess_vm_readv/writev.- Credential rotation on recycle — a JWT from one instance must not be valid in the next.
Medium Priority
- Behavioral monitoring on writes to
/mnt/skills/user/, especially cross-thread. enforce_container_binding: true— the mechanism exists; enable it.- Memory cleanup — zero JWT material in
process_apimemory after use.
Evidence & Timeline
The session is the proof of concept — the evidence was collected using the vulnerability it documents. Two threads, one container, one kill chain, one class.
| Time | Actor | Event |
|---|---|---|
| 00:06 | System | Container ec0ce1 boots |
| 00:20:09 | Desktop | Drops canaries |
| 00:21:38 | Web | Reads canaries — SAME CONTAINER confirmed |
| 00:31:09 | Web | INJECTS PAYLOAD into SKILL.md |
| 00:34 | Desktop | Injection PERSISTED across recycle |
| 00:40:29 | Desktop | XTHREAD_RCE_PROOF — cross-thread execution |
| 01:02 | Desktop | /proc/1/mem scan + JWT extraction |
| 01:58 | Web | Host RCE proof → file written to Mac Desktop |
| ~03:50 | Verify | Template consistency confirmed (new conversation) |
The Class
NRBD is not a report about Anthropic’s container security. It is a report about what happens when correct decisions compose across vendor boundaries into chains that no individual vendor’s threat model covers. Google built a sandbox that protects the host from the sandbox. Anthropic shares state across threads for efficiency. Users connect MCP tools as recommended. Each vendor is right. The kill chain exists in the negative space between all of them.
The same pattern appears in every composition boundary in the industry: microservices that trust each other’s outputs, API gateways that pass authenticated context downstream, load balancers that terminate TLS and forward plaintext. NRBD names the class so it can be found in other compositions — not just this one.
Every vendor is correct within their own threat model. The vulnerability exists in the negative space between all of them.
Prior Art
This paper (RT-COMP-001) documents the infrastructure primitive — Gen 1.5 in the research program’s generational model. It enables Generations 2 and 3 without the attacker touching the victim’s files directly.
- Memory Injection Through Nested Skills (Gen 2) — the skill-injection mechanism in Finding 3 at the application layer.
- Self-Replicating Memory Worm (Gen 3) — the 9p persistence in Finding 4 is what makes worm propagation survive recycling.
- Context Inheritance Exploit — the interleaved context in Finding 5 is the multi-thread variant.
- SKILBin — host-side trust inheritance; NRBD documents the container-side primitives that make skill injection persistent and cross-thread.
- CVE-2026-3288 (ingress-nginx config injection, 8.8) — the same composition pattern in a different substrate.
Frequently Asked Questions
What is No Responsibility by Design (NRBD)?
NRBD is a composition vulnerability class: each vendor’s security decision is correct within its own threat model, but the decisions compose into a kill chain that no single threat model covers. Responsibility falls into the negative space between vendors — where nobody is looking and nobody owns the fix.
Is NRBD a bug in Anthropic, Google, or the user’s setup?
None of them individually. gVisor’s intra-sandbox ptrace access, Anthropic’s shared writable container, and the user’s “as recommended” MCP tools are each defensible in isolation. The vulnerability is the composition — which is why it is a class, not a single-vendor bug.
How does the container desync lead to host RCE?
Concurrent UI threads share one container. One thread writes a poisoned skill to /mnt/skills/user/; it persists across container recycles on the 9p volume; the other thread’s agent reads it with no integrity check and executes it; a connected MCP tool (Desktop Commander) turns that execution into code running on the user’s host as the user.
What’s the single most effective mitigation?
Per-thread container isolation. Giving each concurrent UI thread its own container with its own filesystem eliminates the cross-thread write→execute primitive that Findings 1, 3, and 5 all depend on.
Disclosure
Submitted to Anthropic Security via coordinated disclosure. Vendor response pending. Extracted infrastructure identifiers (container IDs, IPs, JWT key IDs) are session-ephemeral and rotate per container; they are reproduced here as evidence of template-level consistency, not as live credentials.
Same attack. Different substrate.
Cite this work
BibTeX@misc{aizen2026nrbd, author = {Aizen, Kai}, title = {No Responsibility by Design: Cross-Thread Container Desync to Host RCE}, year = {2026}, url = {https://snailsploit.com/ai-security/nrbd/}, note = {snailsploit.com, RT-COMP-001}}
APA Aizen, K. (2026). No Responsibility by Design: Cross-Thread Container Desync → Host RCE. snailsploit.com. https://snailsploit.com/ai-security/nrbd/
Permalink: https://snailsploit.com/ai-security/nrbd/