A Linux-only sandbox primitives library providing namespace isolation, cgroup v2 resource limits, seccomp-BPF syscall filtering, and network isolation for sandboxed process execution.
libsandbox is Linux-only; it fails to compile on other platforms.
- Linux kernel 5.10+ (cgroup v2 + pidfd); some sub-features such as
cgroup.killprefer newer kernels and degrade gracefully on older ones - x86_64 or aarch64 (for seccomp BPF compilation)
- cgroup v2 mounted at
/sys/fs/cgroup - Unprivileged user namespaces enabled (
kernel.unprivileged_userns_clone=1) - MSRV: Rust 1.78
- Optional
landlockfeature: itsprepare_landlockpreset needs kernel ≥ 6.2 (a higher floor than the rest of the crate) — see Cargo Features
Probe unprivileged-user-namespace availability at runtime with
libsandbox::is_supported(). (This checks userns support only; cgroup v2
availability is validated lazily when a sandbox is configured.)
- Namespace isolation -- user, PID, mount, network, UTS, and IPC namespaces via
clone() - Filesystem isolation -- bind mounts, tmpfs, optional rootfs with
pivot_root(); add / remove / remount dynamically in running sandboxes viaMountHandle - Network isolation -- network namespace with optional HTTP proxy for domain-based whitelisting
- Resource limits -- memory, CPU, wall time, CPU time, PID count, FD count via cgroup v2 and rlimit; per-limit enforcement status and metrics surfaced via
ExecutionReport - Seccomp-BPF -- preset and custom syscall filtering profiles
- Landlock LSM (optional,
landlockfeature) -- filesystem-access enforcement via the Linux Landlock LSM
All of this runs rootless through user-namespace UID/GID mapping and cgroup v2
delegation. The public surface is a composable builder API (filesystem /
resources / network / security / environment / namespace), and spawned children
accept caller-provided stdio via the Stdio enum (pipes, null, inheritance, or
a pre-opened file descriptor such as a PTY slave).
use libsandbox::{Sandbox, Permission, MB};
use libsandbox::config::{FilesystemConfig, ResourceConfig, NetworkConfig};
use std::time::Duration;
let sandbox = Sandbox::builder()
.filesystem(
FilesystemConfig::builder()
.mount("/data/input", "/input", Permission::ReadOnly)
.working_dir("/tmp")
.build()
.unwrap()
)
.resources(
ResourceConfig::builder()
.memory_limit(256 * MB)
.wall_time_limit(Duration::from_secs(30))
.build()
.unwrap()
)
.network(NetworkConfig::none())
.build()
.unwrap();
let result = sandbox.run("python3", &["-c", "print(\"hello\")"])?;
println!("{}", result.stdout_lossy());
assert!(result.success());Resource limits fail closed. On hosts without a usable delegated cgroup v2 parent (the common rootless case), explicitly requested cgroup-backed limits error rather than run unbounded. Inspect per-limit status and degradation via
sandbox.run_cmd(cmd, args).run_detailed().
use libsandbox::Sandbox;
let sandbox = Sandbox::builder().build().unwrap();
let child = sandbox.spawn("bash", &["--login"])?;
// `spawn()` pipes stdout/stderr by default. Calling `wait()` on undrained pipes
// returns `ErrorKind::WouldDeadlock`, so collect output with `wait_with_output()`
// (or read `child.stdout_fd()` concurrently before calling `wait()`). For an
// async wait, enable the `tokio` feature and use `Child::wait_async()`.
let output = child.wait_with_output()?;
println!("exit: {}", output.status.code());-
tokio(default) -- enables the HTTP network proxy (NetworkMode::Proxied) for domain-based whitelisting andChild::wait_async(). Disable with--no-default-featuresfor a pure-sync, no-network build that avoids the tokio compile-time and binary-size cost. -
landlock(optional) -- enables thelandlockmodule (prepare_landlock/install_landlock+ aChildSetuphook) and widens theStandard/Strictseccomp allowlists withlandlock_restrict_selfso the child can enter its domain. Independent oftokio.The
prepare_landlockpreset is pinned to landlock ABI V3 (kernel ≥ 6.2): it handlesRefer(V2, needed for cross-directorylink/rename) andTruncate(V3), but deliberately notIoctlDev(V5) — handling that would deny device ioctls (/dev/ttyterminal control) unless each device is granted explicitly. Callers who wantIoctlDevconfinement pass a higher ABI tobuild_rulesetdirectly and grant the devices themselves.The
build_rulesetmechanism is fully ABI-parametric, so escape-hatch callers pick their own floor. Each tier needs (per the landlock-rsABIenum; V4/V6/V7 add no filesystem right over their predecessor):ABI Kernel Adds ( AccessFs, cumulative)V1 5.13 base filesystem rights V2 5.19 Refer(cross-directorylink/rename)V3 6.2 TruncateV4 6.7 (no new AccessFsright)V5 6.10 IoctlDevV6 6.12 (no new AccessFsright)V7 6.15 (no new AccessFsright)Below V2 the preset's
EXDEVfailure returns (cross-dirlink/renamefail mid-compile), so use V2+ unless the workload provably never crosses directories.
# Pure-sync build, no network proxy, plus landlock enforcement:
libsandbox = { version = "0.1", default-features = false, features = ["landlock"] }The examples/ directory demonstrates common usage:
agent_composed.rs-- composed agent sandbox using aChildSetuphookcode_judge.rs-- online-judge style: strict resource limits + security isolation, network disabledcustom_process.rs-- bring-your-ownstd::process::Commandusing the child-sideprepare_*/install_*primitivesdemo.rs-- basic smoke test ofSandbox::builder()+run()network_allow.rs-- network domain whitelist viaNetworkConfig::proxied(requires thetokiofeature)spawn_demo.rs-- interactivespawn()API (read / write / wait / kill)
Run one with cargo run --example demo (add --features landlock where needed).
- API reference (docs.rs)
- Architecture -- internal design and implementation details
- This project is a fork of nanosandbox by Erio Harrison. The code has since been fully rewritten: significant, more complex functionality has been added, so the project no longer considers itself "nano". All non-Linux platform support was removed to focus on Linux and reduce maintenance cost.
- libsandbox is largely driven by the sandboxing needs of just-agent, which serves as the primary consumer, providing usage feedback and exercising experimental features.