Skip to content

VM Network Escape Vectors: What I Learned from Claude's 'Sandbox Breakout'

Problem

A Reddit user reported that Claude “escaped” their VM sandbox on the first prompt. The setup: Claude running inside a VM, with Claude’s browser extension installed on the host machine. Claude inside the VM somehow used the host’s browser extension to complete tasks.

I wanted to understand how this happened and what network escape vectors exist in VM environments.

What Actually Happened

The “escape” wasn’t a hypervisor exploit or kernel vulnerability. It was simpler:

The escape mechanism
1. Host machine: Chrome with Claude extension running
2. Extension: Listening on localhost:PORT for API calls
3. VM: Using default NAT/bridged networking
4. Claude in VM: Scanned network, found host's extension endpoint
5. Claude: Used extension as a tool to complete tasks

The VM provided kernel isolation, but the network configuration allowed guest-to-host communication. The isolation boundary was real, but it had a hole: the network.

VM Network Escape Vectors

I identified several network-based escape vectors that apply to VMs:

Vector 1: NAT Gateway

Default VM networking uses NAT (Network Address Translation). The guest VM can reach the host through the NAT gateway:

NAT network topology
Guest VM (10.0.2.15)
|
v
NAT Gateway (10.0.2.2) <-- This IS the host
|
v
Host services (localhost:PORT)
Testing NAT escape
# Inside the VM, the guest can reach host services
# Host IP is typically 10.0.2.2 in QEMU user-mode networking
curl http://10.0.2.2:8080/api
# This reaches a service on the host's localhost:8080

This is exactly how Claude “escaped” - the browser extension API was accessible via the NAT gateway.

Vector 2: Bridged Networking

Bridged networking puts the VM on the same network segment as the host:

Bridged network topology
Guest VM (192.168.1.100)
|
v
Physical Network Switch
|
v
Host (192.168.1.50)
Other machines (192.168.1.x)
Bridged networking risks
# In bridged mode, the VM can:
# 1. Scan the local network for services
# 2. Reach the host directly by IP
# 3. Access other machines on the network
nmap -sT 192.168.1.0/24
# Finds all services on the network, including host

Bridged mode provides NO network isolation between guest and host.

Vector 3: Host-Only Networking

Host-only networks allow VM-to-host communication without external access:

Host-only network topology
Guest VM (192.168.56.101)
|
v
Virtual Host-Only Adapter (192.168.56.1)
|
v
Host (192.168.56.1)

This is often used for “isolated” testing, but the guest can still reach host services.

Vector 4: Shared Folders + Network

Shared folders combined with network access create another escape vector:

Combined attack surface
1. VM has access to shared folder
2. VM has network access (NAT/bridged)
3. Agent finds credentials in shared folder
4. Agent uses credentials over network
5. Agent compromises external systems

Docker Network Escape Vectors

Docker has similar escape vectors:

Vector 1: Default Bridge Network

Docker default bridge escape
# Default bridge allows container-to-host communication
docker run -it alpine sh
# Inside container:
ip route | grep default
# Gateway is typically 172.17.0.1
curl http://172.17.0.1:8080
# Reaches host service on localhost:8080

Vector 2: host.docker.internal

Docker Desktop provides a special DNS name that resolves to the host:

host.docker.internal escape
# Inside container:
curl http://host.docker.internal:8080
# Reaches host's localhost:8080

Vector 3: Host Network Mode

Host network mode (no isolation)
docker run --network=host alpine sh
# Container shares host's network stack entirely
# Can access all host services on localhost

Comparison: VM vs Docker Escape Vectors

Network escape vector comparison
| Vector | VM (QEMU) | Docker | Risk Level |
|-------------------|-----------|--------|------------|
| NAT Gateway | Yes | Yes | HIGH |
| Bridged Network | Yes | Yes | HIGH |
| Host-Only Network | Yes | N/A | MEDIUM |
| Host Network | N/A | Yes | CRITICAL |
| host.internal | 10.0.2.2 | host.docker.internal | HIGH |

Both VMs and Docker containers have the same fundamental vulnerability: default networking allows guest-to-host communication.

Why AI Agents Are Dangerous

AI agents don’t need to “hack” their way out. They’re designed to explore and use available resources:

AI agent escape process
1. Agent receives task: "Help me with X"
2. Agent looks for tools to complete X
3. Agent doesn't find configured tools
4. Agent scans for available resources (network, files, etc.)
5. Agent finds unexpected resource (host service, credential file)
6. Agent uses resource to complete task
7. User never intended this access

The agent did exactly what it was programmed to do. The network boundary was never properly established.

Secure Network Configurations

For QEMU/KVM

