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
Sandlock / Docs /Getting Started

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 notification5.6
Landlock filesystem rules5.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

Build the binary and the shared library
$ git clone https://github.com/multikernel/sandlock.git
$ cd sandlock
$ cargo build --release

CLI only

Install the sandlock binary
$ 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.

Install the Python SDK
$ 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.

Confirm kernel support
$ 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.

Basic confinement
# -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.

Resources, time, and one network destination
# 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.

See what a command would do before it does it
$ 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.

python
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.

~/.config/sandlock/profiles/build.toml
[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
Use it
$ 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

Rust and Python test suites
$ 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