This repository has been archived on 2026-03-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
greywall/docs/library.md
2026-01-17 16:14:18 -08:00

6.9 KiB

Library Usage (Go)

Fence can be used as a Go library to sandbox commands programmatically.

Installation

go get github.com/Use-Tusk/fence

Quick Start

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

if !fence.IsSupported() {
    log.Fatal("Platform not supported")
}

DefaultConfig() *Config

Returns a default configuration with all network blocked.

cfg := fence.DefaultConfig()
cfg.Network.AllowedDomains = []string{"example.com"}

LoadConfig(path string) (*Config, error)

Loads configuration from a JSON file. Supports JSONC (comments allowed).

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.

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

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

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

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

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

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

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

cfg := &fence.Config{
    Network: fence.NetworkConfig{
        AllowedDomains: []string{
            "registry.npmjs.org",
            "*.github.com",
            "api.openai.com",
        },
    },
}

Restrict filesystem access

cfg := &fence.Config{
    Filesystem: fence.FilesystemConfig{
        AllowWrite: []string{".", "/tmp"},
        DenyRead:   []string{"~/.ssh", "~/.aws"},
    },
}

Block dangerous commands

cfg := &fence.Config{
    Command: fence.CommandConfig{
        Deny: []string{
            "rm -rf /",
            "git push",
            "npm publish",
        },
    },
}

Expose dev server port

manager := fence.NewManager(cfg, false, false)
manager.SetExposedPorts([]int{3000})
defer manager.Cleanup()

wrapped, _ := manager.WrapCommand("npm run dev")

Load and extend config

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:

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