Lesson 6: How the Sandbox is Technically Made
This lesson provides a deep technical dive into OpenShell's sandbox architecture, exploring the multiple layers of Linux kernel security features that create a defense-in-depth isolation model for AI agent execution.
1. Container Technology Foundation
1.1 Runtime Options
OpenShell supports multiple container runtimes through configurable compute drivers:
| Driver | Use Case | Isolation Level |
|---|---|---|
| Docker | Local development, standard deployments | Container-level |
| Podman | Rootless containers, CI/CD pipelines | Container-level |
| MicroVM | High-security workloads | Hardware-virtualized |
| Kubernetes | Production orchestration | Pod-level with securityContext |
Source: NVIDIA OpenShell GitHub Repository
1.2 How OpenShell Differs from Standard Docker
While OpenShell uses Docker as a compute driver, it adds several security layers that standard Docker containers lack:
- Landlock LSM enforcement - Kernel-level filesystem restrictions beyond container mounts
- Gateway-based network interception - All egress routed through policy engine
- Binary-scoped network policies - Rules apply per-executable, not per-container
- Aggressive capability dropping - Drops 15+ capabilities vs. Docker's default retention
- Build-time hardening - Compiler toolchains removed from runtime image
# Standard Docker run
docker run -it ubuntu bash
# OpenShell sandbox creation - adds multiple security layers automatically
openshell sandbox create -- claude
2. Linux Kernel Security Features
Defense in Depth Stack
2.1 Linux Namespaces
Namespaces provide process isolation by virtualizing system resources. OpenShell uses several namespace types:
| Namespace | Isolates | OpenShell Usage |
|---|---|---|
pid |
Process IDs | Agent processes cannot see host PIDs |
net |
Network stack | All egress forced through gateway proxy |
mnt |
Mount points | Isolated filesystem view |
user |
User/group IDs | sandbox user isolation from host |
uts |
Hostname/domain | Isolated hostname per sandbox |
ipc |
IPC mechanisms | Isolated shared memory |
Source: OpenClaw Security Documentation
2.2 Control Groups (cgroups)
Cgroups enforce resource limits to prevent denial-of-service attacks:
# OpenShell resource limits
ulimit -u 512 # Maximum 512 processes (best-effort)
ulimit -n 65536 # Maximum 65536 file descriptors
These limits prevent a compromised agent from:
- Fork-bombing the host system
- Exhausting file descriptor resources
- Consuming excessive CPU/memory (when cgroup limits configured)
2.3 Seccomp-BPF (System Call Filtering)
Seccomp-BPF allows filtering system calls using Berkeley Packet Filter programs. OpenShell uses this to block dangerous syscalls:
// Simplified seccomp filter structure
struct seccomp_data {
int nr; // System call number
__u32 arch; // Architecture (x86_64, arm64, etc.)
__u64 instruction_pointer; // Where the call originated
__u64 args[6]; // Syscall arguments
};
// Filter return actions (in precedence order)
SECCOMP_RET_KILL_PROCESS // Terminate immediately
SECCOMP_RET_KILL_THREAD // Kill only calling thread
SECCOMP_RET_TRAP // Send SIGSYS signal
SECCOMP_RET_ERRNO // Return error, don't execute
SECCOMP_RET_LOG // Log and allow
SECCOMP_RET_ALLOW // Permit execution
OpenShell Seccomp Configuration:
- Socket domain filtering enabled
no_new_privsflag set viaprctl()- Enforcement order: namespace entry, privilege drop, Landlock, then seccomp
Source: Linux seccomp(2) man page
2.4 Capabilities Dropping
Linux capabilities divide root privileges into distinct units. OpenShell aggressively drops capabilities using capsh:
# Initial capability drop (entrypoint)
cap_sys_admin # Mount, namespace manipulation
cap_sys_ptrace # Process tracing/debugging
cap_net_raw # Raw socket access
cap_dac_override # Bypass file permission checks
cap_sys_chroot # chroot() syscall
cap_fsetid # Set file SUID/SGID bits
cap_setfcap # Set file capabilities
cap_mknod # Create device nodes
cap_audit_write # Write to audit log
cap_net_bind_service # Bind to ports < 1024
# Additional drop during setpriv step-down
cap_setuid # Change UID
cap_setgid # Change GID
cap_fowner # Bypass file ownership checks
cap_chown # Change file ownership
cap_kill # Send signals to other processes
Setting NEMOCLAW_REQUIRE_CAP_DROP=1 makes these drops fail-closed rather than best-effort.
Source: OpenClaw Security Documentation
3. Landlock LSM (Critical Section)
3.1 What is Landlock?
Landlock is a stackable Linux Security Module (LSM) that enables unprivileged processes to create security sandboxes. Unlike traditional MAC systems (SELinux, AppArmor), Landlock allows applications to sandbox themselves without system administrator involvement.
Traditional vs Landlock Security Models
Key Properties:
- Unprivileged - No root or CAP_SYS_ADMIN required
- Stackable - Up to 16 ruleset layers can be combined
- Permanent - Once enforced, restrictions cannot be removed, only tightened
- Inherited - Child processes inherit parent's Landlock domain
Source: Linux Kernel Landlock Documentation
3.2 How Landlock Enforces Filesystem Restrictions
Landlock operates through three system calls:
// Step 1: Create a ruleset defining what access rights to handle
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs =
LANDLOCK_ACCESS_FS_EXECUTE |
LANDLOCK_ACCESS_FS_WRITE_FILE |
LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_READ_DIR |
LANDLOCK_ACCESS_FS_REMOVE_DIR |
LANDLOCK_ACCESS_FS_REMOVE_FILE |
LANDLOCK_ACCESS_FS_MAKE_CHAR |
LANDLOCK_ACCESS_FS_MAKE_DIR |
LANDLOCK_ACCESS_FS_MAKE_REG |
LANDLOCK_ACCESS_FS_MAKE_SOCK |
LANDLOCK_ACCESS_FS_MAKE_FIFO |
LANDLOCK_ACCESS_FS_MAKE_BLOCK |
LANDLOCK_ACCESS_FS_MAKE_SYM |
LANDLOCK_ACCESS_FS_REFER |
LANDLOCK_ACCESS_FS_TRUNCATE |
LANDLOCK_ACCESS_FS_IOCTL_DEV,
};
int ruleset_fd = landlock_create_ruleset(&ruleset_attr,
sizeof(ruleset_attr), 0);
// Step 2: Add rules for specific paths
struct landlock_path_beneath_attr path_beneath = {
.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_READ_DIR,
.parent_fd = open("/usr", O_PATH | O_CLOEXEC),
};
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
&path_beneath, 0);
// Step 3: Enforce the ruleset (requires no_new_privs first)
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
landlock_restrict_self(ruleset_fd, 0);
close(ruleset_fd);
3.3 OpenShell's Landlock Configuration
OpenShell applies Landlock with a specific filesystem layout:
| Path | Access | Purpose |
|---|---|---|
/usr |
Read-only | System binaries and libraries |
/lib |
Read-only | Shared libraries |
/proc |
Read-only | Process information |
/dev/urandom |
Read-only | Cryptographic randomness |
/app |
Read-only | Application code |
/etc |
Read-only | Configuration files |
/var/log |
Read-only | Log files (read for debugging) |
/sandbox |
Writable | Agent working directory |
/tmp |
Writable | Temporary files |
/dev/null |
Writable | Null device |
/dev/pts |
Writable | Pseudo-terminals |
# OpenShell Landlock configuration (conceptual)
landlock:
compatibility: best_effort # Falls back gracefully on older kernels
read_only:
- /usr
- /lib
- /proc
- /dev/urandom
- /app
- /etc
- /var/log
writable:
- /sandbox
- /tmp
- /dev/null
- /dev/pts
3.4 Why Kernel-Level Enforcement Matters
Landlock enforcement happens inside the kernel, which provides critical security guarantees:
- Bypass-proof - Application code cannot disable or circumvent restrictions
- Race-free - No TOCTOU (time-of-check-time-of-use) vulnerabilities
- Universal - Applies to all access methods (direct syscalls, library calls, etc.)
- Inherited - Child processes automatically sandboxed
Important: Landlock layout changes require sandbox re-creation; they cannot be hot-reloaded at runtime.
Kernel Requirements:
- Minimum kernel: Linux 5.13
- Build config:
CONFIG_SECURITY_LANDLOCK=y - Network rules: Requires kernel 5.18+ (ABI v4) for TCP, 6.8+ (ABI v10) for UDP
# Verify Landlock is active
dmesg | grep landlock
# Should show: "landlock: Up and running"
# Check ABI version
cat /sys/kernel/security/landlock/abi
Source: Landlock Official Documentation, Linux Kernel Documentation
4. Network Interception Architecture
4.1 Gateway Proxy Model
OpenShell routes all sandbox egress through a CONNECT proxy gateway. This is enforced at the network namespace level - the sandbox has no direct internet access.
Gateway Proxy Model
4.2 Binary Identification and Verification
The gateway identifies calling binaries through kernel-trusted mechanisms:
- Read
/proc/<pid>/exe- Kernel-provided executable path (cannot be spoofed) - Walk process tree for ancestor binaries
- Compute SHA256 hash of each binary on first use
- Cache hash and verify on subsequent requests
- Block if binary hash changes (detects runtime replacement attacks)
# Example: Agent process calls curl which makes HTTP request
# Gateway sees:
# - Direct binary: /usr/bin/curl
# - Parent binary: /usr/bin/python3
# - Grandparent: /app/agent.py
# Policy can specify rules for any ancestor in the chain
4.3 Protocol Inspection Levels
OpenShell supports multiple protocol inspection depths:
| Protocol | Inspection Level | What Gets Checked |
|---|---|---|
none (L4-only) |
Transport | Host, port, binary only - TCP relayed without inspection |
rest |
HTTP | Auto-detects/terminates TLS, checks method and path |
websocket |
WebSocket | Matches upgrade requests, methods, paths |
json-rpc |
RPC | Parses JSON-RPC, matches method names |
mcp |
MCP Protocol | Matches tool names and parameter patterns |
4.4 Implementation: eBPF vs iptables
OpenShell's network interception uses a gateway proxy model rather than kernel-level packet filtering:
- Not iptables-based - iptables can only filter at L3/L4, cannot inspect HTTP methods/paths
- Not eBPF-based for policy - eBPF would require elevated privileges in the sandbox
- User-space gateway - Runs as separate
gatewayuser, receives all connections via network namespace routing
The gateway proxy approach allows:
- Deep protocol inspection (HTTP, WebSocket, JSON-RPC, MCP)
- TLS termination for encrypted traffic inspection
- Credential injection for managed inference endpoints
- Rich logging and auditing
# Inside sandbox - all egress goes through gateway
sandbox$ curl -sS https://api.github.com/zen
# Request flow: sandbox -> network namespace -> gateway -> policy check -> internet
# Policy-denied request
sandbox$ curl -sS -X POST https://api.github.com/repos/octocat/hello/issues -d '{}'
{"error":"policy_denied","detail":"POST /repos/octocat/hello/issues not permitted by policy"}
4.5 SSRF Protection
OpenShell includes built-in SSRF (Server-Side Request Forgery) protection:
- Internal address blocking remains active even when endpoints match allowlist
- EC2 metadata endpoint (
169.254.169.254) explicitly blocked AWS_EC2_METADATA_DISABLED=trueenforced for all processes
Source: OpenClaw Security Documentation
5. The Policy Engine Internals
5.1 Architecture Overview
Policy Engine Architecture
5.2 Five Protection Layers
| Layer | Enforcement Point | Runtime Changeable | Scope |
|---|---|---|---|
| Network | OpenShell gateway | Yes (openshell policy set) |
Egress filtering |
| Filesystem | Landlock LSM + mounts | No (requires re-creation) | Path access control |
| Process | Container runtime | No (requires re-creation) | Syscalls, capabilities |
| Gateway Auth | OpenShell gateway | No (set at build time) | Credential management |
| Inference | OpenShell gateway | Yes (openshell inference set) |
LLM routing |
5.3 Policy Configuration Structure
# Example OpenShell policy.yaml
version: 1
name: github-readonly
network:
endpoints:
- host: api.github.com
port: 443
protocol: rest
binaries:
- /usr/bin/curl
- /usr/bin/gh
- "**/python*" # Glob: matches any python binary
methods:
- GET # Read-only operations
paths:
- /repos/** # Any repository endpoint
- /users/** # User information
- /zen # API health check
- host: "*.githubusercontent.com"
port: 443
protocol: none # L4 only - for raw file downloads
binaries:
- "**" # Any binary
access: full # No method/path restrictions
inference:
routes:
- pattern: "api.anthropic.com"
provider: anthropic-managed
model: claude-sonnet-4-20250514
filesystem:
# Locked at creation - cannot be changed at runtime
writable:
- /sandbox
- /tmp
process:
# Locked at creation
capabilities_drop:
- cap_sys_admin
- cap_sys_ptrace
- cap_net_raw
5.4 Decision Flow
When a request arrives, the policy engine evaluates in order:
- Binary identification - Read
/proc/<pid>/exe, verify hash - Destination matching - Check host:port against allowlist
- Binary glob matching - Does the binary match any pattern?
- Protocol inspection - If protocol specified, parse and extract fields
- Method/path matching - For HTTP, check verb and URL path
- SSRF check - Always block internal addresses
- Final decision - Allow, Deny, or Route to inference
5.5 Logging and Auditing
All policy decisions are logged for security auditing:
# View sandbox logs including policy decisions
openshell logs my-sandbox --tail
# Example log output
[2024-01-15T10:23:45Z] ALLOW binary=/usr/bin/curl dest=api.github.com:443 method=GET path=/repos/nvidia/openshell
[2024-01-15T10:23:47Z] DENY binary=/usr/bin/curl dest=api.github.com:443 method=POST path=/repos/nvidia/openshell/issues reason=method_not_allowed
[2024-01-15T10:23:50Z] ROUTE binary=/app/agent dest=api.anthropic.com:443 routed_to=anthropic-managed model=claude-sonnet-4-20250514
# Full diagnostic bundle
openshell debug --sandbox my-sandbox --output debug.tar.gz
5.6 Operator Approval Flow
When a request is blocked, operators can approve it through the TUI:
# Launch terminal UI
openshell term
# In TUI: blocked requests appear in approval queue
# Operator can:
# - Approve for this session (persists until sandbox destroyed)
# - Deny permanently
# - Add to policy file for future sandboxes
Source: OpenClaw Sandbox Management Documentation, OpenShell GitHub
6. Build-Time Security Hardening
OpenShell removes potentially dangerous tools from the runtime image:
# Removed from runtime image (cannot be used even if sandbox is compromised)
gcc gcc-12 g++ g++-12 cpp cpp-12 # Compilers
make # Build tools
netcat-openbsd netcat-traditional ncat # Network utilities
PATH is locked to prevent binary injection:
# Hardened PATH (set at entrypoint)
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
7. User Isolation Model
| Component | User | Rationale |
|---|---|---|
| Agent processes | sandbox |
Unprivileged user for agent code |
| Gateway (root-entrypoint) | gateway |
Separate user prevents agent from accessing gateway memory |
| Gateway (managed topology) | sandbox |
Uses same UID due to no_new_privs constraint |
8. Summary: Complete Security Stack
Complete OpenShell Security Stack
Network rules, inference routing] C[Gateway Proxy
Binary verification, protocol inspection] end subgraph "Container Layer" D[Docker/Podman/K8s
Image isolation, resource limits] E[User Isolation
sandbox/gateway users] end subgraph "Kernel Layer" F[Landlock LSM
Filesystem restrictions] G[Seccomp-BPF
Syscall filtering] H[Namespaces
pid, net, mnt, user, uts, ipc] I[Cgroups
Resource limits] J[Capabilities
Privilege reduction] end K[Linux Kernel] A --> B B --> C C --> D D --> E E --> F F --> G G --> H H --> I I --> J J --> K end style F fill:#ff9900,stroke:#333,stroke-width:3px style G fill:#ff6600,stroke:#333,stroke-width:2px style B fill:#0066ff,stroke:#333,stroke-width:2px
Quiz: Test Your Understanding
Question 1
Why does OpenShell use Landlock LSM instead of relying solely on container mount restrictions?
Show Answer
Landlock provides kernel-level enforcement that is bypass-proof. Unlike container mounts which can potentially be circumvented through symbolic links, mount namespace manipulation, or container escape vulnerabilities, Landlock rules are enforced inside the kernel where they cannot be disabled by application code. Additionally, Landlock restrictions are inherited by child processes and cannot be removed once applied - only tightened.
Question 2
How does the OpenShell gateway identify which binary made an outbound network request?
Show Answer
The gateway reads /proc/<pid>/exe, which is a kernel-provided symlink to the executable that cannot be spoofed by the process. It also walks the process tree to identify ancestor binaries and computes SHA256 hashes of each binary on first use. If a binary's hash changes between requests (indicating runtime replacement), the request is blocked.
Question 3
What is the enforcement order for security mechanisms when a new sandbox process starts?
Show Answer
The enforcement order is: namespace entry (process enters isolated namespaces), privilege drop (capabilities dropped via capsh), Landlock (filesystem restrictions applied), then seccomp (syscall filtering enabled). This order ensures that by the time user code runs, all protections are in place and cannot be circumvented.
Question 4
Which OpenShell protection layers can be modified at runtime, and which require sandbox re-creation?
Show Answer
Runtime changeable: Network policies (via openshell policy set) and Inference routing (via openshell inference set).
Require re-creation: Filesystem restrictions (Landlock layout), Process constraints (capabilities, seccomp), and Gateway authentication (set at build time).
Question 5
Why does OpenShell use a gateway proxy model for network interception instead of eBPF or iptables?
Show Answer
The gateway proxy model enables deep protocol inspection at layers 4-7, including HTTP methods/paths, WebSocket messages, JSON-RPC methods, and MCP tool calls. iptables only operates at L3/L4 and cannot inspect application-layer protocols. eBPF would require elevated privileges inside the sandbox, which would weaken isolation. The user-space gateway can also terminate TLS for encrypted traffic inspection and inject credentials for managed inference endpoints.
References
- NVIDIA OpenShell GitHub Repository - Source code and README
- OpenClaw Security Documentation - Detailed security architecture
- OpenClaw Sandbox Management - CLI commands and operations
- Linux Kernel Landlock Documentation - Landlock API reference
- Landlock Official Site - Project overview and resources
- seccomp(2) Linux Manual Page - Seccomp-BPF reference
- eBPF.io - eBPF technology overview