use anyhow::{bail, Result};
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
extern "C" {
fn sandbox_init(profile: *const c_char, flags: u64, errorbuf: *mut *mut c_char) -> c_int;
fn sandbox_free_error(errorbuf: *mut c_char);
}
pub fn apply(dir: &std::path::Path) -> Result<()> {
let dir_str = dir
.to_str()
.ok_or_else(|| anyhow::anyhow!("sandbox: working directory path is not valid UTF-8"))?;
let home = dirs::home_dir().unwrap_or_default();
let home_str = home.to_str().unwrap_or_default().to_owned();
let xdg_data_home = home.join(".local").join("share");
let xdg_data_home_str = xdg_data_home.to_str().unwrap_or_default().to_owned();
let profile = format!(
r#"(version 1)
(allow default)
(deny file-read* file-write*
(subpath "{home}/.ssh")
(subpath "{home}/.gnupg")
(subpath "{home}/.aws")
(subpath "{home}/.kube")
(subpath "{home}/.config/gcloud")
(subpath "{home}/.azure")
(subpath "{home}/.config/op")
)
(deny file-write*
(subpath "/")
)
(allow file-write*
(subpath "{dir}")
(subpath "{xdg_data_home}")
(subpath "/dev")
(subpath "/tmp")
(subpath "/private/tmp")
(subpath "/private/var/folders")
)"#,
home = home_str,
dir = dir_str,
xdg_data_home = xdg_data_home_str,
);
let profile_cstr = CString::new(profile)
.map_err(|_| anyhow::anyhow!("sandbox: profile contains null byte"))?;
let mut errorbuf: *mut c_char = std::ptr::null_mut();
let ret = unsafe { sandbox_init(profile_cstr.as_ptr(), 0, &mut errorbuf) };
if ret != 0 {
let msg = if errorbuf.is_null() {
"unknown error".to_string()
} else {
let s = unsafe { CStr::from_ptr(errorbuf) }
.to_string_lossy()
.into_owned();
unsafe { sandbox_free_error(errorbuf) };
s
};
bail!("sandbox_init failed: {}", msg);
}
crate::log_info!(
"Sandbox active (Seatbelt): writes locked to {}",
dir.display()
);
Ok(())
}