Warning
Work in Progress: This repository is actively under development and things may change or break without warning
closured uses eBPF LSM hooks to ensure your NixOS system only executes what its closure declares, either auditing or blocking other attempted executions
On startup closured builds an allowlist from the requisites of its closure roots
(/run/current-system, /run/booted-system and /nix/var/nix/profiles/system
by default) and reports any exec that falls outside it. Events are classified
as:
closure: a store path in the allowed closure (only reported with--all)store: a store path not in the allowed closurewrapper: a setuid wrapper under/run/wrappersmemory/deleted: an unlinked executable (memfd or deleted file)outside: anything else
The allowlist refreshes automatically when a closure root changes, so a deploy is picked up without a restart.
- kernel >= 6.12 with BTF (
/sys/kernel/btf/vmlinux) - the BPF LSM enabled (
bpfpresent in the active LSM list:cat /sys/kernel/security/lsm)
nix build
sudo ./result/bin/closured # loading the eBPF program needs rootor just
sudo nix runThe flake exports a NixOS module that runs closured as a hardened systemd service:
{
inputs.closured.url = "github:samiser/closured";
imports = [inputs.closured.nixosModules.default];
services.closured.enable = true;Events are logged as NDJSON: journalctl -u closured -o cat | grep '^{' | jq
Refreshing the allowlist means shelling out to nix-store --query --requisites,
which takes long enough that switch-to-configuration (a binary from the new
generation) can run before the refresh happens and be denied. closured preload
fixes this by allowlisting a closure before you activate it:
nixos-rebuild build
sudo closured preload ./result
nixos-rebuild switchpreload returns only once the hashes are in the BPF map, so the switch is safe
the moment it exits.
nixos-rebuild boot followed by a reboot needs no preload at all because at
boot /run/current-system already points at the new generation.
Preloads are held until the closure catches up with them, and are removed after
--preload-ttl seconds (default 900) if the system is never activated, so
abandoning a build does not leave it executable indefinitely.
Any process can tighten its own cgroup to a specific closure:
systemd-run --user --scope sh -c 'closured confine /nix/store/...-some-app && some-app'Execs from that cgroup outside the given closures are then denied (or only
reported with --audit), on top of whatever the global policy says. Confine
can only tighten the caller's own cgroup, so it deliberately needs no
privileges. The restriction is dropped automatically when the cgroup goes
away, and events from a confined cgroup carry the name given with --label
(or the first path's store name).
Confinement stacks on top of the global policy, which gives two useful modes.
With --enforce, everything outside the system closure is denied by default
and confinement tightens specific apps further. With --confine-only, the
global policy allows (and doesn't report) anything, so only confined cgroups
are restricted.
This is the hook waypak uses to stop sandboxed apps executing anything outside their own closure.
nix develop gives you a shell where you can build with cargo
nix flake check runs two NixOS VM tests:
checks.<system>.vmcovers enforcement, the control socket and preload expiry.checks.<system>.switchbuilds a second system generation, preloads it and activates it under--enforce, then checks the preload is consumed once the closure catches up.
With the exception of eBPF code, closured is distributed under the terms of either the MIT license or the Apache License (version 2.0), at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
All eBPF code is distributed under either the terms of the GNU General Public License, Version 2 or the MIT license, at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the GPL-2 license, shall be dual licensed as above, without any additional terms or conditions.