harn-vm 0.10.177

Async bytecode virtual machine for the Harn programming language
use super::*;
use crate::stdlib::sandbox::{ProcessSandboxDenialReporting, ProcessSandboxNetwork};

#[test]
fn externally_confined_child_preserves_network_uncertainty_and_denial_coverage() {
    use crate::process_sandbox::current_process_confinement;
    use crate::stdlib::sandbox::ProcessSandboxReportingContext;

    const CHILD_WORKSPACE: &str = "NETWORK_RECEIPT_EXTERNAL_CHILD_WORKSPACE";
    const RESULT_PREFIX: &str = "EXTERNAL_NETWORK_RECEIPT ";
    if let Some(workspace) = std::env::var_os(CHILD_WORKSPACE) {
        assert!(current_process_confinement().is_none());
        assert!(Backend::inherits_network_grants());
        assert!(super::super::nested::outer_denies("network-outbound", None));
        let no_policy = ProcessSandboxReportingContext::current();
        assert!(no_policy.policy.is_none());
        assert_eq!(
            no_policy.reporting,
            ProcessSandboxDenialReporting::NotEnforced
        );
        assert_eq!(no_policy.network, ProcessSandboxNetwork::Unknown);
        let mut requested = macos_policy_with_workspace_ops(&[]);
        requested.workspace_roots = vec![std::path::PathBuf::from(workspace).display().to_string()];
        requested.side_effect_level = Some("network".into());
        crate::orchestration::push_execution_policy(requested.clone());
        let context = ProcessSandboxReportingContext::current();
        assert!(context.policy.is_some());
        assert_eq!(
            context.reporting,
            ProcessSandboxDenialReporting::InferredOnly
        );
        assert_eq!(context.network, ProcessSandboxNetwork::Inherited);
        let output = Backend::run_to_output(
            "/bin/echo",
            &strings(["external-inherited-command-reached"]),
            &ProcessCommandConfig::default(),
            &requested,
            SandboxProfile::Worktree,
        )
        .expect("spawn under the external profile");
        crate::orchestration::pop_execution_policy();
        assert!(output.status.success());
        assert_eq!(output.stdout, b"external-inherited-command-reached\n");
        println!(
            "{RESULT_PREFIX}{}",
            serde_json::to_value(context.report()).unwrap()
        );
        return;
    }

    let workspace = tempfile::tempdir().expect("workspace");
    let exe = std::env::current_exe().expect("test binary");
    let mut outer = macos_policy_with_workspace_ops(&[]);
    outer.workspace_roots = vec![workspace.path().display().to_string()];
    outer.read_only_roots = vec![exe
        .ancestors()
        .nth(3)
        .expect("build root")
        .display()
        .to_string()];
    outer.side_effect_level = Some("process_exec".into());
    let availability = Backend::run_to_output(
        "/usr/bin/true",
        &[],
        &ProcessCommandConfig::default(),
        &outer,
        SandboxProfile::Worktree,
    );
    if crate::sandbox_host::assert_unavailable_result(&availability) {
        eprintln!("external network receipt not_measured: host process confinement unavailable");
        return;
    }
    assert!(availability
        .expect("sandboxed host control")
        .status
        .success());
    let output = Command::new(SANDBOX_EXEC_PATH)
        .arg("-p")
        .arg(render_profile_for_program(&outer, &exe.display().to_string()))
        .arg(&exe)
        .args([
            "stdlib::sandbox::macos::tests::network_receipt::externally_confined_child_preserves_network_uncertainty_and_denial_coverage",
            "--exact", "--nocapture", "--test-threads=1",
        ])
        .env(CHILD_WORKSPACE, workspace.path())
        .current_dir(workspace.path())
        .output().expect("run the externally confined case");
    assert!(output.status.success(), "{output:?}");
    let stdout = String::from_utf8(output.stdout).expect("child stdout");
    let report: serde_json::Value = serde_json::from_str(
        stdout
            .lines()
            .find_map(|line| line.split_once(RESULT_PREFIX).map(|(_, json)| json))
            .unwrap_or_else(|| panic!("actual child receipt missing from stdout: {stdout}")),
    )
    .expect("child receipt JSON");
    assert_eq!(
        report["network"],
        serde_json::json!({"status": "inherited"})
    );
    assert_eq!(report["enforced"], true);
}

