# Sandlock confines Linux processes without root, and its own API examples end mid-statement

> Sandlock is a Rust sandbox built on Landlock, seccomp-bpf and seccomp user notification, with no root, no cgroups, no containers and an HTTP-level ACL layer that the comparison table marks unavailable for containers and microVMs. The isolation story is well specified; the published examples, benchmarks and install paths for the two SDKs are uneven.

**multikernel/sandlock** — The lightest AI sandbox. A process-based sandbox for Linux, no container, no VM, no privilege, no prompt injection

- Repository: https://github.com/multikernel/sandlock
- Website: https://sandlock.io
- Stars: 540 · Forks: 56
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/multikernel-sandlock

## Four Linux primitives do all the isolation

The confinement story is built from kernel features rather than from a hypervisor. Landlock handles filesystem, network and IPC scoping, seccomp-bpf filters syscalls, and seccomp user notification provides resource limits, IP enforcement and /proc virtualization. On top of those sits a copy-on-write filesystem that protects the working directory automatically. There is no root requirement, no cgroup and no container.

The comparison table against a container and a Firecracker microVM is where the design shows. Sandlock is credited with roughly 5 ms startup against about 200 ms for a container and 100 ms for a microVM, a shared kernel rather than a separate guest, and an HTTP-level ACL of method, host and path rules, which the other two columns mark as unavailable. Syscall filtering is seccomp-bpf for both Sandlock and containers, and resource limits come from seccomp notification paired with SIGSTOP rather than cgroup v2.

The footnote is honest about the comparison. Rootless containers do exist, but the table notes they require user namespace support and `/etc/subuid` configuration, which is the friction Sandlock is positioned against.

The whole premise rests on those primitives being present, which is the subject of the kernel requirement table.

## Two tables give Docker two different startup numbers

The feature comparison states a container startup time of roughly 200 ms. The performance section, benchmarked on what it calls a typical Linux workstation, reports `/bin/echo` startup for Docker at 307 ms against 2 ms bare metal and 7 ms inside Sandlock, with 5 ms of overhead described as 44 times faster than Docker. The 44 times figure is consistent with 307 divided by 7, and the 5 ms overhead matches the difference between 7 and 2.

So the two tables disagree by half again on the same measurement, with no explanation for which figure came from which harness. A reader comparing Sandlock against a container has to pick between them.

The rest of the benchmark holds together better. Redis SET at 100K operations runs at 82K requests per second bare metal, 80K in Sandlock and 52K in Docker, described as 97.1 percent of bare metal, and GET is the same shape at 79K, 77K and 53K. p99 latency is 0.5 ms bare, 0.6 ms in Sandlock and 1.5 ms in Docker, described as about 2.5 times lower than Docker, which matches. The copy-on-write fork figure of 530 ms for 1000 forks works out to 530 microseconds per fork and roughly 1900 forks per second, and both derived numbers check out.

The gap is not arithmetic, it is provenance. No CPU, kernel version, Redis version, container runtime or measurement tool is named, so none of these figures can be reproduced or compared against your own host.

## Every SDK example in the visible README stops mid-statement

The quick start gives a complete CLI example, which is the one part that reads cleanly:

```bash
sandlock run -w /tmp -r /usr -r /lib -m 512M -- python3 untrusted.py
```

Then the examples fall apart. The HTTP-level ACL block ends in the middle of a rule string, cut off inside an API path. The Python example builds a Sandbox with fs_writable, fs_readable, a memory cap, a process cap and clean_env, runs a command with a timeout, and asserts on the result, which is complete, but the dry-run variant that follows stops after an opening variable name.

The Rust example is the same shape. The builder chain with fs_read, fs_write, a memory cap and a name, the run call and the success assertion all read fine, and then the HTTP ACL variant that demonstrates http_allow and http_deny ends at the name call.

In a document whose entire purpose is showing other people how to construct a policy, an unparseable example is worse than no example, because a reader has to guess whether the truncation is the end of the API or the end of the page. The actual guides are linked separately for the Python and Go SDKs and for the CLI, so the information exists, just not in the place a new user lands.

## The kernel floor is 6.12 and older kernels silently lose a protection

The stated requirement is Linux 6.12 or newer, identified as Landlock ABI v6, with Rust 1.70 or newer to build and Python 3.8 or newer if you want the Python SDK. The feature-to-kernel table shows exactly what each floor buys:

| Feature | Minimum kernel |
|---|---|
| seccomp user notification | 5.6 |
| Landlock filesystem rules | 5.13 |
| Landlock TCP port rules | 6.7 (ABI v4) |
| Landlock IPC scoping | 6.12 (ABI v6) |

So the 6.12 requirement is not a general minimum, it is the version at which IPC scoping arrived. On a 6.7 kernel you keep filesystem and TCP port rules but lose IPC scoping, and on anything older the gaps widen.

The documentation is explicit that protections can be selectively waived per policy when needed, pointing at a dedicated opt-out section. That mechanism is what makes the project runnable across a heterogeneous fleet, and it is also the sharpest edge in the design: the same switch that lets a laptop at kernel 5.15 proceed is one a misconfigured policy could flip for a production workload. Nothing in the visible material describes whether a waived protection is logged, reported in the run result, or silent.

## Two language SDKs, one C ABI underneath

The architecture is six pieces, and the shape is worth reading before installing anything. The core is a Rust library holding Landlock, seccomp, the supervisor, the copy-on-write layer and the pipeline logic. The CLI is a thin Rust binary on top of it. The OCI component is a runtime shim for containerd, CRI-O and Kubernetes that operates without namespaces.

