use std::path::PathBuf;
use miette::Result;
use super::{RuntimeSandboxPolicy, SandboxSpawnSpec, policy_paths_with_resolved};
const SANDBOX_EXECUTABLE: &str = "/usr/bin/sandbox-exec";
const SEATBELT_BASE_POLICY: &str = r#"(version 1)
(deny default)
(allow process-exec)
(allow process-fork)
(allow signal (target same-sandbox))
(allow process-info* (target same-sandbox))
(allow sysctl-read)
(allow ipc-posix-sem)
(allow mach-lookup)
(allow system-socket)
(allow pseudo-tty)
(allow file-read* file-write* file-ioctl (literal "/dev/ptmx"))
(allow file-read* file-write* (regex "^/dev/ttys[0-9]+$"))
(allow file-ioctl (regex "^/dev/ttys[0-9]+$"))
(allow file-read* file-test-existence file-read-metadata (subpath "/dev"))
(allow file-read* file-write* (literal "/dev/null"))
(allow file-read* file-write* (literal "/dev/zero"))
(allow file-read* file-write* (literal "/dev/tty"))
(allow file-read-data file-write-data (subpath "/dev/fd"))
(allow network-inbound (local ip))
(allow network-outbound)
"#;
#[cfg(test)]
pub fn wrap_shell_command(
policy: &RuntimeSandboxPolicy,
program: &str,
args: Vec<String>,
) -> Result<SandboxSpawnSpec> {
wrap_command(policy, PathBuf::from(program), args)
}
pub fn wrap_command(
policy: &RuntimeSandboxPolicy,
program: PathBuf,
args: Vec<String>,
) -> Result<SandboxSpawnSpec> {
let mut sections = vec![SEATBELT_BASE_POLICY.to_string()];
sections.push(render_read_policy(policy));
sections.push(render_write_policy(policy));
sections.push(render_protected_path_denies(policy));
let profile = sections
.into_iter()
.filter(|section| !section.trim().is_empty())
.collect::<Vec<_>>()
.join("\n\n");
let mut sandbox_args = vec![
"-p".to_string(),
profile,
"--".to_string(),
program.to_string_lossy().into_owned(),
];
sandbox_args.extend(args);
Ok(SandboxSpawnSpec {
program: PathBuf::from(SANDBOX_EXECUTABLE),
args: sandbox_args,
})
}
fn render_read_policy(policy: &RuntimeSandboxPolicy) -> String {
let fs = &policy.filesystem;
let mut lines = Vec::new();
if fs.full_disk_read {
lines.push(
"(allow file-read* file-map-executable file-test-existence file-read-metadata (subpath \"/\"))"
.to_string(),
);
} else {
for root in policy_paths_with_resolved(&fs.readable_roots) {
lines.push(format!(
"(allow file-read* file-map-executable file-test-existence file-read-metadata (subpath \"{}\"))",
root.display()
));
}
for root in &fs.writable_roots {
for root_path in policy_paths_with_resolved(std::slice::from_ref(&root.root)) {
lines.push(format!(
"(allow file-read* file-map-executable file-test-existence file-read-metadata (subpath \"{}\"))",
root_path.display()
));
}
}
}
lines.join("\n")
}
fn render_write_policy(policy: &RuntimeSandboxPolicy) -> String {
let fs = &policy.filesystem;
let mut lines = Vec::new();
if fs.full_disk_write {
lines.push("(allow file-write* (subpath \"/\"))".to_string());
} else {
for root in &fs.writable_roots {
for root_path in policy_paths_with_resolved(std::slice::from_ref(&root.root)) {
lines.push(format!(
"(allow file-write* file-read-metadata file-test-existence (subpath \"{}\"))",
root_path.display()
));
}
for subpath in policy_paths_with_resolved(&root.read_only_subpaths) {
lines.push(format!(
"(deny file-write* (subpath \"{}\"))",
subpath.display()
));
}
}
}
lines.join("\n")
}
fn render_protected_path_denies(policy: &RuntimeSandboxPolicy) -> String {
let mut lines = Vec::new();
for path in policy_paths_with_resolved(&policy.filesystem.deny_read_paths) {
lines.push(format!(
"(deny file-read* file-map-executable file-test-existence file-read-metadata (subpath \"{}\"))",
path.display()
));
}
for path in policy_paths_with_resolved(&policy.filesystem.deny_write_paths) {
lines.push(format!(
"(deny file-write* (subpath \"{}\"))",
path.display()
));
}
lines.join("\n")
}