pub mod carve;
pub mod profile;
#[cfg(target_os = "linux")]
#[path = "linux/mod.rs"]
mod platform;
#[cfg(target_os = "macos")]
#[path = "macos/mod.rs"]
mod platform;
#[cfg(target_os = "windows")]
#[path = "windows/mod.rs"]
mod platform;
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
#[path = "other.rs"]
mod platform;
use std::fmt;
use std::path::{Path, PathBuf};
use anyhow::Result;
use clap::ValueEnum;
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum Backend {
Auto,
Mount,
Landlock,
Locks,
Seatbelt,
}
impl fmt::Display for Backend {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Backend::Auto => "auto",
Backend::Mount => "mount",
Backend::Landlock => "landlock",
Backend::Locks => "locks",
Backend::Seatbelt => "seatbelt",
};
f.pad(name)
}
}
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
#[derive(Debug, Clone)]
pub enum Availability {
Available { detail: String },
Unavailable { reason: String },
}
impl Availability {
pub fn is_available(&self) -> bool {
matches!(self, Availability::Available { .. })
}
}
impl fmt::Display for Availability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Availability::Available { detail } if detail.is_empty() => f.write_str("available"),
Availability::Available { detail } => write!(f, "available ({detail})"),
Availability::Unavailable { reason } => write!(f, "unavailable — {reason}"),
}
}
}
pub struct Plan {
pub backend: Backend,
pub protected: Vec<PathBuf>,
pub pinned: Vec<PathBuf>,
pub carve: Option<carve::Carve>,
pub profile: Option<String>,
}
impl Plan {
pub fn build(backend: Backend, root: &Path, protected: Vec<PathBuf>) -> Plan {
let carve =
(backend == Backend::Landlock).then(|| carve::plan(&protected, &carve::read_dir));
let pinned = match backend {
Backend::Mount | Backend::Locks | Backend::Seatbelt => {
pinned_directories(root, &protected)
}
_ => Vec::new(),
};
let profile = (backend == Backend::Seatbelt)
.then(|| profile::build(&protected, &pinned, &profile::on_disk));
Plan {
backend,
protected,
pinned,
carve,
profile,
}
}
}
pub fn pinned_directories(root: &Path, protected: &[PathBuf]) -> Vec<PathBuf> {
let mut pinned = std::collections::BTreeSet::new();
for path in protected {
for ancestor in path.ancestors().skip(1) {
if !ancestor.starts_with(root) {
break;
}
pinned.insert(ancestor.to_path_buf());
if ancestor == root {
break;
}
}
}
pinned.into_iter().collect()
}
pub fn availability() -> Vec<(Backend, Availability)> {
platform::availability()
}
#[cfg(target_os = "windows")]
pub use platform::guard;
#[cfg(not(target_os = "windows"))]
#[path = "unguarded.rs"]
pub mod guard;
pub fn resolve(requested: Backend) -> Result<Backend> {
let availability = availability();
let find = |backend: Backend| {
availability
.iter()
.find(|(candidate, _)| *candidate == backend)
.map(|(_, status)| status.clone())
.unwrap_or(Availability::Unavailable {
reason: format!(
"the {backend} backend does not exist on {}",
std::env::consts::OS
),
})
};
match requested {
Backend::Auto => {
for backend in [
Backend::Mount,
Backend::Landlock,
Backend::Seatbelt,
Backend::Locks,
] {
if find(backend).is_available() {
return Ok(backend);
}
}
let reasons = availability
.iter()
.map(|(backend, status)| format!("\n {backend:<9} {status}"))
.collect::<String>();
anyhow::bail!(
"no enforcement backend is available:{reasons}\n\n\
Nothing here can stop an agent from writing to the protected paths.\n\
`ralon hook install` refuses an agent's own edit tools; running the\n\
agent under WSL or Linux is the only enforcement."
)
}
backend => match find(backend) {
Availability::Available { .. } => Ok(backend),
Availability::Unavailable { reason } => {
anyhow::bail!("the {backend} backend is unavailable — {reason}")
}
},
}
}
pub fn enforce_and_exec(
plan: &Plan,
command: &[std::ffi::OsString],
) -> Result<std::process::ExitCode> {
platform::enforce_and_exec(plan, command)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pins_every_directory_down_to_a_protected_file() {
let root = Path::new("/p");
let protected = vec![
PathBuf::from("/p/src/deep/index.tsx"),
PathBuf::from("/p/.env"),
];
assert_eq!(
pinned_directories(root, &protected),
[
PathBuf::from("/p"),
PathBuf::from("/p/src"),
PathBuf::from("/p/src/deep"),
]
);
}
#[test]
fn pinning_stops_at_the_project_root() {
let root = Path::new("/p");
let pinned = pinned_directories(root, &[PathBuf::from("/p/a")]);
assert_eq!(pinned, [PathBuf::from("/p")]);
}
#[test]
fn nothing_protected_means_nothing_pinned() {
assert!(pinned_directories(Path::new("/p"), &[]).is_empty());
}
}