//go:build linux // +build linux package platform import ( "os" ) // bubblewrapConfig contains configuration for Bubblewrap sandbox behavior. // This allows for tuning sandbox isolation without hardcoding magic values // throughout the translator. type bubblewrapConfig struct { // Essential system paths that are always mounted read-only for package managers // to function. These paths provide access to system libraries, binaries, and // runtime dependencies. essentialSystemPaths []string // Essential device files that must be accessible in the sandbox. // These are critical for basic I/O operations and random number generation. essentialDevices []string // Proc filesystem paths to mount. The /proc filesystem provides runtime // information about processes, system resources, and kernel parameters. procPaths []string // Maximum depth for glob pattern expansion. Limits filesystem traversal // to prevent excessive scanning of deep directory trees. // Set to 0 for unlimited depth (not recommended). maxGlobDepth int // Maximum number of paths to expand from a single glob pattern. // Prevents memory exhaustion from patterns matching huge directory trees. maxGlobPaths int // Glob fallback threshold for coarse-grained binding. // When glob expansion yields more than this many paths, fallback to binding // the parent directory instead of individual files for scalability. globFallbackThreshold int // Total argument limit for bwrap command. // Warns when total arguments exceed this limit (approaching ARG_MAX). totalArgsLimit int // Whether to unshare the network namespace by default if policy has no network rules. // When true and no network rules specified, completely isolates network access. unshareNetworkByDefault bool // Whether to unshare the PID namespace. Isolates process tree visibility. // Recommended for security but may break some package managers that inspect processes. unsharePID bool // Whether to unshare the IPC namespace. Isolates System V IPC and POSIX message queues. unshareIPC bool // Whether to create a new session (setsid). Detaches from terminal session. newSession bool // Whether to die when parent process exits. Ensures cleanup of orphaned sandboxes. dieWithParent bool // Seccomp filter configuration seccomp seccompConfig // Maximum depth to scan for mandatory deny patterns (e.g., .env files in subdirectories) // Set to 0 to only check literal paths, higher values scan subdirectories. mandatoryDenyScanDepth int } // seccompConfig contains seccomp-bpf filter settings type seccompConfig struct { // Whether to enable seccomp filtering enabled bool // Path to seccomp filter file (BPF bytecode) // If empty, uses built-in default filter filterPath string // Syscalls to deny (blocklist approach) // Common dangerous syscalls: ptrace, kexec_load, module_init, etc. deniedSyscalls []string } // newDefaultBubblewrapConfig creates a bubblewrap config with safe default values. // These defaults are based on: // - Common Linux filesystem layouts // - Anthropic Sandbox Runtime implementation patterns // // This config is for maintaining safe defaults for the sandbox. Future enhancements // will allow the user to override the config through policy or sandbox config available at PMG level. func newDefaultBubblewrapConfig() *bubblewrapConfig { return &bubblewrapConfig{ // Essential system paths (read-only) // Based on Filesystem Hierarchy Standard (FHS) essentialSystemPaths: []string{ "/usr", // User binaries, libraries, documentation "/lib", // Essential shared libraries "/lib64", // 64-bit libraries (on x86_64 systems) "/bin", // Essential command binaries (may be symlink to /usr/bin) "/sbin", // System binaries (may be symlink to /usr/sbin) "/etc", // System configuration files (read-only access needed for DNS, etc.) "/opt", // Optional application software packages "/var/lib", // Variable state information (package databases, etc.) "/sys", // Sysfs - kernel and device information }, // Essential device files // Required for basic I/O, randomness, and null device operations essentialDevices: []string{ "/dev/null", "/dev/zero", "/dev/random", "/dev/urandom", "/dev/full", "/dev/tty", // For terminal operations }, // Proc filesystem paths // Provides process and system information procPaths: []string{ "/proc", // Full proc filesystem }, // Glob expansion limits // Conservative defaults to prevent DoS via huge glob patterns maxGlobDepth: 5, // Scan up to 5 directory levels maxGlobPaths: 10000, // Maximum 10k paths per glob pattern globFallbackThreshold: 100, // Fallback to parent dir above 100 paths totalArgsLimit: 8000, // Total bwrap argument safety limit // Network isolation (default: isolate network if no rules) unshareNetworkByDefault: true, // Process/IPC isolation unsharePID: true, // Isolate PID namespace unshareIPC: true, // Isolate IPC namespace newSession: true, // Create new session dieWithParent: true, // Cleanup on parent exit // Seccomp configuration seccomp: seccompConfig{ enabled: false, // Disabled by default (Phase 4 enhancement) filterPath: "", // Use built-in filter when enabled deniedSyscalls: []string{ // Dangerous syscalls that should be blocked "ptrace", // Process tracing (debugging/injection) "kexec_load", // Load new kernel "module_init", // Load kernel modules "reboot", // System reboot "swapon", // Enable swap "swapoff", // Disable swap "mount", // Mount filesystems "umount", // Unmount filesystems "pivot_root", // Change root filesystem "chroot", // Change root directory "unshare", // Create new namespaces (prevent nested sandboxing) "setns", // Join existing namespace "acct", // Process accounting "add_key", // Add key to kernel keyring "request_key", // Request key from kernel "keyctl", // Manipulate kernel keyring "ioperm", // Set port I/O permissions "iopl", // Set I/O privilege level "perf_event_open", // Performance monitoring }, }, // Scan depth for finding dangerous files in project directories mandatoryDenyScanDepth: 3, } } // shouldUnshareNetwork determines whether to isolate network based on policy. // Returns true if network should be completely isolated (--unshare-net). func (c *bubblewrapConfig) shouldUnshareNetwork(hasAllowRules bool, hasDenyAll bool) bool { // If there are allow rules, don't isolate network // Note: bubblewrap can't do per-host filtering, so allow rules mean "allow network" // The allow_outbound rules serve as documentation of intended access if hasAllowRules { return false } // No allow rules - check if we should deny all if hasDenyAll { return true } // No allow rules and no deny-all - use default behavior return c.unshareNetworkByDefault } // getEssentialSystemPaths returns essential system paths for read-only binding. // Filters out paths that don't exist on this system (e.g., /lib64 on 32-bit). func (c *bubblewrapConfig) getEssentialSystemPaths() []string { existingPaths := make([]string, 0, len(c.essentialSystemPaths)) for _, path := range c.essentialSystemPaths { if _, err := os.Stat(path); err == nil { existingPaths = append(existingPaths, path) } } return existingPaths } // getEssentialDevices returns essential device files for binding. // Filters out devices that don't exist on this system. func (c *bubblewrapConfig) getEssentialDevices() []string { existingDevices := make([]string, 0, len(c.essentialDevices)) for _, device := range c.essentialDevices { if _, err := os.Stat(device); err == nil { existingDevices = append(existingDevices, device) } } return existingDevices }