Sandlock: a process sandbox for Linux that uses Landlock and seccomp instead of containers
The lightest AI sandbox. A process-based sandbox for Linux, no container, no VM, no privilege, no prompt injection
At a glance
- What is it?
- Sandlock confines untrusted code with Landlock, seccomp-bpf and seccomp user notification, with no root, no cgroups and no image build. It suits agent and function workloads that need strict filesystem and network rules on a shared kernel, and it is the wrong tool wherever the kernel is older than 5.13 or the workload needs its own kernel.
- Who is it for?
- Adopt Sandlock when you run untrusted or model-generated code on Linux 6.12 or later, when you cannot grant root or provision a VM, and when the workload is a process rather than a machine image. Do not adopt it where the host kernel is older than 5.13, where the code needs to load its own kernel modules or mount filesystems, or where a separate guest kernel is a compliance requirement.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap Sandlock is aimed at: confinement without an image or a hypervisor
Containers and microVMs both impose setup that has nothing to do with the code being confined. The README's comparison table lists root as required for containers and for Firecracker, an image build step for both, and startup times of roughly 200 ms and 100 ms respectively against about 5 ms for Sandlock. The container row carries an asterisk: rootless containers exist but need user namespace support and /etc/subuid configuration. Sandlock's claim is that a process can be confined tightly enough for untrusted code without any of that, by using kernel facilities that are already present. The audience is narrow and identifiable: teams running AI agents, function-as-a-service handlers, or any pipeline that executes code they did not write, on hosts where they do not have root and do not want to maintain images. The topics list on the repository (ai-agents, faas, promptinjection) matches that reading. The 5 ms figure is a table entry in the README, not a measurement I can reproduce here.
Landlock for paths and ports, seccomp for syscalls, user notification for the rest
The confinement is assembled from three kernel mechanisms, and the split matters because each covers what the others cannot. Landlock handles filesystem, network and IPC scoping. seccomp-bpf filters syscalls. seccomp user notification, available since kernel 5.6, is what the README credits with resource limits, IP enforcement and /proc virtualization. That third layer is the interesting one: a supervisor process intercepts the notifications and decides, which is how memory caps and per-connection IP decisions are enforced without cgroups or a network namespace. The README also mentions a copy-on-write filesystem that protects the working directory, described as built-in and automatic, and a vDSO component inside sandlock-core. The codebase is split into sandlock-core (the Rust library holding Landlock, seccomp, supervisor, COW and pipeline logic), sandlock-cli, sandlock-oci (an OCI runtime shim for containerd, CRI-O and Kubernetes, described as namespace-less), sandlock-ffi exposing a C ABI as libsandlock_ffi.so, and Python and Go SDKs that bind to that shared library through ctypes and cgo. The dependency graph in the README shows the CLI, the OCI shim and both SDKs all landing on sandlock-core, with the SDKs going through the FFI library first. One consequence worth stating: the Python and Go bindings are not separate implementations, so a policy expressed through the SDK follows the same core path as the CLI.
Kernel version is the real installation requirement
Sandlock asks for Linux 6.12 or later, which corresponds to Landlock ABI v6, and Rust 1.70 or later to build. Python 3.8 or later is optional and only needed for the Python SDK. The README's table is more useful than the headline number because it separates features by the kernel that introduced them: seccomp user notification at 5.6, Landlock filesystem rules at 5.13, Landlock TCP port rules at 6.7 (ABI v4), and Landlock IPC scoping at 6.12 (ABI v6). Read that table as a map of what degrades on older kernels. A host on 6.1 can have filesystem rules and user notification but not Landlock TCP port rules, which is exactly the feature the network allowlist examples depend on. The README states that protections can be selectively waived per policy and points to docs/sandbox-reference.md#protection-opt-out. That is a deliberate escape hatch, and it is also the place where a policy can quietly become weaker than its author intended. Building from source is two commands: cargo build --release for the binary and shared library, then cd python && pip install -e . for the Python SDK, which the README says auto-builds the Rust FFI library. CLI-only installation is cargo install --path crates/sandlock-cli.
The command line in practice: read paths, a writable path, and a network grammar
The basic shape is sandlock run followed by flags and then the command after a double dash. Read access is granted per path with -r, writable paths with -w, so sandlock run -r /usr -r /lib -w /tmp -- ls /tmp confines a listing of /tmp to those three trees. The interactive example adds -r /lib64 -r /bin -r /etc and runs /bin/sh, noting that the sandboxed command inherits the terminal. Resource flags are -m for memory (512M in the example), -P for what the README groups under resource limits, and -t for a timeout in seconds. Networking has a grammar worth learning before you write policies. --net-allow takes host:port, a bare host or host:* for every port, an IP, a CIDR range, or an IPv6 literal in brackets, and the README says IP forms are matched by containment with no DNS. A spec with no scheme covers both TCP and UDP; tcp:// or udp:// pins one protocol. The wildcard '*' opens any host and any port over TCP and UDP, while ICMP needs its own icmp:// form and depends on net.ipv4.ping_group_range. --net-deny is the inverse and is mutually exclusive with --net-allow. Above that sits an HTTP layer: --http-allow takes method, host and path, --http-deny takes patterns such as "* */admin/*", and rules with concrete hosts auto-extend --net-allow with host:80,443. HTTPS inspection is handled by an ephemeral CA that Sandlock generates and splices into a bundle named with --http-inject-ca, or by --http-ca and --http-key if you supply your own. --http-ca-out exports the generated certificate so a runtime with a compiled-in CA list can be pointed at it, as in the Node example that sets --env NODE_EXTRA_CA_CERTS=/tmp/sandlock-ca.pem. GPU access is a separate boundary: --gpu takes all or indices like 0,2, and the README states that only the chosen /dev/nvidiaN nodes are openable, so a sandbox given --gpu 0 cannot touch other GPUs. The documented NVIDIA recipe also needs -r /sys, -r /proc and -w /proc/self/task.
Where the design runs out: shared kernel, opt-outs, and the MITM trust bundle
Sandlock shares the host kernel. The comparison table says so directly, listing Shared for Sandlock and containers against Separate guest for Firecracker. If your threat model includes kernel exploits, or a policy requires a distinct kernel for the workload, this is the wrong tool and no flag changes that. The second limitation is the opt-out mechanism. Because protections can be waived per policy, a permissive policy is not the same as a broken one, and the difference is invisible from the command line unless you read the reference document. Third, the HTTPS interception path modifies trust material: --http-inject-ca writes a generated CA into a bundle you name, and the Node workaround requires exporting the certificate and setting the runtime's own environment variable. That is a real change to the process's trust configuration, and any runtime that ignores the bundle or the environment variable will fail its TLS connections rather than fall back. Fourth, the network grammar has sharp edges: '*' covers TCP and UDP but not ICMP, IP specifications are matched by containment without DNS resolution, and --net-allow and --net-deny cannot be combined. Fifth, the README is truncated in the supplied material at a heading beginning "Credent", so the credential-handling section and anything after it cannot be assessed here. Finally, the 6.12 recommendation means Sandlock is effectively a recent-kernel tool; on long-term-support distributions that ship older kernels, the network and IPC parts of a policy will not behave as the examples suggest.
How it differs from gVisor and from rootless Podman
gVisor takes the opposite approach to the same problem. It interposes a user-space kernel between the workload and the host, reimplementing syscall handling in Go, so the workload does not talk to the host kernel directly. Sandlock does not reimplement anything: it asks the host kernel to restrict the process through Landlock and seccomp, and adds a supervisor only for the decisions the kernel delegates through user notification. The practical difference is coverage versus compatibility. gVisor can run on hosts where Landlock is absent, at the cost of a syscall surface that is emulated rather than native. Sandlock gets native syscall performance and depends on kernel features being present, which is why the ABI table matters more than the version headline. Rootless Podman is a different comparison: it gives you an image format, a registry workflow and a user namespace, and the README notes the /etc/subuid configuration that rootless containers require. Sandlock has no image to build and no namespace to configure, but it also has no image to ship, so reproducibility across machines depends on you pinning the read paths and the interpreter rather than on a content-addressed layer. For an agent that needs to call one API host and read a Python installation, the Sandlock command is shorter and starts faster. For a service that must run the same bits on a cluster of mixed kernels, an image-based runtime answers a question Sandlock does not.
Release cadence, licence, and what to confirm before a rollout
The repository is not archived, is written in Rust, and is licensed Apache-2.0. Releases listed are v0.8.5, v0.8.6 and v0.8.7, dated July, August and September 2026, which is roughly a monthly cadence, and the last push to the default branch is dated after the most recent release. A pre-1.0 version number with that cadence means the CLI surface can still move between minor releases, so pin the version you install with cargo install --path crates/sandlock-cli or by building a tagged revision rather than tracking the default branch. The Apache-2.0 licence permits commercial use and modification and includes an explicit patent grant; it also requires that you preserve notices and state changes, and it does not grant trademark rights. That is a description of the licence text, not legal advice, and the FFI library and SDKs are distributed under the same repository licence as far as the supplied material shows. The upgrade cost is concentrated in two places: the policy grammar, which has grown features such as scheme prefixes and HTTP rules across these releases, and the kernel ABI floor, which moves as Landlock gains abilities. Before adopting, check uname -r on every target host against the table in the README, decide explicitly which protections you are willing to waive and record that decision next to the policy file, and confirm whether your workload needs the credential handling described in the section the README excerpt cuts off.
Editorial conclusion
Adopt Sandlock when you run untrusted or model-generated code on Linux 6.12 or later, when you cannot grant root or provision a VM, and when the workload is a process rather than a machine image. Do not adopt it where the host kernel is older than 5.13, where the code needs to load its own kernel modules or mount filesystems, or where a separate guest kernel is a compliance requirement. Before relying on it, check the running kernel with uname -r against the ABI table in the README, confirm that the Landlock features your policy needs are present rather than silently waived, and read docs/sandbox-reference.md#protection-opt-out to see which protections the policy can drop.
Community notes