#[test]
fn self_confined_child_reports_inherited_network_with_an_active_policy() {
    use crate::process_sandbox::confine_current_process;
    use crate::stdlib::sandbox::ProcessSandboxReportingContext;

    const CHILD_WORKSPACE: &str = "NETWORK_RECEIPT_CONFINED_CHILD_WORKSPACE";
    const RESULT_PREFIX: &str = "INHERITED_NETWORK_RECEIPT ";
    if let Some(workspace) = std::env::var_os(CHILD_WORKSPACE) {
        let mut outer = macos_policy_with_workspace_ops(&[]);
        outer.workspace_roots = vec![std::path::PathBuf::from(workspace).display().to_string()];
        outer.side_effect_level = Some("process_exec".into());
        confine_current_process(&outer).expect("confine the test child");
        assert!(super::super::nested::outer_denies("network-outbound", None));
        let mut requested = outer;
        requested.side_effect_level = Some("network".into());
        crate::orchestration::push_execution_policy(requested.clone());
        let context = ProcessSandboxReportingContext::current();
        assert!(context.policy.is_some());
        assert_eq!(context.network, ProcessSandboxNetwork::Inherited);
        let output = Backend::run_to_output(
            "/bin/echo",
            &strings(["inherited-command-reached"]),
            &ProcessCommandConfig::default(),
            &requested,
            SandboxProfile::Worktree,
        )
        .expect("spawn under the inherited profile");
        crate::orchestration::pop_execution_policy();
        assert!(output.status.success());
        assert_eq!(output.stdout, b"inherited-command-reached\n");
        println!(
            "{RESULT_PREFIX}{}",
            serde_json::to_value(context.report()).unwrap()
        );
        return;
    }

    let workspace = tempfile::tempdir().expect("workspace");
    let mut policy = macos_policy_with_workspace_ops(&[]);
    policy.workspace_roots = vec![workspace.path().display().to_string()];
    let availability = Backend::run_to_output(
        "/usr/bin/true",
        &[],
        &ProcessCommandConfig::default(),
        &policy,
        SandboxProfile::Worktree,
    );
    if crate::sandbox_host::assert_unavailable_result(&availability) {
        eprintln!("inherited network receipt not_measured: host process confinement unavailable");
        return;
    }
    assert!(availability
        .expect("sandboxed host control")
        .status
        .success());
    let output = Command::new(std::env::current_exe().expect("test binary"))
        .args([
            "stdlib::sandbox::macos::tests::network_receipt::self_confined_child_reports_inherited_network_with_an_active_policy",
            "--exact",
            "--nocapture",
            "--test-threads=1",
        ])
        .env(CHILD_WORKSPACE, workspace.path())
        .current_dir(workspace.path())
        .output()
        .expect("run the isolated confinement case");
    assert!(output.status.success(), "{output:?}");
    let stdout = String::from_utf8(output.stdout).expect("child stdout");
    let report: serde_json::Value = serde_json::from_str(
        stdout
            .lines()
            .find_map(|line| line.split_once(RESULT_PREFIX).map(|(_, json)| json))
            .unwrap_or_else(|| panic!("actual child receipt missing from stdout: {stdout}")),
    )
    .expect("child receipt JSON");
    assert_eq!(
        report["network"],
        serde_json::json!({"status": "inherited"})
    );
    assert_eq!(report["enforced"], true);
}

#[test]
fn network_receipt_matches_the_rendered_process_grant() {
    let proxy = crate::orchestration::ProcessNetworkProxy {
        http_port: 3128,
        socks_port: 1080,
    };
    for (ceiling, loopback, proxy, [direct_network, tcp_loopback, managed_proxy], proxy_port) in [
        ("process_exec", false, None, [false, false, false], false),
        ("network", false, None, [true, true, false], false),
        ("network", true, None, [false, true, false], false),
        ("network", false, Some(proxy), [false, false, true], true),
        (
            "process_exec",
            false,
            Some(proxy),
            [false, false, false],
            false,
        ),
        ("network", true, Some(proxy), [false, true, true], false),
        (
            "process_exec",
            true,
            Some(proxy),
            [false, true, true],
            false,
        ),
    ] {
        let mut policy = macos_policy_with_workspace_ops(&["read_text"]);
        policy.side_effect_level = Some(ceiling.into());
        policy.process_sandbox.allow_tcp_loopback = loopback;
        policy.process_network_proxy = proxy;
        let report = ProcessSandboxNetwork::capture(
            ProcessSandboxDenialReporting::InferredOnly,
            Some(&policy),
            false,
        );
        assert_eq!(
            report,
            ProcessSandboxNetwork::Policy {
                direct_network,
                tcp_loopback,
                managed_proxy
            }
        );
        let profile = render_profile(&policy);
        assert_eq!(
            profile.contains("(allow network*)"),
            direct_network,
            "{profile}"
        );
        assert_eq!(
            profile.contains("(remote ip \"localhost:*\")"),
            loopback,
            "{profile}"
        );
        assert_eq!(
            profile.contains("(remote ip \"localhost:3128\")"),
            proxy_port,
            "{profile}"
        );
    }
}