That shim is the strategic piece. It lets an existing container orchestrator keep its interface while the workload runs under process-level confinement instead of a container, which means migration off containers does not require rewriting deployment manifests.

The remaining three pieces are bindings. A C ABI shared library named `libsandlock_ffi.so` is the bridge, with the Python SDK reaching it through ctypes and the Go SDK through cgo. So there is exactly one native surface, and both language SDKs are thin layers over it.

That has a practical consequence for extension points. Custom seccomp-notification handlers are documented as being writable in Rust and over the C ABI, and from Python as well, which is the case that most needs the FFI layer to be stable since Python cannot reach the Rust internals directly.

## The Go SDK needs three files placed by hand and Python does not

Installing the two SDKs is not symmetric. The Python side is a one-line editable install after the Rust build. The Go side needs the native library, a C header and a pkg-config file laid into place, and the Makefile exists specifically for that:

```bash
cargo build --release -p sandlock-ffi
```

The `install-go-lib` target then installs `libsandlock_ffi.so` into the library directory, installs `sandlock.h` into the include directory, and generates `sandlock.pc` from a template in the Go directory by substituting the prefix and the version. Released builds are expected to find the library through pkg-config, and both `PREFIX` and `DESTDIR` are honored. A matching `uninstall-go-lib` target removes the same three files.

The version it stamps in comes from scraping Cargo.toml with a sed expression that pulls the first `version = ` line it finds, which happens to be the workspace package version in the current manifest but is positional rather than structural. Add another version line earlier in that file and the generated pkg-config file silently describes the wrong release.

The Makefile declares four phony targets, all of them Go-facing. There is no target for the Python SDK or for the CLI, so the two installs with the most tooling are handled entirely by cargo and pip.

## Learning mode and dynamic callbacks replace a fixed policy

Beyond static rules, the documentation describes two extension mechanisms that make Sandlock more than a fixed wrapper.

One is a learning command, `sandlock learn`, which generates a profile from an observed run. That inverts the usual workflow: instead of writing a policy and hoping it covers what the workload does, you let the workload run once under observation and derive a profile from what it actually touched. The CLI reference also covers profiles alongside inspection, process listing and killing, so a learned profile is a first-class artifact rather than a one-off.

The other is a dynamic policy callback layer, documented with events, verdicts, context methods and time-of-check-to-time-of-use guarantees. A TOCTOU guarantee in a sandbox callback matters because the callback runs in a supervisor process outside the confined child, so a decision made against state the child can still change is the obvious way to build a race into a policy engine.

Pipelines are the third layer, covering the COW fork and a map-reduce pattern, which suggests the design anticipates running many short-lived confined tasks rather than one long-lived service. Together these three suggest the tool is aimed at workloads whose real access pattern is not known in advance, which is the actual difficulty with sandboxing agent code.

## Conclusion

Sandlock is a credible fit for running untrusted Python or agent code on a Linux host where rootless containers are unavailable or too heavy, and a poor fit if your kernel is older than 6.12 or if you need the guarantees to be uniform, since protections can be waived per policy. Before deploying, pin your kernel against the feature table, measure startup on your own hardware since the published numbers name no machine, and read the protection opt-out section before you rely on it.

## FAQ

### What is sandlock and what does it use to isolate a process?

It is a lightweight Linux process sandbox written in Rust that confines untrusted code with Landlock for filesystem, network and IPC, seccomp-bpf for syscall filtering, and seccomp user notification for resource limits, IP enforcement and /proc virtualization, with no root, no cgroups and no containers.

### What kernel version does sandlock require?

Linux 6.12 or newer, which is the version that introduced Landlock IPC scoping at ABI v6. Earlier floors are listed per feature: seccomp user notification needs 5.6, Landlock filesystem rules need 5.13 and Landlock TCP port rules need 6.7.

### How do I install the sandlock CLI and Python SDK?

Build with cargo build --release, then install the Python SDK with pip install -e . from the python directory. To install only the CLI, use cargo install --path crates/sandlock-cli. The Go SDK is different and needs the shared library, header and pkg-config file installed by the Makefile.

### Can sandlock restrict HTTP requests by method and path?

Yes, through a transparent proxy with allow and deny rules expressed as method, host and path, for example allowing a GET pattern on one documentation host. The comparison table marks this capability as unavailable for containers and microVMs, and full endpoint grammar and interception details are in the network documentation.

### Does sandlock need root privileges?

No. The stated position is no root, no cgroups and no image build. The comparison footnote notes that rootless containers do exist but need user namespace support and /etc/subuid configuration, which is the gap Sandlock is positioned against.

### How does sandlock compare with Docker on startup time?

Two published tables give two answers. The feature comparison puts Sandlock at about 5 ms against about 200 ms for a container and 100 ms for a microVM, while the performance section reports /bin/echo startup at 7 ms in Sandlock against 307 ms in Docker and calls that 44 times faster, with no hardware or kernel named in either.

## Sources

- [License: Apache-2.0](https://github.com/multikernel/sandlock/blob/main/LICENSE)
- [multikernel/sandlock on GitHub](https://github.com/multikernel/sandlock)
- [Project website](https://sandlock.io)
- [README](https://github.com/multikernel/sandlock/blob/main/README.md)
- [Releases](https://github.com/multikernel/sandlock/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/multikernel-sandlock
