Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 46 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Greywall wraps commands in a deny-by-default sandbox. Filesystem access is restricted to the current directory by default. Use `--learning` to trace what else a command needs and auto-generate a config profile. All network traffic is transparently redirected through [greyproxy](https://github.com/GreyhavenHQ/greyproxy), a deny-by-default transparent proxy with a live allow/deny dashboard. Run `greywall setup` to install greyproxy automatically.

*Supports Linux and macOS. See [platform support](docs/platform-support.md) for details.*
*Supports Linux, macOS, and Windows (via WSL). See [platform support](docs/platform-support.md) for details.*

https://github.com/user-attachments/assets/7d62d45d-a201-4f24-9138-b460e4c157a8

Expand Down Expand Up @@ -183,22 +183,51 @@ By default, traffic routes through the GreyProxy SOCKS5 proxy at `localhost:4305

## Platform support

| Feature | Linux | macOS |
|---------|:-----:|:-----:|
| **Sandbox engine** | bubblewrap | sandbox-exec (Seatbelt) |
| **Filesystem deny-by-default (read/write)** | ✅ | ✅ |
| **Syscall filtering** | ✅ (seccomp) | ✅ (Seatbelt) |
| **Filesystem access control** | ✅ (Landlock + bubblewrap) | ✅ (Seatbelt) |
| **Violation monitoring** | ✅ (eBPF) | ✅ (Seatbelt denial logs) |
| **Transparent proxy (full traffic capture)** | ✅ (tun2socks + TUN) | ❌ |
| **DNS capture** | ✅ (DNS bridge) | ❌ |
| **Proxy via env vars (SOCKS5 / HTTP)** | ✅ | ✅ |
| **Network isolation** | ✅ (network namespace) | N/A |
| **Command allow/deny lists** | ✅ | ✅ |
| **Environment sanitization** | ✅ | ✅ |
| **Learning mode** | ✅ (strace) | ✅ (eslogger, requires sudo) |
| **PTY support** | ✅ | ✅ |
| **External deps** | bwrap, socat | none |
| Feature | Linux | macOS | Windows (WSL) |
|---------|:-----:|:-----:|:-------------:|
| **Sandbox engine** | bubblewrap | sandbox-exec (Seatbelt) | bubblewrap (WSL 2) |
| **Filesystem deny-by-default (read/write)** | ✅ | ✅ | ✅ (WSL 2) |
| **Syscall filtering** | ✅ (seccomp) | ✅ (Seatbelt) | ✅ (WSL 2) |
| **Filesystem access control** | ✅ (Landlock + bubblewrap) | ✅ (Seatbelt) | ✅ (WSL 2) |
| **Violation monitoring** | ✅ (eBPF) | ✅ (Seatbelt denial logs) | ✅ (WSL 2) |
| **Transparent proxy (full traffic capture)** | ✅ (tun2socks + TUN) | ❌ | ✅ (WSL 2) |
| **DNS capture** | ✅ (DNS bridge) | ❌ | ✅ (WSL 2) |
| **Proxy via env vars (SOCKS5 / HTTP)** | ✅ | ✅ | ✅ |
| **Network isolation** | ✅ (network namespace) | N/A | ✅ (WSL 2) |
| **Command allow/deny lists** | ✅ | ✅ | ✅ |
| **Environment sanitization** | ✅ | ✅ | ✅ |
| **Learning mode** | ✅ (strace) | ✅ (eslogger, requires sudo) | ✅ (WSL 2) |
| **PTY support** | ✅ | ✅ | ✅ |
| **External deps** | bwrap, socat | none | bwrap, socat (inside WSL) |

### Windows (WSL)

Greywall runs on Windows through **WSL 2** (Windows Subsystem for Linux). WSL 2 provides a full Linux kernel, so all Linux sandboxing features — including bubblewrap, seccomp, Landlock, and network namespaces — work as expected.

**Requirements:**

1. **WSL 2** — WSL 1 is not supported (no real Linux kernel).
2. **systemd enabled** — required for bubblewrap namespace support. Add to `/etc/wsl.conf`:
```ini
[boot]
systemd = true
```
3. **`/etc/resolv.conf` must not be a symlink** — the default WSL-generated symlink breaks bwrap namespace setup. Fix it:
```bash
sudo rm /etc/resolv.conf
echo "nameserver 10.255.255.254" | sudo tee /etc/resolv.conf
```
Then disable auto-generation in `/etc/wsl.conf`:
```ini
[network]
generateResolvConf = false
```
4. **Restart WSL** after making changes:
```powershell
wsl --shutdown
```

Run `greywall check` to verify your WSL environment is correctly configured.

See [platform support](docs/platform-support.md) for more details.

Expand Down
51 changes: 51 additions & 0 deletions cmd/greywall/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
Expand Down Expand Up @@ -525,6 +526,29 @@ func runCheck(_ *cobra.Command, _ []string) error {

steps := sandbox.PrintDependencyStatus()

// WSL2: check for common issues that break bwrap namespace setup
if isWSL() {
if info, err := os.Lstat("/etc/resolv.conf"); err == nil && info.Mode()&os.ModeSymlink != 0 {
fmt.Println(sandbox.CheckFail("WSL2: /etc/resolv.conf is a symlink (bwrap will fail)"))
steps = append(steps,
"Fix WSL2 resolv.conf symlink:\n"+
" sudo rm /etc/resolv.conf && echo \"nameserver 10.255.255.254\" | sudo tee /etc/resolv.conf\n"+
" Add to /etc/wsl.conf: [network] generateResolvConf = false | [boot] systemd = true\n"+
" Then run: wsl --shutdown from PowerShell",
)
} else {
fmt.Println(sandbox.CheckOK("WSL2: /etc/resolv.conf is a real file"))
}
if !isSystemdEnabled() {
fmt.Println(sandbox.CheckFail("WSL2: systemd not enabled (required for bwrap namespace support)"))
steps = append(steps,
"Enable systemd in /etc/wsl.conf: add [boot] systemd = true, then run wsl --shutdown",
)
} else {
fmt.Println(sandbox.CheckOK("WSL2: systemd enabled"))
}
}

status := proxy.Detect()
brewManaged := proxy.IsBrewManaged(status.Path)

Expand Down Expand Up @@ -584,6 +608,33 @@ func runCheck(_ *cobra.Command, _ []string) error {
return nil
}

// isWSL reports whether the current environment is Windows Subsystem for Linux.
func isWSL() bool {
if runtime.GOOS != "linux" {
return false
}
// WSL sets the WSL_DISTRO_NAME environment variable
if os.Getenv("WSL_DISTRO_NAME") != "" {
return true
}
// Fallback: check /proc/version for Microsoft signature
data, err := os.ReadFile("/proc/version")
if err != nil {
return false
}
lower := strings.ToLower(string(data))
return strings.Contains(lower, "microsoft") || strings.Contains(lower, "wsl")
}

// isSystemdEnabled reports whether systemd is running as PID 1.
func isSystemdEnabled() bool {
target, err := os.Readlink("/proc/1/exe")
if err != nil {
return false
}
return strings.Contains(target, "systemd")
}

// newSetupCmd creates the setup subcommand for installing greyproxy.
func newSetupCmd() *cobra.Command {
return &cobra.Command{
Expand Down