landstrip 0.15.11

Sandbox for coding agents with parametrized state
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2026 Jarkko Sakkinen

//! Linux sandbox platform using Landlock and seccomp.

pub(crate) mod fd;
mod landlock;
mod seccomp;

use crate::policy::AccessPolicy;
use crate::trap::{Result, Trap};
use crate::trap_fd::TrapFd;
use fd::close_inherited_fds;
use landlock::{enforce_access_policy, landlock_features};
use seccomp::NetworkFilter;
use std::ffi::{OsStr, OsString};
use std::os::unix::process::CommandExt;
use std::process::{self, Command};

#[allow(clippy::needless_pass_by_value)]
pub(crate) fn execute(
    policy: &AccessPolicy,
    tool: &OsStr,
    args: &[OsString],
    trap_fd: &TrapFd,
) -> Result<()> {
    let network = &policy.network_access;
    let unrestricted_network = network.is_unrestricted();
    let landlock_features = landlock_features()?;
    if seccomp::needs_unix_socket_broker(&network.unix_socket_access) {
        let engine = if landlock_features.resolve_unix {
            "landlock"
        } else {
            "seccomp"
        };
        log::debug!("{engine}: Unix socket policy enabled");
    }

    let needs_fs_broker = seccomp::needs_filesystem_broker(policy) || trap_fd.is_enabled();
    let needs_network_broker = !unrestricted_network
        && (network.local_tcp_bind
            || !network.connect_tcp_ports.is_empty()
            || seccomp::needs_unix_socket_broker(&network.unix_socket_access));

    if needs_network_broker || needs_fs_broker {
        let status = seccomp::run_broker(
            policy,
            landlock_features,
            tool,
            args,
            needs_network_broker,
            needs_fs_broker,
            trap_fd,
        )?;
        trap_fd.close();
        process::exit(status);
    }

    enforce_access_policy(policy, landlock_features)?;

    if !unrestricted_network {
        let filter = seccomp::network_filter(
            NetworkFilter {
                notify_bind: false,
                notify_connect: false,
                notify_filesystem: false,
                unix_sockets: seccomp::unix_socket_filter(&network.unix_socket_access),
            },
            true,
        )?;
        filter.load()?;
    }
    close_inherited_fds();
    let error = Command::new(tool).args(args).exec();
    Err(Trap::tool_exec(Some(tool.to_os_string()), &error))
}