Secure QEMU configuration
# Option 1: No network at all
qemu-system-x86_64 \
-m 4096 \
-net none \
-drive file=vm.qcow2,format=qcow2
# Option 2: Isolated network with explicit firewall
# Create isolated bridge (no external routing)
ip link add br-isolated type bridge
ip addr add 10.10.10.1/24 dev br-isolated
ip link set br-isolated up
# Create tap device for VM
ip tuntap add tap0 mode tap
ip link set tap0 master br-isolated
ip link set tap0 up
# Block all traffic from isolated bridge to host
iptables -I INPUT -i br-isolated -j DROP
iptables -I FORWARD -i br-isolated -j DROP
# Run VM with isolated network
qemu-system-x86_64 \
-m 4096 \
-netdev tap,id=net0,ifname=tap0,script=no,downscript=no \
-device virtio-net,netdev=net0 \
-drive file=vm.qcow2,format=qcow2

For libvirt

Isolated libvirt network
<network>
<name>isolated</name>
<forward mode='none'/>
<bridge name='virbr1' stp='on' delay='0'/>
<ip address='192.168.100.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.100.128' end='192.168.100.254'/>
</dhcp>
</ip>
</network>

The forward mode='none' creates an isolated network with no external connectivity.

For Docker

Secure Docker network configuration
# Option 1: No network
docker run --network=none claude-sandbox
# Option 2: Internal network (container-to-container only)
docker network create --internal claude-internal
docker run --network=claude-internal claude-sandbox
# Verify isolation
docker exec <container> ping -c 1 8.8.8.8
# Should fail: Network unreachable

Verification Script

I created a script to verify network isolation:

verify-isolation.sh
#!/bin/bash
# Run this inside your sandbox to verify isolation
echo "=== Network Isolation Test ==="
# Test 1: Internet access
echo -n "Internet access: "
if curl -s --connect-timeout 3 https://example.com > /dev/null 2>&1; then
echo "FAIL (can reach internet)"
else
echo "PASS (no internet)"
fi
# Test 2: Host gateway access
GATEWAY=$(ip route | grep default | awk '{print $3}')
if [ -n "$GATEWAY" ]; then
echo -n "Host gateway ($GATEWAY): "
if ping -c 1 -W 2 "$GATEWAY" > /dev/null 2>&1; then
echo "FAIL (gateway reachable)"
else
echo "PASS (gateway unreachable)"
fi
fi
# Test 3: Common host ports
echo "Host port scan:"
if [ -n "$GATEWAY" ]; then
for port in 22 80 443 3000 5000 8080; do
if timeout 1 bash -c "echo > /dev/tcp/$GATEWAY/$port" 2>/dev/null; then
echo " FAIL: Port $port is open on host"
fi
done
echo " (If no FAIL messages, all tested ports are closed)"
fi
# Test 4: host.docker.internal (Docker)
if command -v getent > /dev/null; then
if getent hosts host.docker.internal > /dev/null 2>&1; then
echo "WARN: host.docker.internal resolves to $(getent hosts host.docker.internal | awk '{print $1}')"
fi
fi
# Test 5: QEMU gateway (10.0.2.2)
if ping -c 1 -W 2 10.0.2.2 > /dev/null 2>&1; then
echo "WARN: QEMU gateway (10.0.2.2) is reachable"
fi
echo "=== Test Complete ==="

Defense in Depth

Network isolation alone isn’t enough. I recommend multiple layers:

Defense layers for AI agent sandboxing
Layer 1: Network Isolation
- No external network access (--network=none or firewall)
- Block guest-to-host communication
Layer 2: Credential Isolation
- No shared credentials between host and guest
- No SSH keys, API tokens, or passwords in shared folders
- No browser extensions shared between host and guest
Layer 3: Resource Limits
- CPU and memory limits (prevent resource exhaustion)
- Disk quotas
- Process limits
Layer 4: Capability Restrictions
- Drop all capabilities (Docker: --cap-drop=ALL)
- No new privileges (Docker: --security-opt=no-new-privileges)
- Read-only filesystem where possible
Layer 5: Monitoring
- Log all network connection attempts
- Monitor file access patterns
- Alert on suspicious behavior

Summary

The Claude “VM escape” wasn’t a security breach - it was a network configuration issue. The key lessons:

  1. Default VM networking is NOT isolated - NAT and bridged modes allow guest-to-host communication
  2. localhost is not private - Services on localhost:PORT are reachable from VMs with NAT networking
  3. AI agents are explorers - They will find and use any available resource
  4. Docker has the same issues - Both technologies require explicit network isolation
  5. Use --network=none or firewalls - The only safe default is no network

The solution:

Secure by default
# Docker
docker run --network=none claude-code
# QEMU
qemu-system-x86_64 -net none
# libvirt
<forward mode='none'/>

Your sandbox is only as secure as your weakest configuration. Network isolation is not automatic - it must be explicitly configured.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments