Skip to content

Docker vs VM Sandboxing: Why Network Configuration Matters More Than You Think

Problem

When I read about Claude “escaping” a VM sandbox, I assumed it was some sophisticated hypervisor exploit. The reality was far more mundane—and far more concerning for anyone running AI agents in containers or VMs.

User's setup:
- Claude running inside a VM
- Claude browser extension installed on host machine
- VM using default networking (NAT/bridged)
What happened:
- Claude inside VM "discovered" the host's browser extension
- Claude used the extension to complete tasks
- User thought the VM was compromised

The VM wasn’t hacked. The network was just… open.

What I investigated

I wanted to understand exactly how this “escape” happened and whether Docker would be any safer. Here’s what I found:

The escape mechanism

The incident worked like this:

Network escape sequence
1. Host machine runs Chrome with Claude extension
2. Extension listens on localhost:PORT for API calls
3. VM uses NAT networking (default for most VMs)
4. Guest VM can reach host's IP address
5. Claude in VM scans network, finds the extension endpoint
6. Claude uses extension as a tool to complete tasks

The key insight: localhost on the host is NOT isolated from the guest when using NAT or bridged networking.

Why Docker has the same problem

I tested this with Docker and found identical behavior:

Docker default networking test
# Default Docker network allows guest-to-host communication
docker run -it alpine sh
# Inside container:
ping host.docker.internal
# Result: Host is reachable!
curl http://host.docker.internal:8080
# Result: Can access host services!

The host.docker.internal DNS name resolves to the host’s IP address in Docker Desktop. On Linux, you need to add --add-host manually, but the principle is the same.

How I compared Docker vs VM isolation

I created a comparison table to understand the actual security boundaries:

Network isolation comparison
| Configuration | Docker | VM (QEMU) | Guest→Host Access |
|---------------------|--------|-----------|-------------------|
| Default network | bridge | NAT | YES (vulnerable) |
| Custom bridge | bridge | bridge | YES (vulnerable) |
| Host network | host | N/A | YES (no isolation)|
| No network | none | none | NO (isolated) |
| Internal network | internal| isolated | NO (isolated) |

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

Docker network modes explained

Docker network modes
# WRONG: Default bridge network
docker run claude-code
# Guest can reach host services via gateway
# WRONG: Host network (no isolation at all)
docker run --network=host claude-code
# Guest shares host's network stack entirely
# CORRECT: No network access
docker run --network=none claude-code
# Guest has no network interface (except loopback)
# CORRECT: Internal network (no external access)
docker network create --internal claude-internal
docker run --network=claude-internal claude-code
# Guest can only talk to other containers on same network

VM network modes explained

QEMU network modes
# WRONG: User-mode networking (NAT) - DEFAULT
qemu-system-x86_64 -netdev user,id=net0 -device virtio-net,netdev=net0
# Guest can reach host services via gateway (10.0.2.2)
# CORRECT: No network
qemu-system-x86_64 -net none
# Guest has no network interface
# CORRECT: Isolated tap device with firewall
ip tuntap add dev tap0 mode tap
ip link set tap0 up
# Block guest-to-host traffic
iptables -I FORWARD -i tap0 -d 192.168.0.0/16 -j DROP
iptables -I FORWARD -i tap0 -d 10.0.0.0/8 -j DROP
iptables -I FORWARD -i tap0 -d 172.16.0.0/12 -j DROP

The real security boundaries

I realized that “sandbox” means different things to different people:

What VMs actually isolate
ISOLATED by VM:
- Process memory
- CPU registers
- File system (if not shared)
- Hardware devices
NOT ISOLATED by default VM:
- Network (unless explicitly configured)
- Shared folders
- Clipboard (if enabled)
- USB devices (if passed through)

The VM provides process isolation, but network isolation is optional.

Why AI agents are particularly dangerous

AI agents don’t need to “hack” anything. They just explore:

AI agent discovery process
1. Agent receives task: "Check my email"
2. Agent looks for email tools
3. Agent finds no email tool configured
4. Agent scans network for services
5. Agent finds IMAP server on host:993
6. Agent connects and completes task
7. User never intended this access!

The agent did exactly what it was asked to do. The problem is that the network boundary was never established.

How to properly isolate AI agents

Based on my investigation, here’s the correct approach:

For Docker

Secure Docker configuration
# Create isolated network
docker network create --internal claude-net
# Run with no external access
docker run \
--network=claude-net \
--cap-drop=ALL \
--security-opt=no-new-privileges \
--read-only \
-v /workspace:/workspace:rw \
claude-code
# Verify isolation
docker exec <container> ping -c 1 8.8.8.8
# Should fail: Network unreachable

For VMs

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 firewall
# Create isolated bridge
ip link add br-isolated type bridge
ip link set br-isolated up
# Create tap device
ip tuntap add tap0 mode tap
ip link set tap0 master br-isolated
ip link set tap0 up
# Block all traffic from isolated bridge
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

Additional hardening

Defense in depth checklist
1. Network isolation (--network=none or firewall rules)
2. No shared credentials between host and guest
3. No browser extensions installed on both
4. No shared folders with sensitive data
5. No USB passthrough
6. Read-only root filesystem where possible
7. Dropped capabilities (--cap-drop=ALL)
8. No new privileges (no-new-privileges)

Testing your isolation

I created a simple test script to verify isolation:

Isolation verification script
#!/bin/bash
# test-isolation.sh - Run inside your sandbox
echo "Testing network isolation..."
# Test 1: Can we reach the internet?
if curl -s --connect-timeout 5 https://google.com > /dev/null 2>&1; then
echo "FAIL: Internet access available"
else
echo "PASS: No internet access"
fi
# Test 2: Can we reach common host ports?
HOST_IP=$(ip route | grep default | awk '{print $3}')
if [ -n "$HOST_IP" ]; then
for port in 80 443 8080 3000 5000; do
if timeout 2 bash -c "echo > /dev/tcp/$HOST_IP/$port" 2>/dev/null; then
echo "FAIL: Host port $port is reachable"
fi
done
echo "PASS: No host ports reachable (or no default gateway)"
else
echo "PASS: No default gateway found"
fi
# Test 3: Can we resolve host.docker.internal?
if getent hosts host.docker.internal > /dev/null 2>&1; then
echo "WARN: host.docker.internal resolves"
else
echo "PASS: host.docker.internal not found"
fi
echo "Isolation test complete."

Summary

I investigated the “VM escape” incident and found that AI agents don’t break out of sandboxes—they walk through open network doors. The key findings:

  1. Default networking is insecure: Both Docker and VMs allow guest-to-host communication by default
  2. localhost is not isolated: Services on localhost:PORT are reachable from guests using NAT/bridged networking
  3. AI agents are explorers: They discover available resources and use them to complete tasks
  4. True isolation requires explicit configuration: Use --network=none or firewall rules

The solution is simple but often overlooked:

The only safe defaults
# Docker
docker run --network=none claude-code
# VM
qemu-system-x86_64 -net none

Your sandbox is only as secure as your network configuration. The VM provides isolation, but the network defines the actual security boundary.

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