#[test]
fn loopback_grant_keeps_managed_proxy_routes_at_both_host_ceilings() {
    use std::io::{BufRead, BufReader, Write};
    use std::net::TcpListener;

    let mut probe_policy = macos_policy_with_workspace_ops(&[]);
    probe_policy.workspace_roots = vec![std::env::current_dir().unwrap().display().to_string()];
    let availability = Backend::run_to_output(
        "/usr/bin/true",
        &[],
        &ProcessCommandConfig::default(),
        &probe_policy,
        SandboxProfile::Worktree,
    );
    if crate::sandbox_host::assert_unavailable_result(&availability) {
        eprintln!("network proxy routes not_measured: host process confinement unavailable");
        return;
    }
    assert!(availability
        .expect("sandboxed host control")
        .status
        .success());

    for ceiling in ["network", "process_exec"] {
        let allowed = TcpListener::bind(("127.0.0.1", 0)).unwrap();
        let allowed_port = allowed.local_addr().unwrap().port();
        let denied = TcpListener::bind(("127.0.0.1", 0)).unwrap();
        let denied_port = denied.local_addr().unwrap().port();
        denied.set_nonblocking(true).unwrap();
        let server = crate::runtime_stack::spawn(move || {
            let (mut stream, _) = allowed.accept().unwrap();
            let mut reader = BufReader::new(&mut stream);
            let mut line = String::new();
            assert!(reader.read_line(&mut line).unwrap() > 0);
            assert_eq!(line, "GET /allowed HTTP/1.1\r\n");
            loop {
                line.clear();
                assert!(reader.read_line(&mut line).unwrap() > 0);
                if line == "\r\n" {
                    break;
                }
            }
            stream.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 21\r\nConnection: close\r\n\r\nnetwork-route-control").unwrap();
        });
        let proxy = crate::egress::ProcessEgressProxy::start_allowlist(&[format!(
            "127.0.0.1:{allowed_port}"
        )])
        .unwrap();
        let mut policy = macos_policy_with_workspace_ops(&["read_text"]);
        policy.workspace_roots = vec![std::env::current_dir().unwrap().display().to_string()];
        policy.side_effect_level = Some(ceiling.into());
        policy.process_sandbox.allow_tcp_loopback = true;
        policy.process_network_proxy = Some(proxy.endpoints());
        let report = ProcessSandboxNetwork::capture(
            ProcessSandboxDenialReporting::InferredOnly,
            Some(&policy),
            false,
        );
        assert_eq!(
            report,
            ProcessSandboxNetwork::Policy {
                direct_network: false,
                tcp_loopback: true,
                managed_proxy: true
            }
        );
        let mut environment = Command::new("/usr/bin/curl");
        super::super::super::process_output::apply_process_config(
            &mut environment,
            &ProcessCommandConfig::default(),
            Some(&policy),
        );
        assert!(environment
            .get_envs()
            .any(|(key, value)| key == "http_proxy"
                && value
                    == Some(std::ffi::OsStr::new(&format!(
                        "http://127.0.0.1:{}",
                        proxy.endpoints().http_port
                    )))));

        let run = |port, path| {
            Backend::run_to_output(
                "/usr/bin/curl",
                &strings([
                    "--silent",
                    "--show-error",
                    "--fail",
                    "--max-time",
                    "10",
                    &format!("http://127.0.0.1:{port}/{path}"),
                ]),
                &ProcessCommandConfig::default(),
                &policy,
                SandboxProfile::OsHardened,
            )
            .expect("sandboxed curl")
        };
        let output = run(allowed_port, "allowed");
        assert!(
            output.status.success(),
            "{}",
            String::from_utf8_lossy(&output.stderr)
        );
        assert_eq!(output.stdout, b"network-route-control");
        server.join().unwrap();
        let output = run(denied_port, "denied");
        assert_eq!(output.status.code(), Some(22));
        assert!(
            matches!(denied.accept(), Err(error) if error.kind() == std::io::ErrorKind::WouldBlock)
        );
        let audit = proxy.audit_snapshot();
        assert_eq!(audit.len(), 2);
        assert!(audit[0].allowed);
        assert!(!audit[1].allowed);
    }
}