Home
Why Sandlock
How It Works Use Cases Comparison Security Model
Docs
Documentation Home Getting Started CLI Reference Python SDK Sandbox Reference
Products
Overview Sandbox HTTP API Sandbox Scheduler
GitHub Schedule a Demo
Architecture

How Sandlock Works

Sandlock is not a runtime the workload runs inside. It is a policy the kernel applies to a process, installed after fork() and before exec(), so the workload has never executed a single unconfined instruction.

Three mechanisms

What Enforces What

Sandlock pushes as much of the policy as possible into the kernel. Only the rules the kernel cannot express, such as "this destination IP" or "this HTTP path", reach the supervisor.

Layer 1

Landlock

Kernel

Landlock is Linux's unprivileged access-control LSM. Sandlock translates the policy's filesystem, network, and IPC rules into a Landlock ruleset and applies it to the child process. From that point the kernel evaluates every access against the ruleset, and the ruleset can never be relaxed for the lifetime of the process tree.

  • Filesystem. Read and write grants are recursive PATH_BENEATH rules. Because the kernel resolves the path itself at access time, these rules are immune to time-of-check/time-of-use attacks that defeat path-string filtering in userspace.
  • TCP. Connect and bind port allowlists (Landlock ABI v4 and later).
  • IPC. Abstract UNIX socket scoping and signal scoping (ABI v6), which keep a sandbox from reaching sibling processes on the host.

Landlock costs nothing per syscall beyond the kernel's own check. This is why confinement is cheap enough to leave on for production workloads.

Layer 2

seccomp-bpf

Kernel

A seccomp-bpf filter removes syscall families a confined workload has no legitimate use for, before any of them can reach the kernel's implementation. Sandlock always applies its default blocklist; a policy can add more denials on top of it, and can re-enable specific named groups such as System V IPC when a workload genuinely needs them.

The same filter installs the notification listener used by layer 3. Because the filter is installed alongside NO_NEW_PRIVS, the child cannot regain privileges through a setuid binary, and the filter is inherited by every descendant.

A no-supervisor mode uses only layers 1 and 2: Landlock plus a kernel-only deny filter, with no supervisor process at all. It is the mode used when nesting one sandlock inside another, since the kernel permits only one notification listener per task.

Layer 3

seccomp user notification

Supervisor

Some policy cannot be expressed as a static kernel rule. "Connect only to this IP", "stop at 512 MB of resident memory", "stage this write instead of performing it" all require a decision at the moment the syscall happens. Seccomp user notification hands those syscalls to an async supervisor running in the parent process.

The supervisor is the one component that is not the kernel, so Sandlock keeps its responsibilities narrow and its ordering fixed: built-in handlers run first, and any handlers a downstream crate registers run after them, which means a custom handler can extend confinement but never subvert it.

Startup

The Confinement Sequence

Order matters. Every restriction is in place before the workload's first instruction runs, and the parent only starts supervising after the child has confirmed the filter is installed.

In the child, after fork()
  1. 1setpgid(0,0)
  2. 2chdir(cwd), if the policy sets one
  3. 3NO_NEW_PRIVSa setuid binary can now confer nothing
  4. 4Landlock ruleset appliedfilesystem, network, and IPC
  5. 5seccomp filter installeddeny list plus the notification listener, whose descriptor goes to the parent
  6. 6Wait for the parent's ready signal
  7. 7Close every descriptor above stderr
  8. 8exec(cmd)the workload's first instruction, already confined
In the parent, meanwhile
  • Receives the notification descriptor
  • Starts the supervisor on tokio
  • Patches the vDSO, if time is frozen
  • Starts the policy callback thread, if one is set
  • Starts the CPU throttle task, if one is set

Everything on the left happens before the workload exists. Everything on the right supervises it once it does.

Why fds are closed at step 7. A descriptor inherited from the parent is a capability the Landlock ruleset never sees: it was opened before confinement, so the kernel will honour it. Closing everything above stderr removes that escape hatch before exec.

The supervisor

Which Syscalls Take a Round Trip

Only the syscalls in this table are intercepted, and only when the corresponding policy feature is active. Everything else runs at full speed under the kernel filter.

Syscall What the supervisor does
clone / fork / vforkEnforces the concurrent process count
mmap / munmap / brk / mremapTracks the memory limit
connect / sendto / sendmsgDestination IP allowlist, on-behalf execution, HTTP ACL redirect
bindOn-behalf bind and port remapping
openat/proc virtualization and COW interception
unlinkat / mkdirat / renameat2COW write interception
execve / execveatPolicy callback hold and vDSO re-patching
getrandomDeterministic PRNG injection
clock_nanosleep / timer_settimeTimer adjustment when time is frozen
getdents64PID filtering and COW directory merging
getsocknamePort remap translation
Network enforcement

Two Paths, Chosen by Your Policy

Sandlock picks the cheapest enforcement path that can express the rules you wrote. You do not configure this; it falls out of the policy.

Direct path

Selected when the policy is pure TCP port rules with no concrete host, IP, or CIDR, and no HTTP ACL. Landlock enforces the port allowlist in the kernel, and there is no per-syscall overhead at all.

Zero userspace round trips
Example: --net-allow :443

On-behalf path

Selected for any host, IP, or CIDR target, any UDP or ICMP rule, or any HTTP ACL rule, because the destination address has to be inspected and Landlock cannot do that. The supervisor duplicates the child's socket, learns its protocol via SO_PROTOCOL, checks the destination, and performs the syscall itself.

Per-connection check, not per-packet
Example: --net-allow api.example.com:443
Components

One Core, Many Front Ends

Sandlock is written in Rust. Every interface, including the SDKs in other languages, drives the same core through the same policy object.

Interfaces
sandlock CLI Python SDKctypes Go SDKcgo sandlock-ociOCI runtime
C ABI
libsandlock_ffi.so
Core
sandlock-coreLandlock · seccomp · COW · pipeline · policy_fn · vDSO

The CLI and both SDKs reach the core through the C ABI. sandlock-oci links the core directly, since it is a Rust crate itself and has no reason to cross the boundary.

Component Role
sandlock-coreRust library: Landlock, seccomp, supervisor, COW, pipelines
sandlock-cliThe sandlock binary
sandlock-ociOCI runtime shim for containerd, CRI-O, and Kubernetes
sandlock-ffiC ABI shared library, libsandlock_ffi.so
Python SDKctypes bindings over the FFI library, plus an MCP server
Go SDKcgo bindings over the FFI library
Requirements

What the Host Kernel Must Provide

Sandlock targets Linux 6.12 and later, which is where Landlock ABI v6 lands. Individual protections can be degraded or disabled per policy when you deploy across a mixed fleet.

Capability Minimum kernel
seccomp user notification5.6
Landlock filesystem rules5.13
Landlock TCP port rules (ABI v4)6.7
Landlock IPC scoping (ABI v6)6.12

Run sandlock check to report the running kernel's Landlock ABI and which protections are available. Building Sandlock itself needs Rust 1.70 or later; the Python SDK needs Python 3.8 or later.

Read the Documentation

Every policy field, every flag, and every enforcement rule is documented, from a five-minute quickstart to the complete sandbox reference.