Skip to content

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.

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 json
import 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))

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.

Code: https://github.com/aerol-ai/aerolvm-examples/blob/main/customer-facing/gVisor-kernel/dockerRuntime.ts

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 json
import 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

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.

Code: https://github.com/aerol-ai/aerolvm-examples/blob/main/customer-facing/gVisor-kernel/gVisorRuntime.ts

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 json
import 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

  • 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.
  • docker-kernel-probe-result.json with the Docker runtime probe output.
  • gvisor-kernel-probe-result.json with the gVisor runtime probe output.