cellos-supervisor 0.5.1

CellOS execution-cell runner — boots cells in Firecracker microVMs or gVisor, enforces narrow typed authority, emits signed CloudEvents.
Documentation
//! L2-03 — private tmpfs workspace isolation.
//!
//! When `CELLOS_SUBPROCESS_UNSHARE=mnt` (or the default that includes `CLONE_NEWNS`) and the
//! spec sets `run.workingDirectory`, the supervisor mounts a fresh tmpfs over that path inside
//! the child's private mount namespace. Two sequential runs see a clean, empty workspace each
//! time; writes from run A are not visible to run B and do not appear on the host after exit.
//!
//! Requires Linux and root or a user namespace with `CAP_SYS_ADMIN` (or `newuidmap`/`newgidmap`).
//! On CI (ubuntu-latest) the runner has sufficient privileges.

#![cfg(target_os = "linux")]

mod linux {
    use std::fs::File;
    use std::io::Write;
    use std::path::{Path, PathBuf};
    use std::process::Command;

    fn supervisor_exe() -> PathBuf {
        if let Some(p) = std::env::var_os("CARGO_BIN_EXE_cellos_supervisor") {
            return PathBuf::from(p);
        }
        let root = Path::new(env!("CARGO_MANIFEST_DIR"))
            .parent()
            .and_then(|p| p.parent())
            .expect("workspace root");
        root.join("target/debug/cellos-supervisor")
    }

    fn write_spec(dir: &Path, spec_id: &str, argv: &[&str], working_dir: &str) -> PathBuf {
        let argv_json: Vec<String> = argv.iter().map(|s| format!("\"{s}\"")).collect();
        let json = format!(
            r#"{{"apiVersion":"cellos.io/v1","kind":"ExecutionCell","spec":{{"id":"{spec_id}","authority":{{}},"lifetime":{{"ttlSeconds":60}},"run":{{"argv":[{argv}],"workingDirectory":"{wd}"}}}}}}"#,
            spec_id = spec_id,
            argv = argv_json.join(","),
            wd = working_dir,
        );
        let path = dir.join(format!("{spec_id}.json"));
        File::create(&path)
            .and_then(|mut f| f.write_all(json.as_bytes()))
            .expect("write spec");
        path
    }

    /// Returns true when the running process can create a new mount namespace.
    ///
    /// `CELLOS_SUBPROCESS_UNSHARE=mnt` calls `unshare(CLONE_NEWNS)` which requires
    /// `CAP_SYS_ADMIN`. Guard each test with this probe so they skip on
    /// github-hosted runners that lack the capability.
    fn unshare_mnt_available() -> bool {
        Command::new("unshare")
            .args(["-m", "/bin/true"])
            .status()
            .map(|s| s.success())
            .unwrap_or(false)
    }

    fn run_supervisor(spec: &Path, workspace: &str) -> std::process::ExitStatus {
        Command::new(supervisor_exe())
            .env("CELLOS_DEPLOYMENT_PROFILE", "portable")
            .env("CELL_OS_USE_NOOP_SINK", "1")
            .env("CELLOS_CELL_BACKEND", "stub")
            .env("CELLOS_SUBPROCESS_UNSHARE", "mnt")
            .env(
                "CELLOS_RUN_ARGV0_ALLOW_PREFIXES",
                "/usr/bin,/bin,/usr/local/bin,/bin/sh,/usr/bin/sh",
            )
            .arg(spec)
            .current_dir(workspace)
            .status()
            .expect("spawn supervisor")
    }

    /// Run A writes a sentinel inside the declared workspace; run B asserts it is absent
    /// (fresh tmpfs each time).
    #[test]
    fn each_run_gets_fresh_workspace() {
        if !unshare_mnt_available() {
            return; // CAP_SYS_ADMIN unavailable (e.g. github-hosted runner)
        }
        let dir = tempfile::tempdir().expect("tempdir");
        let ws = dir.path().to_str().expect("UTF-8 path").to_string();
        let host_sentinel = dir.path().join("sentinel.txt");

        // Run A: write a sentinel into the workspace mount.
        let spec_a = write_spec(
            dir.path(),
            "ws-run-a",
            &[
                "/bin/sh",
                "-c",
                "echo run-a-was-here > sentinel.txt && test -f sentinel.txt",
            ],
            &ws,
        );
        let status_a = run_supervisor(&spec_a, &ws);
        assert!(status_a.success(), "run A should succeed: {status_a:?}");

        // The sentinel must NOT exist on the host after run A exits: the tmpfs mount should
        // disappear with the private mount namespace, leaving the host workspace clean.
        assert!(
            !host_sentinel.exists(),
            "sentinel leaked to host workspace — tmpfs mount did not isolate run A's writes"
        );

        // Run B: assert the sentinel is absent because its workspace tmpfs is fresh.
        let spec_b = write_spec(
            dir.path(),
            "ws-run-b",
            &["/bin/sh", "-c", "! test -f sentinel.txt"],
            &ws,
        );
        let status_b = run_supervisor(&spec_b, &ws);
        assert!(
            status_b.success(),
            "run B should see a clean workspace with no sentinel from run A: {status_b:?}"
        );
    }

    /// A cell's working directory starts empty — no files from the host bleed through.
    #[test]
    fn workspace_is_empty_on_start() {
        if !unshare_mnt_available() {
            return; // CAP_SYS_ADMIN unavailable (e.g. github-hosted runner)
        }
        let dir = tempfile::tempdir().expect("tempdir");
        // Create a file in the tempdir on the host so it would be visible without isolation.
        std::fs::write(dir.path().join("host-file.txt"), b"host content").expect("write");
        let ws = dir.path().to_str().expect("UTF-8 path").to_string();

        // The cell's tmpfs overlays the working directory, so host-file.txt is hidden.
        let spec = write_spec(
            dir.path(),
            "ws-empty",
            &["/bin/sh", "-c", "! test -f host-file.txt"],
            &ws,
        );
        let status = run_supervisor(&spec, &ws);
        assert!(
            status.success(),
            "workspace should be empty (tmpfs hides host files): {status:?}"
        );

        // Verify the host file is still there after the cell exits.
        assert!(
            dir.path().join("host-file.txt").exists(),
            "host file should be unchanged after cell exits"
        );
    }
}