Eu parei de rodar agentes de IA direto no meu WSL: usando Dev Containers + gVisor com Claude Code e Codex
Hardening AI Coding Agents: A Layered Isolation Strategy with Dev Containers and gVisor
Current Situation Analysis
The transition from conversational AI to autonomous coding agents has fundamentally changed how developers interact with their local environments. Tools like Claude Code, Codex, and OpenCode no longer just suggest snippets; they execute shell commands, modify filesystems, install dependencies, spawn background processes, and fetch external resources. This autonomy dramatically accelerates development velocity, but it also introduces a critical security blind spot: privilege boundary confusion.
Most developers launch these agents directly inside WSL2 terminals or standard Docker containers, assuming the environment provides sufficient isolation. In reality, WSL2 and conventional Docker containers share the host Linux kernel. They namespace processes, filesystems, and networks, but they do not virtualize hardware or intercept system calls at the kernel boundary. When an AI agent runs in this context, it inherits the full user context of the host session. It can read SSH keys, access environment variables containing API secrets, traverse parent directories, and execute destructive commands if misdirected by ambiguous prompts or malicious project files (e.g., crafted .cursorrules or README.md instructions).
This risk is frequently overlooked because:
- Container illusion: Developers equate "running in a container" with "running in a VM." Docker's default
runcruntime provides process isolation, not kernel isolation. - Agent trust bias: Teams assume AI tools are inherently safe because they are vendor-backed, ignoring that agents operate on probabilistic instruction parsing rather than deterministic security policies.
- Workspace sprawl: Modern projects mount host directories into containers. Without explicit volume scoping, agents can escape the intended project boundary.
Production environments have already begun treating AI agents as untrusted workloads. The industry is shifting toward defense-in-depth architectures that combine workspace scoping, syscall interception, and application-level permission models.
WOW Moment: Key Findings
Isolating AI agents isn't about choosing a single tool; it's about stacking complementary boundaries. The following comparison demonstrates how layered isolation drastically reduces the attack surface while maintaining developer ergonomics.
| Approach | Kernel Isolation | Filesystem Boundary | Network Scope | Agent Permission Model | WSL2 Compatibility |
|---|---|---|---|---|---|
| Native Host Terminal | None | Full host access | Unrestricted | Vendor default (often broad) | Native |
| Standard Docker Container | Shared host kernel | Scoped to container + mounts | Bridge/NAT | Vendor default | Native |
| Dev Container + gVisor (ptrace) + Agent Sandbox | User-space kernel interception | Strict workspace mount | Controllable via runtime | Explicit sandbox/approval modes | Requires ptrace platform |
Why this matters: gVisor intercepts system calls and implements a subset of the Linux kernel in user space. This means even if an agent executes a malicious or misconfigured syscall, it never reaches the host kernel. Combined with Dev Containers (which scope the workspace) and agent-native sandboxes (which enforce least-privilege execution), the blast radius of a compromised or misdirected agent drops from host-level to container-scoped, with syscall-level filtering as a fallback. This architecture enables teams to adopt AI agents aggressively without compromising host integrity or violating compliance baselines.
Core Solution
The implementation follows a defense-in-depth pattern: workspace scoping β syscall interception β application-level restrictions. Each layer compensates for the limitations of the others.
Step 1: Install and Register gVisor Runtime on WSL2/Linux
gVisor provides the runsc binary. On WSL2, the default systrap platform frequently fails with EOF errors due to ptrace and signal handling differences in the WSL2 kernel. The ptrace platform trades minor performance overhead for significantly higher compatibility.
# Download and extract runsc
curl -fsSL https://gvisor.dev/archive/master/latest/runsc -o runsc
chmod +x runsc
sudo mv runsc /usr/local/bin/
# Register the ptrace-based runtime with Docker
sudo runsc install --runtime runsc-secure -- --platform=ptrace
sudo systemctl restart docker || sudo service docker restart
Rationale: Explicitly naming the runtime runsc-secure avoids conflicts with default runc. The ptrace flag ensures syscall interception works reliably under WSL2's translation layer.
Step 2: Configure Docker Daemon for Fallback Safety
To prevent silent fallback to runc when runsc-secure is unavailable, configure Docker to enforce the runtime or fail explicitly.
// /etc/docker/daemon.json
{
"runtimes": {
"runsc-secure": {
"path": "/usr/local/bin/runsc",
"runtimeArgs": [
"--platform=ptrace"
]
}
},
"default-runtime": "runc",
"log-level": "warn"
}
Rationale: Keeping runc as default preserves compatibility for non-AI workloads. The explicit runtime configuration ensures Dev Containers can target runsc-secure without affecting other containers.
Step 3: Build the Isolated Dev Container Configuration
Create a workspace-scoped Dev Container that injects the gVisor runtime and restricts volume mounts to the project directory only.
// .devcontainer/secure-agent.json
{
"name": "ai-coding-sandbox",
"image": "mcr.microsoft.com/devcontainers/base:jammy",
"runArgs": [
"--runtime=runsc-secure",
"--network=bridge",
"--cap-drop=ALL",
"--security-opt=no-new-privileges:true"
],
"mounts": [
"type=bind,source=${localWorkspaceFolder},target=/sandbox/workspace,readonly=false"
],
"features": {
"ghcr.io/devcontainers/features/node:1": {
"nodeVersion": "20.18"
},
"ghcr.io/devcontainers/features/git:1": {
"version": "latest"
}
},
"postCreateCommand": "npm install -g corepack@latest && corepack enable",
"customizations": {
"vscode": {
"extensions": [
"ms-azuretools.vscode-docker",
"ms-vscode.vscode-typescript-next",
"dbaeumer.vscode-eslint"
]
}
},
"remoteUser": "vscode",
"workspaceFolder": "/sandbox/workspace"
}
Rationale:
--cap-drop=ALLand--security-opt=no-new-privileges:truestrip Linux capabilities, preventing privilege escalation inside the container.--network=bridgeisolates networking from the host. Agents cannot directly bind to host ports or access host services.- Explicit
mountsconfiguration prevents accidental inheritance of parent directories or sensitive host paths. workspaceFolderforces VS Code to operate strictly within/sandbox/workspace.
Step 4: Configure Agent-Level Sandboxing
Layer application restrictions on top of the runtime boundary.
Claude Code: Enable the native sandbox to restrict shell execution.
# Inside the container terminal
claude /sandbox
# Select: "Sandbox BashTool, with regular permissions"
This routes all command execution through bubblewrap and socat, enforcing filesystem and network constraints at the process level.
Codex: Enforce approval-based execution.
# Configure via environment or CLI flag
export CODEX_PERMISSION_MODE="default"
codex --require-approval
The default mode restricts internet access and out-of-scope file modifications, requiring explicit user confirmation for elevated actions.
Step 5: Verify Runtime and Boundary Enforcement
Always validate that gVisor is active and the workspace is correctly scoped.
# Check active container runtime
CONTAINER_ID=$(docker ps -q --filter "ancestor=mcr.microsoft.com/devcontainers/base:jammy" | head -n 1)
docker inspect "$CONTAINER_ID" --format '{{.HostConfig.Runtime}}'
# Expected output: runsc-secure
# Verify workspace isolation
docker exec "$CONTAINER_ID" pwd
# Expected output: /sandbox/workspace
# Test syscall interception (should fail gracefully)
docker exec "$CONTAINER_ID" nsenter --target 1 --mount --uts --ipc --net --pid -- ls /
# Expected: permission denied or container-scoped output
Rationale: Silent runtime fallback is a common production failure. Explicit verification ensures the isolation layer is active before granting agent autonomy.
Pitfall Guide
| Pitfall | Explanation | Fix |
|---|---|---|
| Assuming Docker Equals Virtualization | Standard containers share the host kernel. A compromised agent can exploit kernel vulnerabilities or escape via misconfigured capabilities. | Use gVisor, Kata Containers, or Firecracker for kernel-level isolation. Never treat runc as a security boundary. |
| Ignoring WSL2 Platform Limitations | systrap relies on low-level syscall interception that conflicts with WSL2's translation layer, causing EOF crashes. |
Explicitly register ptrace platform. Accept minor performance trade-off for stability. |
| Mounting Host Directories Implicitly | VS Code Dev Containers sometimes inherit parent paths or mount /home recursively, exposing SSH keys, .env files, and shell history. |
Use explicit mounts configuration. Never rely on default volume inheritance. Audit mount points with docker inspect. |
| Over-Reliance on Agent "Full Access" Modes | Flags like --dangerously-skip-permissions or Full Access bypass all safety checks, granting unrestricted filesystem and network control. |
Enforce sandbox or approval modes. Reserve full access for disposable CI runners, never local dev environments. |
| Skipping Runtime Verification | Docker may silently fall back to runc if runsc is missing or misconfigured, leaving agents unisolated. |
Add runtime verification to post-start hooks. Fail fast if runsc-secure is not active. |
| Neglecting Network Egress Controls | Agents can fetch external resources, exfiltrate data, or interact with internal services if network namespaces are unrestricted. | Use --network=bridge or custom Docker networks. Implement egress proxies or firewall rules for sensitive environments. |
| Mixing Host and Container Toolchains | Running host-installed Node/Python inside the container causes path conflicts, permission errors, and broken symlinks. | Use Dev Container features exclusively. Remove host toolchain references from $PATH. |
Production Best Practice: Treat AI agents as untrusted processes. Apply the same security posture you would use for third-party plugins or user-submitted scripts. Layer isolation, enforce least privilege, and verify boundaries programmatically.
Production Bundle
Action Checklist
- Install gVisor
runscbinary and registerptraceruntime with Docker - Configure
daemon.jsonto support explicit runtime targeting without breaking default workloads - Create
.devcontainer/secure-agent.jsonwith capability drops and explicit mount scoping - Enable Claude Code sandbox or Codex approval mode before first execution
- Add runtime verification script to container lifecycle hooks
- Restrict network access to bridge mode; configure egress proxies if required
- Audit volume mounts to ensure only
${localWorkspaceFolder}is exposed - Document agent permission policies and enforce them via team Dev Container templates
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|---|---|---|
| Local prototyping with trusted repos | Dev Container + standard runc + agent sandbox |
Fast iteration; kernel isolation not critical | Low (standard Docker) |
| Untrusted third-party code or open-source contributions | Dev Container + gVisor (ptrace) + strict sandbox |
Prevents syscall exploitation and filesystem escape | Medium (gVisor overhead ~10-15%) |
| CI/CD pipeline execution | Ephemeral containers + network isolation + approval gates | Prevents supply chain attacks and credential leakage | Low (cloud compute scales) |
| Compliance-heavy environments (SOC2, HIPAA) | gVisor/microVM + egress proxy + audit logging | Meets kernel isolation and data exfiltration controls | High (infrastructure + monitoring) |
Configuration Template
// .devcontainer/ai-isolation.json
{
"name": "secure-ai-workspace",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04",
"runArgs": [
"--runtime=runsc-secure",
"--cap-drop=ALL",
"--security-opt=no-new-privileges:true",
"--network=bridge"
],
"mounts": [
"type=bind,source=${localWorkspaceFolder},target=/ai-sandbox/project"
],
"workspaceFolder": "/ai-sandbox/project",
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": "true",
"upgradePackages": "true"
},
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"version": "latest",
"moby": "true"
}
},
"postCreateCommand": "mkdir -p /ai-sandbox/logs && chmod 700 /ai-sandbox/logs",
"customizations": {
"vscode": {
"extensions": [
"ms-azuretools.vscode-docker",
"ms-vscode-remote.remote-containers"
]
}
},
"remoteUser": "vscode"
}
Quick Start Guide
- Install gVisor: Download
runsc, make it executable, and register theptraceruntime with Docker. Restart the Docker service. - Initialize Workspace: Copy the configuration template to
.devcontainer/ai-isolation.jsonin your project root. - Launch Container: Open VS Code, run
Dev Containers: Reopen in Container, and select the new configuration. - Verify Isolation: Execute the runtime verification command. Confirm output matches
runsc-secureandpwdreturns/ai-sandbox/project. - Start Agent: Launch Claude Code or Codex inside the container terminal. Enable sandbox/approval mode. Begin development with enforced boundaries.
Mid-Year Sale β Unlock Full Article
Base plan from just $4.99/mo or $49/yr
Sign in to read the full article and unlock all tutorials.
Sign In / Register β Start Free Trial7-day free trial Β· Cancel anytime Β· 30-day money-back
