Membrane: Building a Transparent Sandbox for AI Agents with eBPF and Nested Containers
Hook
Most AI agent sandboxes are either black boxes you can't audit or require privileged containers that defeat the purpose of isolation. Membrane sidesteps both traps with a minimalist architecture that's 50 times smaller than comparable tools.
Context
AI coding agents have evolved from autocomplete suggestions to autonomous systems that can execute arbitrary commands, install packages, and make network requests. This shift creates a genuine security problem: how do you let an AI agent do useful work without giving it the keys to your entire system?
The traditional answer has been either heavyweight virtualization (slow, resource-intensive) or optimistic trust (dangerous). IDE extensions try to solve this with confirmation dialogs, but that breaks the autonomous workflow. Cloud-based solutions like E2B provide isolation but require sending your code to third-party servers. Meanwhile, general-purpose container security tools like AppArmor and SELinux require writing complex policies and provide limited visibility into what's actually happening. Membrane takes a different approach: treat the AI agent like a Unix process, make the sandbox transparent and auditable, and use kernel-level observability to see everything it does.
Technical Insight
Membrane's architecture consists of two containers orchestrated by a lightweight Go binary. The first container, membrane-agent, runs your AI agent in an isolated environment using Sysbox, a container runtime that enables unprivileged Docker-in-Docker. The second container, membrane-handler, acts as both a network proxy and policy enforcement point. This separation means the agent can't bypass network restrictions even if it compromises its own container.
The network filtering is particularly clever. Rather than trying to implement firewall rules inside the agent container, Membrane forces all traffic through the handler container using Docker networking. The handler runs mitmproxy with custom logic to allow or deny requests based on configurable patterns. Here's how you configure network policies in your workspace:
# .membrane/config.yml
network:
allow:
- "*.github.com"
- "pypi.org"
deny:
- "*"
This gets translated into proxy rules that intercept every HTTP/HTTPS request. The agent container's network is configured so it has no direct route to the internet—everything must traverse the handler's proxy. If the agent tries to bypass this by manipulating iptables or creating raw sockets, it hits the unprivileged container boundary. Sysbox ensures the nested containers run without the --privileged flag, so the agent can spawn its own containers (useful for testing) without gaining host access.
The observability layer uses Tracee, an eBPF-based runtime security tool. eBPF programs run in the kernel and can trace every syscall without modifying application code or requiring privileged containers. Membrane captures filesystem operations, network connections, and process execution:
# Start membrane with eBPF tracing enabled
membrane start --trace
# In another terminal, attach to see live syscall events
membrane attach --trace
# Example output:
# 2024-01-15 10:23:45 [openat] /home/agent/.bashrc
# 2024-01-15 10:23:46 [connect] 140.82.121.4:443 (github.com)
# 2024-01-15 10:23:47 [execve] /usr/bin/python3 script.py
This kernel-level visibility is Membrane's killer feature. Unlike application-level logging that can be disabled or manipulated, eBPF traces are collected by the kernel itself. The agent can't hide its behavior even if it's actively trying to evade detection.
The filesystem isolation uses a layered approach. The agent container starts with read-only mounts for system directories, and uses .membraneignore files (similar to .gitignore) to prevent access to sensitive paths:
# .membrane/.membraneignore
.env
*.key
*.pem
secrets/
.git/config
On macOS, Membrane runs inside a Colima VM because eBPF requires a Linux kernel. The CLI handles this transparently—when you run membrane start on macOS, it first checks if Colima is running, starts the VM if needed, and configures Docker contexts to use the VM's daemon. On Linux, it uses the system Docker daemon directly. This cross-platform approach sacrifices some performance for consistency, but it means the same security boundaries apply regardless of developer OS.
The codebase is remarkably small—under 1000 lines of Go versus over 50,000 for comparable tools like OpenShell. This minimalism is intentional. Every line of security code is a potential vulnerability, so keeping the codebase auditable is itself a security feature. The core logic is straightforward: parse config, start containers with appropriate volumes and network settings, stream logs, and clean up on exit. Complex features like protocol-aware filtering or ML-based anomaly detection are explicitly out of scope.
Gotcha
The Docker dependency is both Membrane's strength and its weakness. Running containers inside containers adds latency—expect 2-3 second startup times even for simple commands. On macOS, the Colima VM layer adds another 1-2GB of memory overhead and requires careful resource allocation. If your CI environment doesn't allow Docker-in-Docker (like some managed GitLab runners), Membrane won't work without infrastructure changes.
The network filtering happens at the HTTP proxy layer, which means it can't catch traffic that doesn't use standard protocols. If an agent makes raw TCP connections or uses custom protocols, those packets still flow through the handler container but aren't subject to allow/deny rules. The handler would need protocol-specific logic for each network protocol you want to filter. Similarly, while eBPF tracing provides incredible visibility, it generates a lot of data. Running Tracee continuously can produce gigabytes of logs for long-running agents, and there's no built-in log rotation or filtering. You'll need to pipe the trace output to your own analysis tools.
Verdict
Use Membrane if you're running AI agents programmatically or in CI/CD pipelines where you need reproducible, auditable sandboxing with strong observability. It's ideal for teams that value minimalist, inspectable codebases and want Unix-native integration (piping, scripting, no IDE lock-in). The eBPF tracing is invaluable for security audits or debugging agent behavior. Skip it if you need native OS integration without Docker overhead, can't tolerate 2-3 second startup latency, or require advanced features like protocol-aware filtering for non-HTTP traffic. Also skip if you're on Windows—while technically possible via WSL2, the layering makes it impractical. For production workloads with strict SLAs, the Docker-in-Docker architecture may be too heavyweight; consider Firecracker or gVisor for stronger isolation with less overhead.