diff --git a/README.md b/README.md index fb27754..19b7f0a 100644 --- a/README.md +++ b/README.md @@ -2,60 +2,37 @@ ![GitHub Release](https://img.shields.io/github/v/release/Use-Tusk/fence) -A Go implementation of process sandboxing with network and filesystem restrictions. - Fence wraps commands in a sandbox that blocks network access by default and restricts filesystem operations based on configurable rules. It's most useful for running semi-trusted code (package installs, build scripts, CI jobs, unfamiliar repos) with controlled side effects, and it can also complement AI coding agents as defense-in-depth. -You can also think of Fence as a permission manager for your CLI coding agents. +You can also think of Fence as a permission manager for your CLI agents. -## Features +```bash +# Block all network access (default) +fence curl https://example.com # → 403 Forbidden -- **Network Isolation**: All network access blocked by default -- **Domain Allowlisting**: Configure which domains are allowed -- **Filesystem Restrictions**: Control read/write access to paths -- **Command Blocking**: Block dangerous commands (e.g., `shutdown`, `rm -rf`) with configurable deny/allow lists -- **SSH Command Filtering**: Control which hosts and commands are allowed over SSH -- **Violation Monitoring**: Real-time logging of blocked requests and sandbox denials -- **Cross-Platform**: macOS (sandbox-exec) and Linux (bubblewrap) -- **HTTP/SOCKS5 Proxies**: Built-in filtering proxies for domain control -- **Permission Import**: Using Claude Code? Import your Claude permissions as Fence configs with `fence import --claude -o ~/.fence.json` +# Allow specific domains +fence -t code npm install # → uses 'code' template with npm/pypi/etc allowed -Fence can be used as a Go package or CLI tool. +# Block dangerous commands +fence -c "rm -rf /" # → blocked by command deny rules +``` -## Documentation - -- [Documentation index](docs/) -- [Configuration](docs/configuration.md) -- [Security model](docs/security-model.md) -- [Architecture](ARCHITECTURE.md) -- [Examples](examples/) - -## Installation - -At the moment, we only support macOS and Linux. For Windows users, we recommend using WSL. - -### Quick Install (Recommended) +## Install ```bash curl -fsSL https://raw.githubusercontent.com/Use-Tusk/fence/main/install.sh | sh ``` -To install a specific version: +
+Other installation methods -```bash -curl -fsSL https://raw.githubusercontent.com/Use-Tusk/fence/main/install.sh | sh -s -- v0.1.0 -``` - -### Install via Go - -If you have Go installed: +**Go install:** ```bash go install github.com/Use-Tusk/fence/cmd/fence@latest ``` -
-Build from source +**Build from source:** ```bash git clone https://github.com/Use-Tusk/fence @@ -69,12 +46,14 @@ go build -o fence ./cmd/fence - `bubblewrap` (for sandboxing) - `socat` (for network bridging) -- `bpftrace` (optional, for filesystem violation visibility with when monitoring with `-m`) +- `bpftrace` (optional, for filesystem violation visibility when monitoring with `-m`) -## Quick Start +## Usage + +### Basic ```bash -# This will be blocked (no domains allowed by default) +# Run command with all network blocked (no domains allowed by default) fence curl https://example.com # Run with shell expansion @@ -82,116 +61,60 @@ fence -c "echo hello && ls" # Enable debug logging fence -d curl https://example.com -``` -For a more detailed introduction, see the [Quickstart Guide](docs/quickstart.md). +# Use a template +fence -t code -- claude # Runs Claude Code using `code` template config -## CLI Usage - -```text -fence [flags] -- [command...] - -Flags: - -c string Run command string directly (like sh -c) - -d, --debug Enable debug logging (shows sandbox command, proxy activity, filter rules) - -m, --monitor Monitor mode (shows blocked requests and violations only) - -p, --port Expose port for inbound connections (can be repeated) - -s, --settings Path to settings file (default: ~/.fence.json) - -t, --template Use built-in template (e.g., code, local-dev-server) - -v, --version Show version information - -h, --help Help for fence - -Subcommands: - import Import settings from other tools (e.g., --claude for Claude Code) -``` - -### Examples - -```bash -# Block all network (default behavior) -fence curl https://example.com -# Output: curl: (56) CONNECT tunnel failed, response 403 - -# Use a built-in template -fence -t code -- claude - -# Extend a template in your config (adds private registry to 'code' template) -# ~/.fence.json: {"extends": "code", "network": {"allowedDomains": ["private.company.com"]}} -fence npm install - -# Use a custom config -fence --settings ./my-config.json npm install - -# Block specific commands (via config file) -# ~/.fence.json: {"command": {"deny": ["git push", "npm publish"]}} -fence -c "git push" # blocked -fence -c "git status" # allowed - -# Run a shell command -fence -c "git clone https://github.com/user/repo && cd repo && npm install" - -# Debug mode shows proxy activity -fence -d wget https://example.com - -# Monitor mode shows violations/blocked requests only +# Monitor mode (shows violations) fence -m npm install -# Expose a port for inbound connections -fence -p 3000 -c "npm run dev" - -# Import settings from Claude Code -fence import --claude -o .fence.json +# Show all commands and options +fence --help ``` -## Library Usage +### Configuration -```go -package main +Fence reads from `~/.fence.json` by default: -import ( - "fmt" - "github.com/Use-Tusk/fence/pkg/fence" -) - -func main() { - // Check if platform supports sandboxing (macOS/Linux) - if !fence.IsSupported() { - fmt.Println("Sandboxing not supported on this platform") - return - } - - // Create config - cfg := &fence.Config{ - Network: fence.NetworkConfig{ - AllowedDomains: []string{"api.example.com"}, - }, - Filesystem: fence.FilesystemConfig{ - AllowWrite: []string{"."}, - }, - Command: fence.CommandConfig{ - Deny: []string{"git push", "npm publish"}, - }, - } - - // Create manager (debug=false, monitor=false) - manager := fence.NewManager(cfg, false, false) - defer manager.Cleanup() - - // Initialize (starts proxies) - if err := manager.Initialize(); err != nil { - panic(err) - } - - // Wrap a command - wrapped, err := manager.WrapCommand("curl https://api.example.com/data") - if err != nil { - panic(err) - } - - fmt.Println("Sandboxed command:", wrapped) +```json +{ + "extends": "code", + "network": { "allowedDomains": ["private.company.com"] }, + "filesystem": { "allowWrite": ["."] }, + "command": { "deny": ["git push", "npm publish"] } } ``` +Use `fence --settings ./custom.json` to specify a different config. + +### Import from Claude Code + +```bash +fence import --claude -o ~/.fence.json +``` + +## Features + +- **Network isolation** - All outbound blocked by default; allowlist domains via config +- **Filesystem restrictions** - Control read/write access paths +- **Command blocking** - Deny dangerous commands like `rm -rf /`, `git push` +- **SSH Command Filtering** - Control which hosts and commands are allowed over SSH +- **Built-in templates** - Pre-configured rulesets for common workflows +- **Violation monitoring** - Real-time logging of blocked requests (`-m`) +- **Cross-platform** - macOS (sandbox-exec) + Linux (bubblewrap) + +Fence can be used as a Go package or CLI tool. + +## Documentation + +- [Index](/docs/README.md) +- [Quickstart Guide](docs/quickstart.md) +- [Configuration Reference](docs/configuration.md) +- [Security Model](docs/security-model.md) +- [Architecture](ARCHITECTURE.md) +- [Library Usage (Go)](docs/library.md) +- [Examples](examples/) + ## Attribution -This project was inspired by Anthropic's [sandbox-runtime](https://github.com/anthropic-experimental/sandbox-runtime). +Inspired by Anthropic's [sandbox-runtime](https://github.com/anthropic-experimental/sandbox-runtime). diff --git a/docs/README.md b/docs/README.md index 702906a..53454d3 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,7 +17,8 @@ Fence is a sandboxing tool that restricts network and filesystem access for arbi ## Reference -- [README](../README.md) - CLI + library usage +- [README](../README.md) - CLI usage +- [Library Usage (Go)](library.md) - Using Fence as a Go package - [Configuration](./configuration.md) - How to configure Fence - [Architecture](../ARCHITECTURE.md) - How fence works under the hood - [Security model](security-model.md) - Threat model, guarantees, and limitations diff --git a/docs/library.md b/docs/library.md new file mode 100644 index 0000000..50948b9 --- /dev/null +++ b/docs/library.md @@ -0,0 +1,312 @@ +# Library Usage (Go) + +Fence can be used as a Go library to sandbox commands programmatically. + +## Installation + +```bash +go get github.com/Use-Tusk/fence +``` + +## Quick Start + +```go +package main + +import ( + "fmt" + "os/exec" + + "github.com/Use-Tusk/fence/pkg/fence" +) + +func main() { + // Check platform support + if !fence.IsSupported() { + fmt.Println("Sandboxing not supported on this platform") + return + } + + // Create config + cfg := &fence.Config{ + Network: fence.NetworkConfig{ + AllowedDomains: []string{"api.example.com"}, + }, + } + + // Create and initialize manager + manager := fence.NewManager(cfg, false, false) + defer manager.Cleanup() + + if err := manager.Initialize(); err != nil { + panic(err) + } + + // Wrap the command + wrapped, err := manager.WrapCommand("curl https://api.example.com/data") + if err != nil { + panic(err) + } + + // Execute it + cmd := exec.Command("sh", "-c", wrapped) + output, _ := cmd.CombinedOutput() + fmt.Println(string(output)) +} +``` + +## API Reference + +### Functions + +#### `IsSupported() bool` + +Returns `true` if the current platform supports sandboxing (macOS or Linux). + +```go +if !fence.IsSupported() { + log.Fatal("Platform not supported") +} +``` + +#### `DefaultConfig() *Config` + +Returns a default configuration with all network blocked. + +```go +cfg := fence.DefaultConfig() +cfg.Network.AllowedDomains = []string{"example.com"} +``` + +#### `LoadConfig(path string) (*Config, error)` + +Loads configuration from a JSON file. Supports JSONC (comments allowed). + +```go +cfg, err := fence.LoadConfig("~/.fence.json") +if err != nil { + log.Fatal(err) +} +if cfg == nil { + cfg = fence.DefaultConfig() // File doesn't exist +} +``` + +#### `DefaultConfigPath() string` + +Returns the default config file path (`~/.fence.json`). + +#### `NewManager(cfg *Config, debug, monitor bool) *Manager` + +Creates a new sandbox manager. + +| Parameter | Description | +|-----------|-------------| +| `cfg` | Configuration for the sandbox | +| `debug` | Enable verbose logging (proxy activity, sandbox commands) | +| `monitor` | Log only violations (blocked requests) | + +### Manager Methods + +#### `Initialize() error` + +Sets up sandbox infrastructure (starts HTTP and SOCKS proxies). Called automatically by `WrapCommand` if not already initialized. + +```go +manager := fence.NewManager(cfg, false, false) +defer manager.Cleanup() + +if err := manager.Initialize(); err != nil { + log.Fatal(err) +} +``` + +#### `WrapCommand(command string) (string, error)` + +Wraps a shell command with sandbox restrictions. Returns an error if: + +- The command is blocked by policy (`command.deny`) +- The platform is unsupported +- Initialization fails + +```go +wrapped, err := manager.WrapCommand("npm install") +if err != nil { + // Command may be blocked by policy + log.Fatal(err) +} +``` + +#### `SetExposedPorts(ports []int)` + +Sets ports to expose for inbound connections (e.g., dev servers). + +```go +manager.SetExposedPorts([]int{3000, 8080}) +``` + +#### `Cleanup()` + +Stops proxies and releases resources. Always call via `defer`. + +#### `HTTPPort() int` / `SOCKSPort() int` + +Returns the ports used by the filtering proxies. + +## Configuration Types + +### Config + +```go +type Config struct { + Extends string // Template to extend (e.g., "code") + Network NetworkConfig + Filesystem FilesystemConfig + Command CommandConfig + SSH SSHConfig + AllowPty bool // Allow PTY allocation +} +``` + +### NetworkConfig + +```go +type NetworkConfig struct { + AllowedDomains []string // Domains to allow (supports *.example.com) + DeniedDomains []string // Domains to explicitly deny + AllowUnixSockets []string // Specific Unix socket paths to allow + AllowAllUnixSockets bool // Allow all Unix socket connections + AllowLocalBinding bool // Allow binding to localhost ports + AllowLocalOutbound *bool // Allow outbound to localhost (defaults to AllowLocalBinding) + HTTPProxyPort int // Override HTTP proxy port (0 = auto) + SOCKSProxyPort int // Override SOCKS proxy port (0 = auto) +} +``` + +### FilesystemConfig + +```go +type FilesystemConfig struct { + DenyRead []string // Paths to deny read access + AllowWrite []string // Paths to allow write access + DenyWrite []string // Paths to explicitly deny write access + AllowGitConfig bool // Allow read access to ~/.gitconfig +} +``` + +### CommandConfig + +```go +type CommandConfig struct { + Deny []string // Command patterns to block + Allow []string // Exceptions to deny rules + UseDefaults *bool // Use default deny list (true if nil) +} +``` + +### SSHConfig + +```go +type SSHConfig struct { + AllowedHosts []string // Host patterns to allow (supports wildcards) + DeniedHosts []string // Host patterns to deny + AllowedCommands []string // Commands allowed over SSH + DeniedCommands []string // Commands denied over SSH + AllowAllCommands bool // Use denylist mode instead of allowlist + InheritDeny bool // Apply global command.deny rules to SSH +} +``` + +## Examples + +### Allow specific domains + +```go +cfg := &fence.Config{ + Network: fence.NetworkConfig{ + AllowedDomains: []string{ + "registry.npmjs.org", + "*.github.com", + "api.openai.com", + }, + }, +} +``` + +### Restrict filesystem access + +```go +cfg := &fence.Config{ + Filesystem: fence.FilesystemConfig{ + AllowWrite: []string{".", "/tmp"}, + DenyRead: []string{"~/.ssh", "~/.aws"}, + }, +} +``` + +### Block dangerous commands + +```go +cfg := &fence.Config{ + Command: fence.CommandConfig{ + Deny: []string{ + "rm -rf /", + "git push", + "npm publish", + }, + }, +} +``` + +### Expose dev server port + +```go +manager := fence.NewManager(cfg, false, false) +manager.SetExposedPorts([]int{3000}) +defer manager.Cleanup() + +wrapped, _ := manager.WrapCommand("npm run dev") +``` + +### Load and extend config + +```go +cfg, err := fence.LoadConfig(fence.DefaultConfigPath()) +if err != nil { + log.Fatal(err) +} +if cfg == nil { + cfg = fence.DefaultConfig() +} + +// Add additional restrictions +cfg.Command.Deny = append(cfg.Command.Deny, "dangerous-cmd") +``` + +## Error Handling + +`WrapCommand` returns an error when a command is blocked: + +```go +wrapped, err := manager.WrapCommand("git push origin main") +if err != nil { + // err.Error() = "command blocked by policy: git push origin main" + fmt.Println("Blocked:", err) + return +} +``` + +## Platform Differences + +| Feature | macOS | Linux | +|---------|-------|-------| +| Sandbox mechanism | sandbox-exec | bubblewrap | +| Network isolation | HTTP/SOCKS proxy | Network namespace + proxy | +| Filesystem restrictions | Seatbelt profiles | Bind mounts | +| Requirements | None | `bubblewrap`, `socat` | + +## Thread Safety + +- `Manager` instances are **not** thread-safe +- Create one manager per goroutine, or synchronize access +- Proxies are shared and handle concurrent connections