gVisor Kernel Isolation Security
This page shows a safe kernel-surface probe. The same Python probe runs twice: once in a direct Docker runtime and once in a gVisor runtime. The probe attempts host-facing privileged operations such as unshare, mount, and dmesg, records what happened, and writes a JSON report.
runtime: "docker"lets you show the baseline container model where workload syscalls target the host kernel directly.runtime: "gvisor"swaps that direct host-kernel path for gVisor's user-space kernel boundary.- The same probe can run in both runtimes with only one field changed, which makes the comparison concrete and easy to explain.
- Currently, both Daytona and E2B use direct Docker isolation. This demo shows how gVisor's kernel mediation provides a stronger security boundary for untrusted code, which is why we are building it as a core runtime option for AerolVM.
Shared probe payload
Section titled “Shared probe payload”This Python payload is the same for both examples. It is a defensive probe, not an exploit. It tries a few privileged operations and records whether they succeeded, failed with EPERM, or were blocked in another way.
import jsonimport subprocess
checks = [ ("capabilities", "sh -lc 'grep ^CapEff: /proc/self/status'"), ("unshare_namespaces", "unshare --mount --uts --ipc --net true"), ("mount_tmpfs", "sh -lc 'mkdir -p /tmp/probe-mount && mount -t tmpfs tmpfs /tmp/probe-mount'"), ("read_dmesg", "sh -lc 'dmesg | head -n 5'"),]
results = []for name, command in checks: completed = subprocess.run(command, shell=True, capture_output=True, text=True) results.append( { "check": name, "command": command, "exitCode": completed.returncode, "stdout": completed.stdout.strip(), "stderr": completed.stderr.strip(), } )
payload = {"results": results}
with open("/workspace/run/probe-report.json", "w", encoding="utf-8") as handle: json.dump(payload, handle, indent=2)
print(json.dumps(payload, indent=2))1. Direct Docker runtime
Section titled “1. Direct Docker runtime”This script uses runtime: "docker". The probe is still isolated by container boundaries, but the syscalls it makes are handled by the host kernel. That direct kernel exposure is the security difference gVisor is designed to reduce.
import { MicroVM } from "@aerol-ai/aerolvm-sdk";import { writeFile } from "node:fs/promises";
const apiUrl = process.env.SB_API_URL ?? "http://127.0.0.1:21212";const patToken = process.env.SB_PAT_TOKEN;
if (!patToken) { throw new Error("Set SB_PAT_TOKEN before running this example.");}
const probePython = `import jsonimport subprocess
# These checks probe kernel information exposure.# In a Docker (runc) container the process shares the real host kernel, so# /proc surfaces the true kernel version, hardware memory layout, and I/O# regions - everything an attacker needs to fingerprint and target known CVEs.# Under gVisor the same reads return synthetic data from the Sentry user-space# kernel: the host kernel version, memory layout, and hardware topology are# never visible inside the sandbox.checks = [ ("capabilities", "sh -lc 'grep ^CapEff: /proc/self/status'"), ("kernel_version", "uname -r"), ("kernel_build", "cat /proc/version"), ("physical_memory_buddy", "cat /proc/buddyinfo"), ("hardware_io_regions", "cat /proc/iomem"), ("net_stats", "cat /proc/net/sockstat"),]
results = []for name, command in checks: completed = subprocess.run(command, shell=True, capture_output=True, text=True) results.append( { "check": name, "command": command, "exitCode": completed.returncode, "stdout": completed.stdout.strip(), "stderr": completed.stderr.strip(), } )
payload = {"runtime": "docker", "results": results}
with open("/workspace/run/probe-report.json", "w", encoding="utf-8") as handle: json.dump(payload, handle, indent=2)
print(json.dumps(payload, indent=2))`;
async function main() { const client = new MicroVM({ apiUrl, patToken }); const sandbox = await client.create({ image: "python:3.12-bookworm", runtime: "docker", networkBlockAll: true, cpu: 0.5, memoryMB: 256, diskGB: 4, });
try { await sandbox.exec("mkdir -p /workspace/run"); await sandbox.uploadFile("/workspace/run/probe.py", probePython);
const result = await sandbox.exec({ command: "python /workspace/run/probe.py", timeoutSeconds: 10, });
const report = new TextDecoder().decode( await sandbox.downloadFile("/workspace/run/probe-report.json"), );
const payload = { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode, report: JSON.parse(report), };
await writeFile( "docker-kernel-probe-result.json", `${JSON.stringify(payload, null, 2)}\n`, );
console.log(JSON.stringify(payload, null, 2)); } finally { await sandbox.destroy(); }}
main().catch((error) => { console.error(error); process.exitCode = 1;});Sample output: https://github.com/aerol-ai/aerolvm-examples/blob/main/customer-facing/gVisor-kernel/docker-kernel-probe-result.json
2. gVisor runtime
Section titled “2. gVisor runtime”This script runs the same probe with runtime: "gvisor". The goal is not that every check suddenly succeeds or fails differently. The point is that the workload is now talking to gVisor's user-space kernel layer instead of directly exercising host-kernel paths.
import { MicroVM } from "@aerol-ai/aerolvm-sdk";import { writeFile } from "node:fs/promises";
const apiUrl = process.env.SB_API_URL ?? "http://127.0.0.1:21212";const patToken = process.env.SB_PAT_TOKEN;
if (!patToken) { throw new Error("Set SB_PAT_TOKEN before running this example.");}
const probePython = `import jsonimport subprocess
# Same checks as the Docker probe so the two result files can be diffed# side-by-side. Under gVisor every kernel-exposure check should return either# empty output or a synthetic value - the Sentry user-space kernel never# forwards these reads to the real host kernel, so the host's identity,# hardware layout, and memory topology remain invisible to sandbox code.checks = [ ("capabilities", "sh -lc 'grep ^CapEff: /proc/self/status'"), ("kernel_version", "uname -r"), ("kernel_build", "cat /proc/version"), ("physical_memory_buddy", "cat /proc/buddyinfo"), ("hardware_io_regions", "cat /proc/iomem"), ("net_stats", "cat /proc/net/sockstat"),]
results = []for name, command in checks: completed = subprocess.run(command, shell=True, capture_output=True, text=True) results.append( { "check": name, "command": command, "exitCode": completed.returncode, "stdout": completed.stdout.strip(), "stderr": completed.stderr.strip(), } )
payload = {"runtime": "gvisor", "results": results}
with open("/workspace/run/probe-report.json", "w", encoding="utf-8") as handle: json.dump(payload, handle, indent=2)
print(json.dumps(payload, indent=2))`;
async function main() { const client = new MicroVM({ apiUrl, patToken }); const sandbox = await client.create({ image: "python:3.12-bookworm", runtime: "gvisor", networkBlockAll: true, cpu: 0.25, memoryMB: 256, diskGB: 4, });
try { await sandbox.exec("mkdir -p /workspace/run"); await sandbox.uploadFile("/workspace/run/probe.py", probePython);
const result = await sandbox.exec({ command: "python /workspace/run/probe.py", timeoutSeconds: 10, });
const report = new TextDecoder().decode( await sandbox.downloadFile("/workspace/run/probe-report.json"), );
const payload = { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode, report: JSON.parse(report), };
await writeFile( "gvisor-kernel-probe-result.json", `${JSON.stringify(payload, null, 2)}\n`, );
console.log(JSON.stringify(payload, null, 2)); } finally { await sandbox.destroy(); }}
main().catch((error) => { console.error(error); process.exitCode = 1;});Sample output: https://github.com/aerol-ai/aerolvm-examples/blob/main/customer-facing/gVisor-kernel/gvisor-kernel-probe-result.json
How to explain the result
Section titled “How to explain the result”- In the direct Docker case, the probe is still attempting privileged operations against the host-kernel interface.
- In the gVisor case, the same workload is mediated by gVisor, so the host kernel is not directly exposed to the same syscall surface.
- This page is intentionally a safety demo, not an exploit demo. It shows why gVisor is a better boundary for untrusted user code without shipping breakout code.
Product outputs
Section titled “Product outputs”docker-kernel-probe-result.jsonwith the Docker runtime probe output.gvisor-kernel-probe-result.jsonwith the gVisor runtime probe output.