Fence uses multiple layers of security on Linux, with graceful fallback when features are unavailable.
| Layer | Technology | Purpose | Minimum Kernel |
|---|---|---|---|
| 1 | bubblewrap (bwrap) | Namespace isolation | 3.8+ |
| 2 | seccomp | Syscall filtering + argv-aware runtime exec policy | 3.5+ (logging: 4.14+, user notif: 5.0+) |
| 3 | Landlock | Filesystem access control | 5.13+ |
| 4 | eBPF monitoring | Violation visibility | 4.15+ (requires CAP_BPF) |
Fence automatically detects available features and uses the best available combination.
To see what features are detected:
# Check what features are available on your system
fence --linux-features
# Example output:
# Linux Sandbox Features:
# Kernel: 6.8
# Bubblewrap (bwrap): true
# Socat: true
# Seccomp: true (log level: 2)
# Landlock: true (ABI v4)
# eBPF: true (CAP_BPF: true, root: true)
#
# Feature Status:
# ✓ Minimum requirements met (bwrap + socat)
# ✓ Landlock available for enhanced filesystem control
# ✓ Violation monitoring available
# ✓ eBPF monitoring available (enhanced visibility)Seccomp feature levels:
Seccomp: true (log level: 1)means seccomp filtering is available and the kernel supportsSECCOMP_RET_LOGSeccomp: true (log level: 2)means seccomp user notification is available; Fence uses this forcommand.runtimeExecPolicy: "argv"on Linux
Landlock is applied via an embedded wrapper approach:
- bwrap spawns
fence --landlock-apply -- <user-command> - The wrapper applies Landlock kernel restrictions
- The wrapper
exec()s the user command
This provides defense-in-depth: both bwrap mounts AND Landlock kernel restrictions are enforced.
Fence supports two Linux runtime child-process exec enforcement modes:
runtimeExecPolicy: "path": default mode; blocks single-token denied executables by path at exec-timeruntimeExecPolicy: "argv": Linux-only opt-in mode; uses seccomp user notification to inspect the actualexecve/execveatargv and enforce command-prefix policy for child processes
In argv mode, Fence uses a small two-part architecture:
- A sandbox-side shim installs a seccomp user-notification filter for
execveandexecveat - A host-side Fence supervisor receives notifications, reconstructs the candidate exec path + argv, and decides allow or deny
This allows rules like denying git push without blocking all git execs.
Requirements:
- Linux kernel with seccomp user notification support (
5.0+) - The Fence CLI binary must be available to host the supervisor path
Failure mode:
- If
runtimeExecPolicy: "argv"is requested and Fence cannot safely inspect the exec request, it fails closed instead of silently downgrading enforcement
- Impact: No Landlock wrapper used; bwrap isolation only
- Fallback: Uses bwrap mount-based restrictions only
- Security: Still protected by bwrap's read-only mounts
- Impact: Blocked syscalls are not logged
- Fallback: Syscalls are still blocked, just silently
- Workaround: Use
dmesgmanually to check for blocked syscalls
- Impact:
command.runtimeExecPolicy: "argv"cannot be used - Fallback: Fence errors if
argvmode is explicitly requested; useruntimeExecPolicy: "path"for the current cross-platform runtime-exec behavior - Security:
pathmode still blocks single-token child execs, but multi-token child exec rules remain preflight-only
- Impact: Filesystem violations not visible in monitor mode
- Fallback: Only proxy-level (network) violations are logged
- Workaround: Run with
sudoor grant CAP_BPF capability
Note
The eBPF monitor uses PID-range filtering (pid >= SANDBOX_PID) to exclude pre-existing system processes. This significantly reduces noise but isn't perfect—processes spawned after the sandbox starts may still appear.
- Impact:
--unshare-netis skipped; network is not fully isolated - Cause: Running in Docker, GitHub Actions, or other environments without
CAP_NET_ADMIN - Fallback: Proxy-based filtering still works; filesystem/PID/seccomp isolation still active
- Check: Run
fence --linux-featuresand look for "Network namespace (--unshare-net): false" - Workaround: Run with
sudo, or in Docker use--cap-add=NET_ADMIN
Note
This is the most common "reduced isolation" scenario. Fence automatically detects this at startup and adapts. See the troubleshooting guide for more details.
- Impact: Cannot run fence on Linux
- Solution: Install bubblewrap:
apt install bubblewrapordnf install bubblewrap
- Impact: Cannot run fence on Linux
- Solution: Install socat:
apt install socatordnf install socat
On WSL2, fence detects the WSL environment and reports it in feature detection (wsl in the summary line). The WSL init binary (/init) is automatically allowed via wslInterop. However, Windows executables under /mnt/ must be configured explicitly.
Fence auto-detects WSL by checking for /proc/sys/fs/binfmt_misc/WSLInterop or /proc/sys/fs/binfmt_misc/WSLInterop-late. When detected:
- The
wslflag appears in feature detection output /initis automatically granted execute permission in Landlock (viawslInterop, enabled by default on WSL)
/init is WSL's init binary — a statically-linked ELF executable. The kernel's binfmt_misc subsystem uses it as the interpreter for Windows PE executables. /usr/bin/wslpath is a symlink to /init.
Windows drive mounts (/mnt/c/, /mnt/d/, etc.) are not auto-allowed — you must add specific executables and paths via allowExecute / allowWrite:
{
"extends": "code",
"filesystem": {
"allowExecute": [
"/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe"
],
"allowWrite": ["/mnt/c/temp"]
}
}To prevent /init from being auto-allowed (e.g., to fully lock down the sandbox on WSL):
{
"filesystem": {
"wslInterop": false
}
}See Configuration > WSL Example for details.
Fence blocks dangerous syscalls that could be used for sandbox escape or privilege escalation:
| Syscall | Reason |
|---|---|
ptrace |
Process debugging/injection |
process_vm_readv/writev |
Cross-process memory access |
keyctl, add_key, request_key |
Kernel keyring access |
personality |
Can bypass ASLR |
userfaultfd |
Potential sandbox escape vector |
perf_event_open |
Information leak |
bpf |
eBPF without proper capabilities |
kexec_load/file_load |
Kernel replacement |
mount, umount2, pivot_root |
Filesystem manipulation |
init_module, finit_module, delete_module |
Kernel module loading |
| And more... | See source for complete list |
On Linux, violation monitoring (fence -m) shows:
| Source | What it shows | Requirements |
|---|---|---|
[fence:http] |
Blocked HTTP/HTTPS requests | None |
[fence:socks] |
Blocked SOCKS connections | None |
[fence:ebpf] |
Blocked filesystem access + syscalls | CAP_BPF or root |
Notes:
- The eBPF monitor tracks sandbox processes and logs
EACCES/EPERMerrors from syscalls - Seccomp violations are blocked but not logged (programs show "Operation not permitted")
- In
runtimeExecPolicy: "argv"mode, denied child execs are reported directly as runtime exec policy denials on stderr - eBPF requires
bpftraceto be installed:sudo apt install bpftrace
| Feature | macOS (Seatbelt) | Linux (fence) |
|---|---|---|
| Filesystem control | Native | bwrap + Landlock |
| Glob patterns | Native regex | Expanded at startup |
| Network isolation | Syscall filtering | Network namespace |
| Syscall filtering | Implicit | seccomp (27 blocked) |
| Child exec argv-aware policy | No practical unprivileged hook | Yes (seccomp user notification, opt-in) |
| Violation logging | log stream | eBPF (PID-filtered) |
| Root required | No | No (eBPF monitoring: yes) |
| Distribution | Default Kernel | Landlock | seccomp LOG | seccomp USER_NOTIF | eBPF |
|---|---|---|---|---|---|
| Ubuntu 24.04 | 6.8 | ✅ v4 | ✅ | ✅ | ✅ |
| Ubuntu 22.04 | 5.15 | ✅ v1 | ✅ | ✅ | ✅ |
| Ubuntu 20.04 | 5.4 | ❌ | ✅ | ✅ | ✅ |
| Debian 12 | 6.1 | ✅ v2 | ✅ | ✅ | ✅ |
| Debian 11 | 5.10 | ❌ | ✅ | ✅ | ✅ |
| RHEL 9 | 5.14 | ✅ v1 | ✅ | ✅ | ✅ |
| RHEL 8 | 4.18 | ❌ | ✅ | ❌ | ✅ |
| Fedora 40 | 6.8 | ✅ v4 | ✅ | ✅ | ✅ |
| Arch Linux | Latest | ✅ | ✅ | ✅ | ✅ |
sudo apt install bubblewrap socatsudo dnf install bubblewrap socatsudo pacman -S bubblewrap socatsudo apk add bubblewrap socatFor full violation visibility without root:
# Grant CAP_BPF to the fence binary
sudo setcap cap_bpf+ep /usr/local/bin/fenceOr run fence with sudo when monitoring is needed:
sudo fence -m <command>