harn-cli 0.10.121

CLI for the Harn programming language — run, test, REPL, format, and lint
use super::*;

#[cfg(target_os = "macos")]
struct CurrentDirGuard(std::path::PathBuf);

#[cfg(target_os = "macos")]
impl Drop for CurrentDirGuard {
    fn drop(&mut self) {
        std::env::set_current_dir(&self.0).expect("restore current dir");
    }
}

fn unset_ambient_egress_policy() -> [crate::env_guard::ScopedEnvVar; 5] {
    [
        crate::env_guard::ScopedEnvVar::unset(harn_vm::egress::HARN_EGRESS_ALLOW_ENV),
        crate::env_guard::ScopedEnvVar::unset(harn_vm::egress::HARN_EGRESS_DENY_ENV),
        crate::env_guard::ScopedEnvVar::unset(harn_vm::egress::HARN_EGRESS_DEFAULT_ENV),
        crate::env_guard::ScopedEnvVar::unset(harn_vm::egress::HARN_EGRESS_BLOCK_PRIVATE_ENV),
        crate::env_guard::ScopedEnvVar::unset(harn_vm::egress::HARN_EGRESS_ALLOW_LOOPBACK_ENV),
    ]
}

#[tokio::test]
async fn execute_run_can_install_an_egress_policy_without_network_authority() {
    let _state_guard = crate::tests::common::harn_state_lock::lock_harn_state_async().await;
    let _egress_environment = unset_ambient_egress_policy();
    harn_vm::reset_thread_local_state();
    let temp = tempfile::TempDir::new().expect("temp dir");
    let script = temp.path().join("main.harn");
    std::fs::write(
        &script,
        r#"
fn main(harness: Harness) {
  harness.net.egress_policy({default: "deny", allow: ["api.example.com"]})
  harness.stdio.println("policy-installed")
}
"#,
    )
    .expect("write script");

    let outcome = execute_run(
        &script.to_string_lossy(),
        false,
        HashSet::new(),
        Vec::new(),
        Vec::new(),
        CliLlmMockMode::Off,
        None,
        RunProfileOptions::default(),
    )
    .await;

    assert_eq!(outcome.exit_code, 0, "stderr:\n{}", outcome.stderr);
    assert_eq!(outcome.stdout, "policy-installed\n");
    harn_vm::reset_thread_local_state();
}

#[tokio::test]
async fn execute_run_can_call_a_matching_http_mock_without_network_authority() {
    let _state_guard = crate::tests::common::harn_state_lock::lock_harn_state_async().await;
    let _egress_environment = unset_ambient_egress_policy();
    harn_vm::reset_thread_local_state();
    let temp = tempfile::TempDir::new().expect("temp dir");
    let script = temp.path().join("main.harn");
    std::fs::write(
        &script,
        r#"
fn main(harness: Harness) {
  const url = "https://api.example.com/value"
  harness.testing.http_mock("GET", url, {status: 200, body: "fixture", headers: {}})
  const response = harness.net.get(url)
  harness.stdio.println(response.body)
}
"#,
    )
    .expect("write script");

    let outcome = execute_run(
        &script.to_string_lossy(),
        false,
        HashSet::new(),
        Vec::new(),
        Vec::new(),
        CliLlmMockMode::Off,
        None,
        RunProfileOptions::default(),
    )
    .await;

    assert_eq!(outcome.exit_code, 0, "stderr:\n{}", outcome.stderr);
    assert_eq!(outcome.stdout, "fixture\n");
    harn_vm::reset_thread_local_state();
}

#[cfg(target_os = "macos")]
#[tokio::test]
async fn canonical_run_allows_tcp_loopback_but_denies_remote_egress() {
    let _cwd_lock = crate::tests::common::cwd_lock::lock_cwd_async().await;
    let _state_guard = crate::tests::common::harn_state_lock::lock_harn_state_async().await;
    let _egress_environment = unset_ambient_egress_policy();
    harn_vm::reset_thread_local_state();
    let temp = tempfile::TempDir::new().expect("temp dir");
    let home = std::path::PathBuf::from(std::env::var_os("HOME").expect("HOME must be set"));
    let hostile_cwd = tempfile::Builder::new()
        .prefix("harn-hostile-cwd-")
        .tempdir_in(home)
        .expect("hostile cwd outside the user-temp sandbox preset");
    let outside_file = hostile_cwd.path().join("denied.txt");
    std::fs::write(&outside_file, "outside").expect("write outside fixture");
    let script = temp.path().join("main.harn");
    let python = r"import os
import socket
print('CWD=' + os.getcwd())
try:
    open(__import__('sys').argv[1], 'rb').read()
except PermissionError:
    print('FILESYSTEM_DENIED')
else:
    raise SystemExit('outside filesystem unexpectedly allowed')
for family, address in ((socket.AF_INET, ('127.0.0.1', 0)), (socket.AF_INET6, ('::1', 0))):
    server = socket.socket(family, socket.SOCK_STREAM)
    server.bind(address)
    server.listen(1)
    client = socket.socket(family, socket.SOCK_STREAM)
    client.connect(server.getsockname())
    accepted, _ = server.accept()
    client.sendall(b'ok')
    assert accepted.recv(2) == b'ok'
    accepted.close()
    client.close()
    server.close()
print('LOOPBACK_OK')
remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote.settimeout(1)
try:
    remote.connect(('192.0.2.1', 9))
except PermissionError:
    print('REMOTE_DENIED')
else:
    raise SystemExit('remote network unexpectedly allowed')";
    let source = format!(
        r#"
fn main(harness: Harness) -> int {{
  const child = harness.process.run({{
    program: "/usr/bin/python3",
    args: ["-c", {python}, {outside_file}],
  }})
  harness.stdio.print(child.stdout)
  harness.stdio.eprint(child.stderr)
  return child.exit_code
}}
"#,
        python = serde_json::to_string(python).expect("encode Python fixture"),
        outside_file =
            serde_json::to_string(&outside_file.to_string_lossy()).expect("encode outside path"),
    );
    std::fs::write(&script, source).expect("write script");

    let original_cwd = std::env::current_dir().expect("current dir");
    std::env::set_current_dir(hostile_cwd.path()).expect("enter hostile ambient cwd");
    let cwd_guard = CurrentDirGuard(original_cwd);

    let outcome = execute_run_with_sandbox_options(
        &script.to_string_lossy(),
        false,
        HashSet::new(),
        Vec::new(),
        Vec::new(),
        CliLlmMockMode::Off,
        None,
        RunProfileOptions::default(),
        RunSandboxOptions::default()
            .with_workspace_root(temp.path())
            .with_process_loopback(true),
    )
    .await;
    drop(cwd_guard);

    assert_eq!(
        outcome.exit_code, 0,
        "stderr:\n{}\nstdout:\n{}",
        outcome.stderr, outcome.stdout
    );
    let workspace = std::fs::canonicalize(temp.path()).expect("canonical workspace");
    assert_eq!(
        outcome.stdout,
        format!(
            "CWD={}\nFILESYSTEM_DENIED\nLOOPBACK_OK\nREMOTE_DENIED\n",
            workspace.display()
        )
    );
    harn_vm::reset_thread_local_state();
}