Greywall restricts network, filesystem, and command access for arbitrary commands. It works by:
- Blocking commands via configurable deny/allow lists before execution
- Routing network traffic through an external SOCKS5 proxy (e.g., GreyProxy) via transparent TUN-based proxying
- Sandboxing processes using OS-native mechanisms (macOS sandbox-exec, Linux bubblewrap)
- Sanitizing environment by stripping dangerous variables (LD_PRELOAD, DYLD_INSERT_LIBRARIES, etc.)
flowchart TB
subgraph Greywall
Config["Config<br/>(JSON)"]
Manager
CmdCheck["Command<br/>Blocking"]
EnvSanitize["Env<br/>Sanitization"]
Sandbox["Platform Sandbox<br/>(macOS/Linux)"]
end
subgraph External
Proxy["SOCKS5 Proxy<br/>(e.g. GreyProxy)"]
DNS["DNS Server"]
end
Config --> Manager
Manager --> CmdCheck
CmdCheck --> EnvSanitize
EnvSanitize --> Sandbox
Sandbox -->|tun2socks| Proxy
Sandbox -->|DNS bridge| DNS
greywall/
├── cmd/greywall/ # CLI entry point
│ └── main.go # Includes --landlock-apply wrapper mode
├── internal/ # Private implementation
│ ├── config/ # Configuration loading/validation
│ ├── platform/ # OS detection
│ ├── proxy/ # GreyProxy detection, installation, and lifecycle
│ └── sandbox/ # Platform-specific sandboxing
│ ├── manager.go # Orchestrates sandbox lifecycle
│ ├── macos.go # macOS sandbox-exec profiles
│ ├── linux.go # Linux bubblewrap + socat bridges + D-Bus proxy
│ ├── 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/greywall/ # Public Go API
└── greywall.go
Handles loading and validating sandbox configuration:
type Config struct {
Network NetworkConfig // Proxy URL, DNS, localhost controls
Filesystem FilesystemConfig // Read/write restrictions
Command CommandConfig // Command deny/allow lists
AllowPty bool // Allow pseudo-terminal allocation
}- Loads from XDG config dir (
~/.config/greywall/greywall.jsonor~/Library/Application Support/greywall/greywall.json) or custom path - Falls back to restrictive defaults (block all network, default command deny list)
- Validates paths and normalizes them
Simple OS detection:
func Detect() Platform // Returns MacOS, Linux, Windows, or Unknown
func IsSupported() bool // True for MacOS and LinuxManages the external GreyProxy lifecycle (detection, installation, startup):
detect.go- Checks if greyproxy is installed and running (health endpoint)install.go- Downloads and installs greyproxy from GitHub releasesstart.go- Starts the greyproxy service
Domain filtering and access control are handled entirely by the external proxy, not by greywall.
Orchestrates the sandbox lifecycle:
- Sets up proxy and DNS bridges to the external SOCKS5 proxy (Linux)
- Extracts embedded
tun2socksbinary for transparent proxying - Checks command against deny/allow lists
- Wraps commands with sandbox restrictions
- Handles cleanup on exit
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
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.
Uses Apple's sandbox-exec with Seatbelt profiles:
flowchart LR
subgraph macOS Sandbox
CMD["User Command"]
SE["sandbox-exec -p profile"]
ENV["Environment Variables<br/>HTTP_PROXY, HTTPS_PROXY<br/>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
Uses bubblewrap (bwrap) with network namespace isolation and transparent SOCKS5 proxying:
flowchart TB
subgraph Host
PROXY["External SOCKS5 Proxy<br/>(e.g. GreyProxy :43052)"]
DNS["DNS Server<br/>(:43053)"]
PSOCAT["socat<br/>(proxy bridge)"]
DSOCAT["socat<br/>(DNS bridge)"]
USOCK["Unix Sockets<br/>/tmp/greywall-*.sock"]
end
subgraph Sandbox ["Sandbox (bwrap --unshare-net)"]
CMD["User Command"]
TUN["tun2socks<br/>(TUN device)"]
ISOCAT["socat<br/>(relay)"]
end
PROXY <--> PSOCAT
DNS <--> DSOCAT
PSOCAT <--> USOCK
DSOCAT <--> USOCK
USOCK <-->|bind-mounted| ISOCAT
CMD -->|all traffic| TUN
TUN --> ISOCAT
How it works:
With --unshare-net, the sandbox has its own isolated network namespace. Unix sockets provide filesystem-based IPC across namespace boundaries:
- Host creates Unix sockets, connects via socat to the external SOCKS5 proxy and DNS server
- Socket files are bind-mounted into the sandbox
- Inside the sandbox, a TUN device routes all traffic through
tun2socks, which forwards to the external proxy via the Unix socket bridge - If TUN is unavailable, greywall falls back to setting proxy environment variables (
HTTP_PROXY,HTTPS_PROXY,ALL_PROXY)
For servers running inside the sandbox that need to accept connections:
flowchart TB
EXT["External Request"]
subgraph Host
HSOCAT["socat<br/>TCP-LISTEN:8888"]
USOCK["Unix Socket<br/>/tmp/greywall-rev-8888-*.sock"]
end
subgraph Sandbox
ISOCAT["socat<br/>UNIX-LISTEN"]
APP["App Server<br/>:8888"]
end
EXT --> HSOCAT
HSOCAT -->|UNIX-CONNECT| USOCK
USOCK <-->|shared via bind /| ISOCAT
ISOCAT --> APP
Flow:
- Host socat listens on TCP port (e.g., 8888)
- Sandbox socat creates Unix socket, forwards to app
- External request → Host:8888 → Unix socket → Sandbox socat → App:8888
flowchart TD
A["1. CLI parses arguments"] --> B["2. Load config from XDG config dir"]
B --> C["3. Create Manager"]
C --> D["4. Manager.Initialize()"]
D --> D1["Extract tun2socks binary"]
D --> D2["[Linux] Create proxy bridge (socat → external SOCKS5)"]
D --> D3["[Linux] Create DNS bridge (socat → DNS server)"]
D --> D4["[Linux] Create reverse bridges (exposed ports)"]
D1 & D2 & D3 & D4 --> E["5. Manager.WrapCommand()"]
E --> E0{"Check command<br/>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<br/>(strip LD_*/DYLD_*)"]
F --> G["7. Execute wrapped command"]
G --> H["8. Manager.Cleanup()"]
H --> H1["Kill socat/tun2socks processes"]
H --> H2["Remove Unix sockets"]
| Feature | macOS | Linux |
|---|---|---|
| Sandbox mechanism | sandbox-exec (Seatbelt) | bubblewrap + Landlock + seccomp |
| Network isolation | Syscall filtering | Network namespace |
| Proxy routing | Environment variables | tun2socks + socat bridges (fallback: env vars) |
| Filesystem control | Profile rules | Bind mounts + Landlock (5.13+) |
| Syscall filtering | Implicit (Seatbelt) | seccomp BPF |
| Inbound connections | Profile rules (network-bind) |
Reverse socat bridges |
| Violation monitoring | log stream | eBPF |
| Env sanitization | Strips DYLD_* | Strips LD_* |
| Requirements | Built-in | bwrap, socat |
On Linux, greywall uses multiple security layers with graceful fallback:
- bubblewrap (core isolation via Linux namespaces)
- seccomp (syscall filtering)
- Landlock (filesystem access control)
- 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 for details.
The -m (monitor) flag enables real-time visibility into blocked operations. These only apply to filesystem and network operations, not blocked commands.
| Prefix | Source | Description |
|---|---|---|
[greywall:logstream] |
macOS only | Kernel-level sandbox violations from log stream |
[greywall:ebpf] |
Linux only | Filesystem/syscall failures (requires CAP_BPF or root) |
On macOS, greywall spawns log stream with a predicate to capture sandbox violations:
log stream --predicate 'eventMessage ENDSWITH "_SBX"' --style compactViolations include:
network-outbound- blocked network connectionsfile-read*- blocked file readsfile-write*- blocked file writes
Filtered out (too noisy):
mach-lookup- IPC service lookupsfile-ioctl- device control operations/dev/tty*writes - terminal outputmDNSResponder- system DNS resolution/private/var/run/syslog- system logging
| Flag | Log stream | eBPF | Sandbox command |
|---|---|---|---|
-m |
Yes (macOS) | Yes (Linux) | No |
-d |
No | No | Yes |
-m -d |
Yes (macOS) | Yes (Linux) | Yes |
See docs/security-model.md for Greywall's threat model, guarantees, and limitations.