//! Filesystem path tables for sandbox profiles. //! //! Collects device files, temp directories, sensitive deny-paths, and //! ecosystem (package-manager / toolchain) writable paths into helpers //! consumed by [`super::profiles`]. #[cfg(all(feature = "enforce", unix))] use std::path::Path; use std::path::PathBuf; // Kigi state directory /// Kigi state directory — always writable (`$KIGI_SHARE_DIR` or `~/.kigi`). pub(crate) fn kigi_home() -> PathBuf { kigi_config::kigi_home() } // Device files & directories /// Device files that need write access for normal tool operation. /// /// Without write access to these, common programs (git, curl, ssh, compilers) /// break because they can't open `/dev/null` as an output sink, allocate PTYs, /// or seed RNGs. /// /// These are individual files (use `allow_file`, not `allow_path`). /// `/dev/pts` is a directory (PTY slaves on Linux) so it uses `allow_path`. #[cfg(all(feature = "enforce", unix))] pub(crate) const DEVICE_FILES: &[&str] = &[ // output sink — used by virtually every CLI tool "/dev/null", // zero source — used by memory allocators "/dev/zero", // entropy — used by crypto/TLS "/dev/random", // entropy — used by crypto/TLS "/dev/urandom", // controlling terminal — used by git, ssh, gpg "/dev/tty", // PTY allocation — used by terminal spawning "/dev/ptmx", // file descriptor access (symlink to /proc/self/fd on Linux) "/dev/fd", ]; /// Device directories that need write access. #[cfg(all(feature = "enforce", unix))] pub(crate) const DEVICE_DIRS: &[&str] = &[ // PTY slaves (Linux) "/dev/pts", ]; // Temporary directories /// Temporary directories that need write access. /// /// On Linux, `/tmp` is the standard temp directory. /// On macOS, programs use both `/tmp` (symlink to `/private/tmp`) and /// `/private/var/folders/` (the real `TMPDIR` / `NSTemporaryDirectory()`). /// git, compilers, and other tools write temp files to `$TMPDIR` which /// resolves to `/private/var/folders/xx/.../T/` on macOS. #[cfg(all(feature = "enforce", unix))] pub(crate) fn temp_writable_paths() -> Vec { let mut paths = vec![PathBuf::from("/tmp"), PathBuf::from("/var/tmp")]; // macOS: /tmp → /private/tmp, but the real TMPDIR is under /private/var/folders. // Also include /private/tmp since Seatbelt may resolve the symlink. if cfg!(target_os = "macos") { for p in ["/private/tmp", "/private/var/tmp", "/private/var/folders"] { let pb = PathBuf::from(p); if pb.exists() && pb.is_dir() { paths.push(pb); } } } // Respect $TMPDIR if it points somewhere else (e.g. custom Linux setups). if let Ok(tmpdir) = std::env::var("TMPDIR") { let pb = PathBuf::from(&tmpdir); if pb.exists() && pb.is_dir() && !paths.contains(&pb) { paths.push(pb); } } paths } // Essential writable paths /// Writable directory paths for profiles that allow workspace writes (workspace, devbox, strict). /// Device files are handled separately via `allow_file` in `to_capability_set_with_config`. #[cfg(all(feature = "enforce", unix))] pub(crate) fn essential_writable_paths(workspace: &Path) -> Vec { let mut paths = vec![workspace.to_path_buf(), kigi_home()]; paths.extend(temp_writable_paths()); paths } /// Writable directory paths for the read-only profile (minimal: just ~/.kigi + temp). /// Device files are handled separately via `allow_file` in `to_capability_set_with_config`. #[cfg(all(feature = "enforce", unix))] pub(crate) fn essential_writable_paths_minimal() -> Vec { let mut paths = vec![kigi_home()]; paths.extend(temp_writable_paths()); paths }