Application-layer security (allowlists, path blocking, command injection protection) is necessary but not sufficient. If an attacker bypasses the allowlist, they run commands with nullclaw’s user permissions. OS-level containment limits the blast radius.
nullclaw implements four sandbox backends behind a common vtable interface. Auto-detection picks the best available option at runtime.
| Phase | Backend | Platform | Effort | Security Gain |
|---|---|---|---|---|
| P0 | Landlock | Linux 5.13+ | Low | High (filesystem) |
| P1 | Firejail | Linux | Low | Very High |
| P2 | Bubblewrap | macOS/Linux | Medium | Very High |
| P3 | Docker | All platforms | High | Complete |
Landlock provides filesystem access control without containers or root. It restricts which paths the process can read/write using Linux Security Module hooks.
// src/security/landlock.zig
pub const LandlockSandbox = struct {
allocator: std.mem.Allocator,
readonly_paths: []const []const u8,
readwrite_paths: []const []const u8,
pub fn apply(self: *LandlockSandbox) !void {
// Create ruleset, add path rules, restrict self
}
};
Config:
{
"security": {
"sandbox": {
"backend": "landlock",
"readonly_paths": ["/usr", "/bin", "/lib"],
"readwrite_paths": ["~/.nullclaw/workspace", "/tmp/nullclaw"]
}
}
}
Firejail wraps commands with namespace-based isolation. No root required. nullclaw detects if firejail is installed and wraps tool execution automatically.
firejail --private=home --private-dev --nosound --no3d --quiet -- <command>
Bubblewrap uses user namespaces to create lightweight containers. Works on macOS and Linux without root.
bwrap --ro-bind /usr /usr \
--dev /dev --proc /proc \
--bind /workspace /workspace \
--unshare-all --share-net \
--die-with-parent \
-- <command>
Run agent tools inside ephemeral containers with memory/CPU limits and no network access.
// Ephemeral container per command
const args = .{
"docker", "run", "--rm",
"--memory", "512m",
"--cpus", "1.0",
"--network", "none",
"--volume", workspace_mount,
image, "sh", "-c", command,
};
nullclaw probes available backends at startup:
Linux: Landlock → Firejail → Bubblewrap → Docker → None
macOS: Bubblewrap → Docker → None
Windows: Docker → None
If no OS-level sandbox is available, nullclaw falls back to application-layer security (command allowlists, path blocking, injection protection).
{
"security": {
"sandbox": {
"backend": "auto"
}
}
}
Valid backends: auto, landlock, firejail, bubblewrap, docker, none.
| Platform | Builds | Runtime Behavior |
|---|---|---|
| Linux ARM (Raspberry Pi) | Yes | Landlock → Firejail → None |
| Linux x86_64 | Yes | Landlock → Firejail → None |
| macOS ARM (Apple Silicon) | Yes | Bubblewrap → None |
| macOS x86_64 | Yes | Bubblewrap → None |
| Windows | Yes | None (app-layer only) |
| RISC-V Linux | Yes | Landlock → None |
Same binary runs everywhere — it adapts protection level based on what’s available.
| Feature | Code Size | RAM Overhead |
|---|---|---|
| Base nullclaw | 639 KB | ~1 MB |
| + Landlock | +~5 KB | +~10 KB |
| + Firejail wrapper | +~5 KB | +0 KB (external) |
| + Bubblewrap wrapper | +~5 KB | +0 KB (external) |
| + Docker wrapper | +~5 KB | +0 KB (external) |
Sandbox adds negligible overhead. Zig comptime means unused backends aren’t compiled.