mobius 0.15.9

A small, modular Rust framework for building coding agents
Documentation
use std::ffi::OsStr;
use std::path::Component;
use std::path::Path;
use std::path::PathBuf;

use tokio::process::Command;

use super::super::Invocation;
use super::super::LocalSandbox;
use super::super::NetworkAccess;
use super::super::WorkspaceAccess;
use super::append_invocation;
use crate::Error;
use crate::Result;

pub(crate) fn sandboxed_command(
    sandbox: &LocalSandbox,
    invocation: &Invocation<'_>,
    network_access: NetworkAccess,
    workspace_access: WorkspaceAccess,
) -> Result<Command> {
    let bwrap = sandbox
        .find_executable("bwrap")
        .map_err(|_| Error::Sandbox("bubblewrap (`bwrap`) is required on Linux".into()))?;
    let mut command = Command::new(bwrap);
    command.args([
        "--new-session",
        "--die-with-parent",
        "--ro-bind",
        "/",
        "/",
        "--dev",
        "/dev",
    ]);
    command.args(["--tmpfs", "/tmp"]);
    if let Some(socket) = ssh_agent_socket(sandbox)
        && let Ok(relative) = socket.strip_prefix("/tmp")
    {
        let mut directory = PathBuf::from("/tmp");
        if let Some(parent) = relative.parent() {
            for component in parent.components() {
                let Component::Normal(component) = component else {
                    continue;
                };
                directory.push(component);
                command.arg("--dir").arg(&directory);
            }
        }
        command.arg("--ro-bind").arg(&socket).arg(&socket);
        command.env("SSH_AUTH_SOCK", socket);
    }
    if sandbox.isolated_home {
        command.args(["--dir", super::super::ISOLATED_HOME]);
    }
    if network_access == NetworkAccess::Denied && Path::new("/run").is_dir() {
        command.args(["--tmpfs", "/run"]);
    }
    command
        .arg(if workspace_access == WorkspaceAccess::ReadOnly {
            "--ro-bind"
        } else {
            "--bind"
        })
        .arg(&sandbox.root)
        .arg(&sandbox.root);
    for root in &sandbox.workspace_roots {
        command
            .arg(if workspace_access == WorkspaceAccess::ReadOnly {
                "--ro-bind"
            } else {
                "--bind"
            })
            .arg(&root.path)
            .arg(&root.path);
    }
    for denied in &sandbox.denied_reads {
        if denied.directory {
            command.arg("--tmpfs").arg(&denied.path);
        } else {
            command.arg("--ro-bind").arg("/dev/null").arg(&denied.path);
        }
    }
    command.args(["--unshare-user", "--unshare-pid"]);
    if network_access == NetworkAccess::Denied {
        command.arg("--unshare-net");
    }
    command
        .arg(if sandbox.empty_proc {
            "--tmpfs"
        } else {
            "--proc"
        })
        .args(["/proc", "--chdir"]);
    command.arg(&sandbox.root);
    command.arg("--");
    append_invocation(&mut command, invocation, sandbox.isolated_home);
    Ok(command)
}

pub(crate) fn protected_full_access_command(
    sandbox: &LocalSandbox,
    invocation: &Invocation<'_>,
) -> Result<Command> {
    let bwrap = sandbox
        .find_executable("bwrap")
        .map_err(|_| Error::Sandbox("bubblewrap (`bwrap`) is required on Linux".into()))?;
    let mut command = Command::new(bwrap);
    command.args([
        "--new-session",
        "--die-with-parent",
        "--bind",
        "/",
        "/",
        "--dev-bind",
        "/dev",
        "/dev",
    ]);
    for denied in &sandbox.denied_reads {
        if denied.directory {
            command.arg("--tmpfs").arg(&denied.path);
        } else {
            command.arg("--ro-bind").arg("/dev/null").arg(&denied.path);
        }
    }
    command.args(["--unshare-user", "--unshare-pid"]);
    command
        .arg(if sandbox.empty_proc {
            "--tmpfs"
        } else {
            "--proc"
        })
        .args(["/proc", "--chdir"]);
    command.arg(&sandbox.root).arg("--");
    append_invocation(&mut command, invocation, sandbox.isolated_home);
    Ok(command)
}

fn ssh_agent_socket(sandbox: &LocalSandbox) -> Option<PathBuf> {
    if sandbox.isolated_home || sandbox.denied_environment.contains("SSH_AUTH_SOCK") {
        return None;
    }
    let value = std::env::var_os("SSH_AUTH_SOCK");
    validated_ssh_agent_socket(value.as_deref(), &sandbox.denied_reads)
}

pub(crate) fn validated_ssh_agent_socket(
    value: Option<&OsStr>,
    denied_reads: &[super::super::DeniedRead],
) -> Option<PathBuf> {
    use std::os::unix::fs::FileTypeExt as _;

    let path = std::fs::canonicalize(Path::new(value?)).ok()?;
    let metadata = std::fs::metadata(&path).ok()?;
    (path.is_absolute()
        && metadata.file_type().is_socket()
        && denied_reads
            .iter()
            .all(|denied| !path.starts_with(&denied.path)))
    .then_some(path)
}