Getting Started
Check the kernel, build from source, run something confined. If your host is on Linux 6.12 or later this takes about five minutes, most of which is Cargo compiling.
Requirements
Sandlock enforces its full protection set on Linux 6.12 or later, which is where Landlock ABI v6 lands. That is the default strict posture, not a hard floor: on older kernels, individual protections can be waived per policy. Building it needs Rust 1.70 or later. The Python SDK needs Python 3.8 or later and is optional.
You do not need root, cgroups, user namespaces, a container runtime, or KVM.
| Feature | Minimum kernel |
|---|---|
| seccomp user notification | 5.6 |
| Landlock filesystem rules | 5.13 |
| Landlock TCP port rules (ABI v4) | 6.7 |
| Landlock IPC scoping (ABI v6) | 6.12 |
Running on a kernel below 6.12 is possible, but each missing protection has to be waived explicitly per policy. See Kernel Protections.
Install
From source
$ git clone https://github.com/multikernel/sandlock.git
$ cd sandlock
$ cargo build --release
CLI only
$ cargo install --path crates/sandlock-cli
Python SDK
The Python package builds the Rust FFI library as part of its install, so a Rust toolchain has to be present.
$ cd python
$ pip install -e .
# with the MCP server extra
$ pip install -e '.[mcp]'
Verify the host
sandlock check reports the running kernel's Landlock ABI and which protections are available. Run it before deploying to a new fleet, not after.
$ sandlock check
Your first sandbox
Sandlock is default-deny. A sandbox with no filesystem rules cannot read /usr, which means it cannot load a dynamic linker, which means nothing runs. Every real invocation names the paths it needs.
# -r grants read, -w grants read and write
$ sandlock run -r /usr -r /lib -w /tmp -- ls /tmp
# An interactive shell needs a few more paths
$ sandlock run -i \
-r /usr -r /lib -r /lib64 -r /bin -r /etc -w /tmp \
-- /bin/sh
Grants are recursive. -r /usr covers everything beneath /usr, because Landlock rules are PATH_BENEATH grants. You do not need to enumerate subdirectories, and listing both a directory and something inside it is redundant.
Add an envelope
Filesystem rules say what the workload can reach. The other three dimensions say how much of the machine it can consume, where it can talk, and how long it may run.
# 512 MB of memory, 20 concurrent processes, 30 second timeout
$ sandlock run -m 512M -P 20 -t 30 -- ./compute.sh
# Exactly one host on exactly one port; everything else is denied
$ sandlock run --net-allow api.openai.com:443 \
-r /usr -r /lib -r /etc -- python3 agent.py
# Start from a minimal environment and set what you need
$ sandlock run --clean-env --env CC=gcc \
-r /usr -r /lib -w /tmp -- make
Protect the working directory
Setting --workdir puts a copy-on-write layer over that directory. Writes are staged rather than applied, then committed when the command exits successfully and discarded when it fails. --dry-run takes it one step further: it runs the command, reports what would have changed, and then discards everything.
$ sandlock run --dry-run --workdir . \
-w . -r /usr -r /lib -r /bin -r /etc -- make build
A build/out.o
M Cargo.lock
D build/stale.o
From Python
The Python SDK takes the same policy as a dataclass. Field names match the CLI concepts, and the sandbox object carries no runtime state, so it can be built once and reused.
from sandlock import Sandbox
sandbox = Sandbox(
fs_writable=["/tmp/sandbox"],
fs_readable=["/usr", "/lib", "/etc"],
max_memory="256M",
max_processes=10,
clean_env=True,
)
result = sandbox.run(["python3", "-c", "print('hello')"], timeout=30)
assert result.success
assert b"hello" in result.stdout
Save the policy
Once a policy stops changing, move it into a TOML profile in ~/.config/sandlock/profiles/ and reference it by name. Profiles use a sectioned schema; flat top-level keys are rejected rather than silently ignored.
[program]
exec = "make"
args = ["-j4"]
clean_env = true
env = { CC = "gcc", LANG = "C.UTF-8" }
[filesystem]
read = ["/usr", "/lib", "/lib64", "/bin", "/etc"]
write = ["/tmp/work"]
[limits]
memory = "512M"
processes = 50
$ sandlock profile list
$ sandlock run -p build # uses [program].exec and args
$ sandlock run -p build -- make test # trailing command wins
You do not have to write the profile by hand. sandlock learn runs a workload under observation and emits a profile covering the paths, connections, and resource peaks it actually used. See Profiles and learn.
Run the tests
$ cargo test --release
$ cd python && pip install -e . && pytest tests/
Tests that spawn real sandboxes are heavier than they look, since each one is a full sandlock process with its own supervisor. Running too many concurrently exhausts kernel limits and causes hangs, so cap the parallelism with --test-threads=4.
Where to go next
Architecture
What each layer enforces, and which syscalls take a userspace round trip.
Network Model
The full rule grammar, protocol gating, and the two enforcement paths.
HTTP ACL
Method and path rules, HTTPS interception, and credential injection.
Sandbox Reference
Every field, every default, in one page.