AppArmor and SELinux Mandatory Access Control for Sandboxes
Mandatory access control in the kernel stops AI agents from escaping their sandbox permissions.

AppArmor and SELinux enforce access control policy from inside the Linux kernel, outside the reach of the process they govern, and that property has turned them from a compliance checkbox into a structural requirement for any infrastructure running AI-generated code. Coding and computer-use agents now generate and execute code autonomously, and that code is untrusted by construction. It can carry the fingerprints of prompt injection, model confusion, or a poisoned training set, and the agent still executes it at scale, under a real user's filesystem permissions, on real infrastructure. Discretionary access control, the familiar uid and gid permission model, fails at the exact moment an attacker gets root inside a process, because root reads everything and DAC has no further argument to make. Mandatory access control sits on the syscall path and gets to say no after the ordinary permission check has already said yes, with policy supplied from outside the process, which makes it immune to a compromised workload trying to rewrite its own rules. Even the act of modifying or unloading that policy is itself governed by the policy, gated behind a capability that a well-written confinement simply does not grant.
Research out of Hong Kong University of Science and Technology, Lingnan University, and collaborating institutions identifies privilege escalation, meaning agent actions that exceed the least privilege actually required for the task, as the defining vulnerability class in LLM-based agent systems. That framing covers multi-agent confused-deputy attacks, where one agent leans on another agent's over-privileged tool access to do something neither was individually supposed to do. One might argue that sandboxing alone (namespaces, seccomp, cgroups) already handles this. It does handle a great deal, but it answers a different question than the one MAC answers, and the next section draws that line precisely.
How MAC works: the kernel enforcement model that makes it different from everything else
AppArmor and SELinux belong to a category called Linux Security Modules, and LSMs intercept operations on the syscall path itself. The check happens in the kernel, not in userspace, and not in whatever container runtime is orchestrating the workload. That placement matters because it decides what a compromise can and cannot reach. Containers rely on namespaces, cgroups, and seccomp, and all three share a single host kernel across every tenant running on that host. A kernel-level escape from any one of those containers compromises the host outright, because namespaces and cgroups were never built to stop an authorized-looking syscall from touching an object it shouldn't. MAC works differently: it is a policy layer applied to objects inside the kernel, enforced regardless of which process is asking or what credentials that process is holding at the moment.
The distinction becomes concrete with a single example. A virtual machine monitor process has a legitimate, routine need to call openat constantly. seccomp will happily permit that syscall, because seccomp's whole job is deciding which syscalls may be made, not which objects they may touch. But that same VMM should never be able to open another tenant's snapshot file, and seccomp has no vocabulary for expressing that restriction. Only an LSM can say: this process, this specific object, no. That is the orthogonal axis MAC adds on top of syscall filtering, and it's why "run it in a container" and "enforce MAC" are not interchangeable claims.
AppArmor and SELinux are both exclusive LSMs. A given kernel can only run one of them as its primary MAC module at any time. Stackable LSMs, among them Landlock, Yama, and the kernel lockdown module, coexist alongside whichever primary module is active, adding their enforcement on top of it. Landlock's specific role in that stack comes up again later, once the application section gives it a concrete job to do.
AppArmor's path-based model
AppArmor enforces mandatory access control by writing policy against filesystem paths, rather than against labels attached to inodes or processes the way SELinux does. A profile names the confined executable and then lists, operation by operation, what it is allowed to do: which files it may read or write, matched by path glob, which network access it may open, which capabilities it may use, and which signals it may send or receive. Paths are matched at open time, with the kernel checking the profile before the syscall is allowed to complete. Profiles can also be composed across process boundaries: a parent process can transition a child into an entirely different, tighter profile on exec. The subprocess an agent spawns to actually run generated code can be confined more strictly than the agent process that spawned it.
A representative excerpt for a sandboxed VMM process might look something like this:
profile vmm-sandbox /usr/bin/vmm-host {
/var/lib/sandbox/snapshots/*.snap r,
/var/lib/sandbox/mem/*.mem rwk,
/var/lib/sandbox/credentials/** deny,
network inet stream,
deny capability sys_admin,
deny capability sys_module,
}
The snapshot file is readable, the memory file is read-write with a lock, and the credentials directory is explicitly denied, regardless of what the process inside asks for. That is the shape of the tool: legible, path-oriented, and fast to write.
AppArmor runs in two distinct modes. Enforcing mode denies violations and logs them. Complain mode only logs violations without blocking anything. Teams shipping agent infrastructure frequently leave profiles in complain mode well into production, which produces an audit trail that looks reassuring and confines nothing. Running in complain mode is not a security control, and any profile audit should check which mode is actually active before crediting it with anything.
AppArmor confines processes purely by filesystem path, lacking any Multi-Level Security or Multi-Category Security mechanism for separating tenants by label. Per-tenant separation of that kind, which regulated multi-tenant AI workloads increasingly require, is very difficult to achieve with AppArmor alone. There's also a distribution-specific trap that catches sandbox builders more often than it should: on Ubuntu 24.04 and later, the default AppArmor policy blocks bubblewrap from creating the user namespaces it needs to function. Any sandbox built on bubblewrap has a prerequisite to satisfy on that distribution before it isolates anything at all, and that gap is documented and real. The Ubuntu version needs to be checked against the deployment target directly.
SELinux's label-based model
SELinux takes a different approach to the same problem. Rather than matching against filesystem paths, SELinux attaches a label to every subject (every process) and every object (every file, socket, and device) on the system, and policy is written as rules over pairs of types. A rule says that a process of type A may act on an object of type B, full stop, and the filesystem path the object happens to live at is irrelevant to the decision.
That label model is what makes Multi-Level Security and Multi-Category Security possible. Different tenants' workloads can be assigned different categories, and the kernel enforces that a process labeled for one tenant cannot touch an object labeled for another, no matter what path that object sits behind. Picture two agent sessions running on the same host for two different enterprise customers: one session's generated code tries, deliberately or through injected instructions, to read a file that belongs to the other customer's session. Under AppArmor's path model, that separation depends entirely on the paths having been scoped correctly in advance. Under SELinux's label model, the kernel checks the category pair directly and refuses the access regardless of where either file happens to live. That is the property that makes SELinux the stronger fit for multi-tenant AI agent platforms where a single host runs sandboxes for multiple customers at once.
SELinux layers a second mechanism on top of type enforcement: Role-Based Access Control. A process's role governs which types it is permitted to transition into, adding a second axis of confinement that constrains not just what an object can be touched, but what a process is even allowed to become. In Kubernetes and OpenShift environments, SELinux assigns each namespace or project a unique MCS label pair, shared by every container inside that namespace, and the kernel then prevents cross-container object access at the kernel level rather than relying on the container runtime to keep that promise. That distinction carries real weight for agent workloads, because a container escape under this model does not automatically hand the attacker access to another tenant's data. The label check still fires on the way out.
The cost of that power is authoring complexity. Writing SELinux policy is considerably harder than writing an AppArmor profile, and administering it is typically a system-wide task requiring elevated privileges rather than something a single team can scope to its own workload. Teams often reach for AppArmor, or for Landlock, when they need developer-facing confinement and don't want to take on system-wide policy administration just to confine one process. Landlock exists specifically to fill the gap that both SELinux's and AppArmor's privileged administration models leave open: a way for a process to confine itself without needing root on the host.
Where the two frameworks differ in practice
Neither framework is universally better than the other. The choice depends on which threat model and which operational context a given deployment actually sits in.
| | AppArmor | SELinux | |---|---|---| | Model | Path-based | Label-based (type enforcement) | | MLS/MCS support | No | Yes | | Default distro | Ubuntu, Debian | RHEL, Fedora | | Authoring complexity | Lower | Higher | | Per-tenant isolation | Limited | Strong (MCS categories) |
AppArmor fits deployments that are already Ubuntu or Debian-based, where it is the default LSM and the tooling around it is mature. It fits workloads where the main concern is filesystem and capability confinement for a single-tenant or lightly multi-tenant agent process, and it fits teams that need to iterate on profiles quickly, since path-based rules are considerably faster to write and audit than type-enforcement policy. It is the right tool when the confinement target is a specific VMM process or agent subprocess.
SELinux fits deployments on RHEL, Fedora, or any distribution where it ships as the default LSM. It fits environments where multi-tenant isolation is a requirement rather than a nice-to-have, since MCS categories give per-tenant, kernel-enforced separation that AppArmor has no mechanism to replicate. It fits workloads bound by compliance standards such as HIPAA or PCI DSS that mandate MLS, MCS, or per-entity type enforcement outright, and it fits environments already running OpenShift or a SELinux-native Kubernetes distribution, where per-pod labels get assigned automatically as part of the platform.
Neither framework replaces the other axis of confinement discussed earlier. seccomp still governs which syscalls a process may make. AppArmor and SELinux still govern which objects those permitted syscalls may touch. The SEAgent MAC framework demonstrates exactly this layering in an LLM agent context, enforcing attribute-based access control over agent-tool interactions as a deterministic system-level layer that complements process isolation.
Applying MAC inside a sandbox that runs AI-generated code
The least obvious decision in building a MAC profile for a microVM-based sandbox is deciding which process to confine. The profile belongs on the VMM process running on the host, not on the guest, because the VMM is the process that actually touches the host's objects: the snapshot files, the memory files, the network interfaces the guest never sees directly.
A well-formed AppArmor profile for a VMM process has to name the snapshot state file as read-only on the restore path, and the memory file needs a lock permission attached to it specifically. These aren't optional refinements. Omit the lock permission and the restore either breaks outright or, worse, leaves the memory file unconfined and readable by anything that can reach the process. Stateful agent workflows, where an agent resumes work from a previously prepared environment, add another requirement on top: the policy has to account for the restored snapshot state's full filesystem footprint, not just the footprint of the process as it starts cold.
That MAC profile sits on top of, not instead of, the standard hardened container invocation: no network by default, every capability dropped, no-new-privileges set, a non-root user, and a seccomp profile limiting the syscall surface. That combination is the baseline. The MAC profile decides which specific objects the syscalls still permitted under that baseline are allowed to touch. Running agent workloads under unprivileged service accounts with MAC profiles attached means that even a fully compromised agent process cannot read credential files, cannot open another tenant's sockets, and cannot modify its own confining profile, because the kernel refuses each of those operations regardless of what uid the process is running under.
That last point is what makes MAC the correct layer for defending against prompt injection specifically. An injected instruction might successfully convince the model to attempt filesystem or credential exfiltration. The kernel-level refusal fires regardless of what the LLM was told to do, checking a label or a path against a rule and returning yes or no based purely on that check. Ji et al.'s SEAgent framework targets exactly this attack class, where an agent is manipulated through natural language into invoking tools beyond its intended scope, and demonstrates that deterministic system-level enforcement catches attacks that machine-learning-based detectors miss entirely, since a detector is still a probabilistic judgment and a kernel deny is not.
Landlock has a specific, narrower job in this stack: letting the agent process confine itself without requiring system-level administration or root on the host. That matters for agent frameworks that need to ship self-confinement as part of the product rather than asking every deploying team to configure SELinux or AppArmor policy by hand. Landlock merged into the Linux kernel at version 5.13, and its capabilities have grown since: TCP network restrictions arrived in Linux 6.7, and signal restrictions followed in Linux 6.12. None of that makes Landlock a substitute for system-wide MAC policy. It is a meaningful complement, usable where privileged administration isn't an option, but the per-tenant isolation and system-wide enforcement that SELinux or AppArmor provide still has to come from somewhere.
Active vulnerabilities in AppArmor that undercut the assumption that enabling it is enough
Enabling AppArmor is not the same claim as being protected by it, and two 2026 disclosures make that distinction concrete enough that any team treating MAC as a static sandbox guarantee needs to account for them directly. CVE-2026-89761, published on September 11, 2026, describes a privilege escalation flaw in the Linux kernel's AppArmor LSM caused by an out-of-bounds write during label vector null termination. An unprivileged local user on a system running loaded AppArmor policy can trigger kernel memory corruption by writing a crafted label to /proc/self/attr/apparmor/current, or by calling lsm_set_self_attr(2) directly.
AppArmor's design isn't the problem; the lesson is that MAC enforcement lives inside kernel code, and kernel code has bugs, which makes patch cadence part of the security property rather than a separate operational concern layered on top of it. A profile that correctly denies every path an attacker might want still runs on top of an LSM implementation that itself needs to stay current. Treating MAC as a one-time configuration step, set once and left alone, misses the fact that the enforcement mechanism itself is software under active development and active scrutiny. Defense-in-depth here means pairing a well-written profile with a patching discipline that keeps pace with disclosures like this one, because the kernel-level guarantee that makes MAC worth adopting in the first place depends on the kernel itself being sound.
Sources
- Taming Various Privilege Escalation in LLM-Based Agent Systems: A Mandatory Access Control Framework
- Sandlock: Confining AI Agent Code with Unprivileged Linux Primitives
- Analysis of Docker Security
- Lic-Sec: an enhanced AppArmor Docker security profile generator
- Solutions for SELinux MCS challenges with GitLab runners
- Technologies for container isolation: A comparison of AppArmor and SELinux
- Sandlock: Confining AI Agent Code with Unprivileged Linux Primitives


