use std::path::Path;
#[derive(Debug, Clone)]
#[non_exhaustive]
#[allow(
clippy::struct_excessive_bools,
reason = "each bool represents an independent capability flag"
)]
pub struct HostCapabilities {
pub virtualization: bool,
pub namespaces: bool,
pub seccomp: bool,
pub mandatory_access_control: bool,
pub cgroups: bool,
pub landlock: bool,
}
#[must_use]
pub fn check_host() -> HostCapabilities {
HostCapabilities {
virtualization: check_virtualization(),
namespaces: check_namespaces(),
seccomp: check_seccomp(),
mandatory_access_control: check_mac(),
cgroups: check_cgroups(),
landlock: check_landlock(),
}
}
#[must_use]
pub fn audit_isolation(caps: &HostCapabilities) -> Vec<String> {
let mut warnings = Vec::new();
if !caps.virtualization {
warnings.push("no hardware virtualization detected — VMs will not run".to_owned());
}
#[cfg(target_os = "linux")]
{
if !caps.namespaces {
warnings.push(
"no namespace isolation (bubblewrap not found) — shim runs without namespaces"
.to_owned(),
);
}
if !caps.seccomp {
warnings
.push("seccomp BPF not available — shim runs without syscall filtering".to_owned());
}
if !caps.mandatory_access_control {
warnings.push(
"no MAC (AppArmor/SELinux/Seatbelt) — no mandatory access control".to_owned(),
);
}
if !caps.landlock {
warnings.push(
"Landlock LSM not available — filesystem restrictions degraded unless fail-closed"
.to_owned(),
);
}
}
#[cfg(target_os = "macos")]
{
if !caps.mandatory_access_control {
warnings.push("sandbox-exec not found — no mandatory access control".to_owned());
}
}
warnings
}
fn check_virtualization() -> bool {
#[cfg(target_os = "linux")]
{
Path::new("/dev/kvm").exists()
}
#[cfg(target_os = "macos")]
{
std::process::Command::new("sysctl")
.args(["-n", "kern.hv_support"])
.output()
.is_ok_and(|o| o.stdout.starts_with(b"1"))
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
false
}
}
#[allow(clippy::missing_const_for_fn, reason = "body is non-const on Linux")]
fn check_namespaces() -> bool {
#[cfg(target_os = "linux")]
{
which("bwrap")
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
#[allow(clippy::missing_const_for_fn, reason = "body is non-const on Linux")]
fn check_seccomp() -> bool {
#[cfg(target_os = "linux")]
{
Path::new("/proc/sys/kernel/seccomp").exists() || Path::new("/proc/self/status").exists()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
fn check_mac() -> bool {
#[cfg(target_os = "linux")]
{
Path::new("/sys/kernel/security/apparmor").exists() || Path::new("/sys/fs/selinux").exists()
}
#[cfg(target_os = "macos")]
{
which("sandbox-exec")
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
false
}
}
#[allow(clippy::missing_const_for_fn, reason = "body is non-const on Linux")]
fn check_cgroups() -> bool {
#[cfg(target_os = "linux")]
{
Path::new("/sys/fs/cgroup/cgroup.controllers").exists()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
#[allow(
clippy::missing_const_for_fn,
reason = "Linux probes via non-const Ruleset APIs"
)]
fn check_landlock() -> bool {
#[cfg(target_os = "linux")]
{
bux_landlock::is_available()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
#[allow(dead_code, reason = "used from cfg-gated PATH probes")]
fn which(name: &str) -> bool {
std::env::var("PATH")
.unwrap_or_default()
.split(':')
.any(|dir| Path::new(dir).join(name).is_file())
}
#[cfg(test)]
#[allow(
clippy::let_underscore_must_use,
reason = "tests use let _ for clarity"
)]
mod tests {
use super::*;
#[test]
fn host_check_returns_struct() {
let caps = check_host();
let _ = format!("{caps:?}");
}
#[test]
fn audit_reports_missing_features() {
#[cfg(target_os = "linux")]
{
let caps = HostCapabilities {
virtualization: true,
namespaces: false,
seccomp: false,
mandatory_access_control: false,
cgroups: false,
landlock: false,
};
let warnings = audit_isolation(&caps);
assert!(warnings.len() >= 4);
assert!(warnings.iter().any(|w| w.contains("namespace")));
assert!(warnings.iter().any(|w| w.contains("Landlock")));
}
#[cfg(target_os = "macos")]
{
let caps = HostCapabilities {
virtualization: false,
namespaces: false,
seccomp: false,
mandatory_access_control: false,
cgroups: false,
landlock: false,
};
let warnings = audit_isolation(&caps);
assert_eq!(warnings.len(), 2);
assert!(warnings.iter().any(|w| w.contains("virtualization")));
assert!(warnings.iter().any(|w| w.contains("sandbox-exec")));
assert!(!warnings.iter().any(|w| w.contains("namespace")));
assert!(!warnings.iter().any(|w| w.contains("Landlock")));
assert!(!warnings.iter().any(|w| w.contains("seccomp")));
}
}
}