pub const KNOWN_ENGINES: &[&str] = &["docker", "podman"];
pub fn detect_container_engine() -> Option<String> {
detect_container_engine_with(&binary_on_path)
}
pub fn detect_container_engine_with(exists: &dyn Fn(&str) -> bool) -> Option<String> {
KNOWN_ENGINES
.iter()
.find(|bin| exists(bin))
.map(|bin| bin.to_string())
}
pub fn binary_on_path(bin: &str) -> bool {
binary_on_path_in(std::env::var_os("PATH"), bin)
}
fn binary_on_path_in(path: Option<std::ffi::OsString>, bin: &str) -> bool {
match path {
Some(paths) => std::env::split_paths(&paths).any(|dir| dir.join(bin).is_file()),
None => false,
}
}
pub fn namespace_supported() -> bool {
cfg!(target_os = "linux")
}
#[derive(Debug, Clone)]
pub struct ContainerRunSpec<'a> {
pub engine: &'a str,
pub image: &'a str,
pub workdir: &'a str,
pub network: bool,
pub mounts: &'a [String],
pub name: &'a str,
}
const FORBIDDEN_MOUNTS: &[&str] = &[
"/",
"/proc",
"/sys",
"/dev",
"/etc",
"/boot",
"/var/run",
"/run",
"/var/run/docker.sock",
"/run/docker.sock",
"/var/run/podman",
];
pub fn mount_allowed(path: &str) -> bool {
let absolute = path.starts_with('/');
let traverses = path.split('/').any(|segment| segment == "..");
if !absolute || traverses {
return false;
}
let normalized = path.trim_end_matches('/');
let normalized = match normalized.is_empty() {
true => "/",
false => normalized,
};
!FORBIDDEN_MOUNTS.iter().any(|forbidden| {
normalized == *forbidden
|| (*forbidden != "/" && normalized.starts_with(&format!("{forbidden}/")))
|| *forbidden == "/" && normalized == "/"
})
}
pub fn container_run_argv(spec: &ContainerRunSpec) -> Vec<String> {
let mut v = vec![
spec.engine.to_string(),
"run".to_string(),
"-d".to_string(),
"--rm".to_string(),
"--name".to_string(),
spec.name.to_string(),
"--cap-drop".to_string(),
"ALL".to_string(),
"--security-opt".to_string(),
"no-new-privileges".to_string(),
"--pids-limit".to_string(),
"512".to_string(),
"--memory".to_string(),
"2g".to_string(),
];
if !spec.network {
v.push("--network".to_string());
v.push("none".to_string());
}
v.push("-v".to_string());
v.push(format!("{0}:{0}", spec.workdir));
for m in spec.mounts.iter().filter(|m| mount_allowed(m)) {
v.push("-v".to_string());
v.push(format!("{m}:{m}"));
}
v.push("-w".to_string());
v.push(spec.workdir.to_string());
v.push(spec.image.to_string());
v.push("sleep".to_string());
v.push("infinity".to_string());
v
}
pub fn container_exec_argv(
engine: &str,
name: &str,
workdir: &str,
shell: &str,
flag: &str,
command: &str,
) -> Vec<String> {
vec![
engine.to_string(),
"exec".to_string(),
"-w".to_string(),
workdir.to_string(),
name.to_string(),
shell.to_string(),
flag.to_string(),
command.to_string(),
]
}
pub fn container_rm_argv(engine: &str, name: &str) -> Vec<String> {
vec![
engine.to_string(),
"rm".to_string(),
"-f".to_string(),
name.to_string(),
]
}
pub fn namespace_argv(shell: &str, flag: &str, command: &str, network: bool) -> Vec<String> {
let mut v = vec![
"unshare".to_string(),
"--user".to_string(),
"--map-root-user".to_string(),
"--mount".to_string(),
"--pid".to_string(),
"--fork".to_string(),
"--mount-proc".to_string(),
];
if !network {
v.push("--net".to_string());
}
v.push(shell.to_string());
v.push(flag.to_string());
v.push(command.to_string());
v
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detect_prefers_docker_then_podman_then_none() {
assert_eq!(
detect_container_engine_with(&|b| ["docker", "podman"].contains(&b)).as_deref(),
Some("docker")
);
assert_eq!(
detect_container_engine_with(&|b| b == "podman").as_deref(),
Some("podman")
);
assert_eq!(detect_container_engine_with(&|_| false), None);
}
#[test]
fn real_detection_and_path_probe_do_not_panic() {
let _ = detect_container_engine();
assert!(!binary_on_path("definitely-not-a-real-binary-xyz"));
}
#[test]
fn binary_on_path_in_handles_present_and_absent_path() {
assert!(!binary_on_path_in(None, "sh"));
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("marker"), "x").unwrap();
let path = std::env::join_paths([dir.path()]).unwrap();
assert!(binary_on_path_in(Some(path.clone()), "marker"));
assert!(!binary_on_path_in(Some(path), "not-there"));
}
#[test]
fn namespace_supported_matches_target() {
assert_eq!(namespace_supported(), cfg!(target_os = "linux"));
}
#[test]
fn forbidden_mounts_are_refused() {
for path in [
"/",
"/proc",
"/sys",
"/dev",
"/etc",
"/etc/shadow",
"/var/run",
"/var/run/docker.sock",
"/run/docker.sock",
"/var/run/podman/podman.sock",
] {
assert!(!mount_allowed(path), "{path} must be refused");
}
}
#[test]
fn relative_and_traversing_mounts_are_refused() {
for path in ["data", "./data", "/work/../etc", "../etc"] {
assert!(!mount_allowed(path), "{path} must be refused");
}
}
#[test]
fn mount_rules_do_not_depend_on_the_host_platform() {
assert!(mount_allowed("/data"));
assert!(mount_allowed("/home/u/project"));
assert!(!mount_allowed(r"C:\data"));
assert!(!mount_allowed(r"\\server\share"));
assert!(!mount_allowed("/data/../etc"));
assert!(!mount_allowed("/.."));
assert!(!mount_allowed(".."));
}
#[test]
fn ordinary_mounts_are_allowed() {
for path in [
"/data",
"/home/user/project",
"/Users/me/code",
"/opt/cache",
"/etcetera",
] {
assert!(mount_allowed(path), "{path} should be allowed");
}
}
#[test]
fn container_run_argv_drops_forbidden_mounts() {
let spec = ContainerRunSpec {
engine: "docker",
image: "ubuntu:24.04",
workdir: "/work",
network: true,
mounts: &["/var/run/docker.sock".to_string(), "/data".to_string()],
name: "lev-abc",
};
let argv = container_run_argv(&spec);
assert!(
!argv.iter().any(|a| a.contains("docker.sock")),
"the engine socket must never reach the argv: {argv:?}"
);
assert!(argv.iter().any(|a| a == "/data:/data"));
}
#[test]
fn container_run_argv_with_network() {
let spec = ContainerRunSpec {
engine: "docker",
image: "ubuntu:24.04",
workdir: "/work",
network: true,
mounts: &["/data".to_string()],
name: "lev-abc",
};
assert_eq!(
container_run_argv(&spec),
vec![
"docker",
"run",
"-d",
"--rm",
"--name",
"lev-abc",
"--cap-drop",
"ALL",
"--security-opt",
"no-new-privileges",
"--pids-limit",
"512",
"--memory",
"2g",
"-v",
"/work:/work",
"-v",
"/data:/data",
"-w",
"/work",
"ubuntu:24.04",
"sleep",
"infinity"
]
);
}
#[test]
fn container_run_argv_no_network_uses_none() {
let spec = ContainerRunSpec {
engine: "podman",
image: "node:22-slim",
workdir: "/w",
network: false,
mounts: &[],
name: "lev-x",
};
let argv = container_run_argv(&spec);
assert_eq!(argv[0], "podman");
assert!(
argv.windows(2)
.any(|w| w == ["--network".to_string(), "none".to_string()])
);
}
#[test]
fn container_run_argv_accepts_arbitrary_engine() {
let spec = ContainerRunSpec {
engine: "nerdctl",
image: "alpine",
workdir: "/w",
network: true,
mounts: &[],
name: "lev-n",
};
assert_eq!(container_run_argv(&spec)[0], "nerdctl");
}
#[test]
fn container_exec_argv_shape() {
assert_eq!(
container_exec_argv("docker", "lev-abc", "/work", "sh", "-c", "ls -la"),
vec![
"docker", "exec", "-w", "/work", "lev-abc", "sh", "-c", "ls -la"
]
);
}
#[test]
fn container_rm_argv_shape() {
assert_eq!(
container_rm_argv("podman", "lev-abc"),
vec!["podman", "rm", "-f", "lev-abc"]
);
}
#[test]
fn namespace_argv_isolated_network() {
let argv = namespace_argv("sh", "-c", "whoami", false);
assert_eq!(argv[0], "unshare");
assert!(argv.contains(&"--net".to_string()));
assert_eq!(&argv[argv.len() - 3..], &["sh", "-c", "whoami"]);
}
#[test]
fn namespace_argv_shared_network_omits_net() {
let argv = namespace_argv("bash", "-c", "echo hi", true);
assert!(!argv.contains(&"--net".to_string()));
assert!(argv.contains(&"--user".to_string()));
}
}