diff --git a/README.md b/README.md index 3f0a66a..b8c65d2 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Create `~/.fence.json` to configure allowed domains and filesystem access: | `allowUnixSockets` | List of allowed Unix socket paths (macOS) | | `allowAllUnixSockets` | Allow all Unix sockets | | `allowLocalBinding` | Allow binding to local ports | +| `allowLocalOutbound` | Allow outbound connections to localhost, e.g., local DBs (defaults to `allowLocalBinding` if not set) | | `httpProxyPort` | Fixed port for HTTP proxy (default: random available port) | | `socksProxyPort` | Fixed port for SOCKS5 proxy (default: random available port) | @@ -138,6 +139,12 @@ import ( ) 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{ diff --git a/internal/config/config.go b/internal/config/config.go index f8f18ff..3940a2e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,6 +25,7 @@ type NetworkConfig struct { AllowUnixSockets []string `json:"allowUnixSockets,omitempty"` AllowAllUnixSockets bool `json:"allowAllUnixSockets,omitempty"` AllowLocalBinding bool `json:"allowLocalBinding,omitempty"` + AllowLocalOutbound *bool `json:"allowLocalOutbound,omitempty"` // If nil, defaults to AllowLocalBinding value HTTPProxyPort int `json:"httpProxyPort,omitempty"` SOCKSProxyPort int `json:"socksProxyPort,omitempty"` } diff --git a/internal/sandbox/macos.go b/internal/sandbox/macos.go index 4e99afc..9cb15d9 100644 --- a/internal/sandbox/macos.go +++ b/internal/sandbox/macos.go @@ -33,6 +33,7 @@ type MacOSSandboxParams struct { AllowUnixSockets []string AllowAllUnixSockets bool AllowLocalBinding bool + AllowLocalOutbound bool ReadDenyPaths []string WriteAllowPaths []string WriteDenyPaths []string @@ -419,10 +420,15 @@ func GenerateSandboxProfile(params MacOSSandboxParams) string { profile.WriteString("(allow network*)\n") } else { if params.AllowLocalBinding { + // Allow binding and inbound connections on localhost (for servers) profile.WriteString(`(allow network-bind (local ip "localhost:*")) (allow network-inbound (local ip "localhost:*")) -(allow network-outbound (local ip "localhost:*")) `) + // Process can make outbound connections to localhost + if params.AllowLocalOutbound { + profile.WriteString(`(allow network-outbound (local ip "localhost:*")) +`) + } } if params.AllowAllUnixSockets { @@ -492,6 +498,11 @@ func WrapCommandMacOS(cfg *config.Config, command string, httpPort, socksPort in // Enable local binding if ports are exposed or if explicitly configured allowLocalBinding := cfg.Network.AllowLocalBinding || len(exposedPorts) > 0 + allowLocalOutbound := allowLocalBinding + if cfg.Network.AllowLocalOutbound != nil { + allowLocalOutbound = *cfg.Network.AllowLocalOutbound + } + params := MacOSSandboxParams{ Command: command, NeedsNetworkRestriction: needsNetwork || len(cfg.Network.AllowedDomains) == 0, // Block if no domains allowed @@ -500,6 +511,7 @@ func WrapCommandMacOS(cfg *config.Config, command string, httpPort, socksPort in AllowUnixSockets: cfg.Network.AllowUnixSockets, AllowAllUnixSockets: cfg.Network.AllowAllUnixSockets, AllowLocalBinding: allowLocalBinding, + AllowLocalOutbound: allowLocalOutbound, ReadDenyPaths: cfg.Filesystem.DenyRead, WriteAllowPaths: allowPaths, WriteDenyPaths: cfg.Filesystem.DenyWrite, @@ -510,6 +522,9 @@ func WrapCommandMacOS(cfg *config.Config, command string, httpPort, socksPort in if debug && len(exposedPorts) > 0 { fmt.Fprintf(os.Stderr, "[fence:macos] Enabling local binding for exposed ports: %v\n", exposedPorts) } + if debug && allowLocalBinding && !allowLocalOutbound { + fmt.Fprintf(os.Stderr, "[fence:macos] Blocking localhost outbound (AllowLocalOutbound=false)\n") + } profile := GenerateSandboxProfile(params) diff --git a/pkg/fence/fence.go b/pkg/fence/fence.go index 2be24e3..6a1a40e 100644 --- a/pkg/fence/fence.go +++ b/pkg/fence/fence.go @@ -3,9 +3,15 @@ package fence import ( "github.com/Use-Tusk/fence/internal/config" + "github.com/Use-Tusk/fence/internal/platform" "github.com/Use-Tusk/fence/internal/sandbox" ) +// IsSupported returns true if the current platform supports sandboxing (macOS/Linux). +func IsSupported() bool { + return platform.IsSupported() +} + // Config is the configuration for fence. type Config = config.Config