mobius 0.15.10

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

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;
use crate::backend::sandbox::MACOS_COMMAND_WRAPPER;
use crate::backend::sandbox::MACOS_SEATBELT_BASE_POLICY;
use crate::backend::sandbox::MACOS_SEATBELT_NETWORK_POLICY;

const PRIVATE_TEMP_POLICY: &str = r#"
(deny file-read* file-write*
  (require-all
    (subpath (param "TEMP_PARENT"))
    (require-not (subpath (param "TEMP_ROOT")))))
"#;

const SEATBELT_POLICY_SUFFIX: &str = r#"
(allow file-read*)
(allow file-write*
  (subpath (param "TEMP_ROOT"))
  (subpath (param "WRITABLE_ROOT")))
"#;

pub(crate) fn protected_full_access_command(
    sandbox: &LocalSandbox,
    invocation: &Invocation<'_>,
) -> Result<Command> {
    let executable = Path::new("/usr/bin/sandbox-exec");
    if !executable.is_file() {
        return Err(Error::Sandbox(
            "/usr/bin/sandbox-exec is unavailable".into(),
        ));
    }
    let mut policy = String::from(
        "(version 1)\n(allow default)\n\
         (deny signal (require-not (target same-sandbox)))\n\
         (deny process-info* (require-not (target same-sandbox)))\n\
         (deny mach-task-name (require-not (target same-sandbox)))\n",
    );
    for (index, denied) in sandbox.denied_reads.iter().enumerate() {
        let parameter = format!("DENIED_READ_{index}");
        policy.push_str(&format!(
            "\n(deny file-read* file-write*\n  (literal (param \"{parameter}\")){}\n)",
            if denied.directory {
                format!("\n  (subpath (param \"{parameter}\"))")
            } else {
                String::new()
            }
        ));
    }
    let mut command = Command::new(executable);
    command.arg("-p").arg(policy);
    for (index, denied) in sandbox.denied_reads.iter().enumerate() {
        let path = denied
            .path
            .to_str()
            .ok_or_else(|| Error::Sandbox("sandbox path is not UTF-8".into()))?;
        command.arg(format!("-DDENIED_READ_{index}={path}"));
    }
    append_isolated_invocation(&mut command, invocation, sandbox.isolated_home);
    Ok(command)
}

pub(crate) fn sandboxed_command(
    sandbox: &LocalSandbox,
    invocation: &Invocation<'_>,
    network_access: NetworkAccess,
    workspace_access: WorkspaceAccess,
) -> Result<Command> {
    let executable = Path::new("/usr/bin/sandbox-exec");
    if !executable.is_file() {
        return Err(Error::Sandbox(
            "/usr/bin/sandbox-exec is unavailable".into(),
        ));
    }
    let mut command = Command::new(executable);
    let mut policy =
        format!("{MACOS_SEATBELT_BASE_POLICY}{SEATBELT_POLICY_SUFFIX}{PRIVATE_TEMP_POLICY}");
    for index in 0..sandbox.workspace_roots.len() {
        let parameter = format!("WORKSPACE_ROOT_{index}");
        policy.push_str(&format!(
            "\n(allow file-write*\n  (literal (param \"{parameter}\"))\n  (subpath (param \"{parameter}\")))"
        ));
    }
    for (index, denied) in sandbox.denied_reads.iter().enumerate() {
        let parameter = format!("DENIED_READ_{index}");
        policy.push_str(&format!(
            "\n(deny file-read*\n  (literal (param \"{parameter}\")){}\n)",
            if denied.directory {
                format!("\n  (subpath (param \"{parameter}\"))")
            } else {
                String::new()
            }
        ));
    }
    if network_access == NetworkAccess::Allowed {
        policy.push_str("\n(allow network-outbound)\n(allow network-inbound)\n");
        policy.push_str(MACOS_SEATBELT_NETWORK_POLICY);
    }
    if workspace_access == WorkspaceAccess::ReadOnly {
        policy.push_str(
            r#"
(deny file-write*
  (literal (param "WRITABLE_ROOT"))
  (subpath (param "WRITABLE_ROOT")))"#,
        );
        for index in 0..sandbox.workspace_roots.len() {
            let parameter = format!("WORKSPACE_ROOT_{index}");
            policy.push_str(&format!(
                "\n(deny file-write*\n  (literal (param \"{parameter}\"))\n  (subpath (param \"{parameter}\")))"
            ));
        }
    }
    command.arg("-p").arg(policy);
    let root = sandbox
        .root
        .to_str()
        .ok_or_else(|| Error::Sandbox("sandbox path is not UTF-8".into()))?;
    command.arg(format!("-DWRITABLE_ROOT={root}"));
    append_temporary_parameters(&mut command, sandbox)?;
    for (index, root) in sandbox.workspace_roots.iter().enumerate() {
        let path = root
            .path
            .to_str()
            .ok_or_else(|| Error::Sandbox("sandbox path is not UTF-8".into()))?;
        command.arg(format!("-DWORKSPACE_ROOT_{index}={path}"));
    }
    for (index, denied) in sandbox.denied_reads.iter().enumerate() {
        let path = denied
            .path
            .to_str()
            .ok_or_else(|| Error::Sandbox("sandbox path is not UTF-8".into()))?;
        command.arg(format!("-DDENIED_READ_{index}={path}"));
    }
    append_isolated_invocation(&mut command, invocation, sandbox.isolated_home);
    Ok(command)
}

fn append_temporary_parameters(command: &mut Command, sandbox: &LocalSandbox) -> Result<()> {
    let temp = std::fs::canonicalize(sandbox.temp.path())?;
    let parent = temp
        .parent()
        .ok_or_else(|| Error::Sandbox("temporary parent unavailable".into()))?;
    for (name, path) in [("TEMP_PARENT", parent), ("TEMP_ROOT", temp.as_path())] {
        let path = path
            .to_str()
            .ok_or_else(|| Error::Sandbox("sandbox path is not UTF-8".into()))?;
        command.arg(format!("-D{name}={path}"));
    }
    Ok(())
}

fn append_isolated_invocation(
    command: &mut Command,
    invocation: &Invocation<'_>,
    isolated_home: bool,
) {
    let piped = matches!(
        invocation,
        Invocation::Argv {
            piped_input: true,
            ..
        }
    );
    command.args([
        "--",
        "/bin/bash",
        "--noprofile",
        "--norc",
        "-c",
        MACOS_COMMAND_WRAPPER,
        "mobius-command",
        if piped { "2" } else { "0" },
        if piped { "/dev/stdin" } else { "/dev/null" },
    ]);
    append_invocation(command, invocation, isolated_home);
}