supercode-harness 0.4.10

The optional native Supercode agent and tool harness
Documentation
//! P5-10 (COMPOSABLE-HARNESS-DESIGN.md §2 module 12 `permissions.sandbox`):
//! REAL, kernel-enforced Landlock fs confinement + coarse network cut-off +
//! `env_policy` for the `bash` tool's subprocess spawn.
//!
//! This box is Linux kernel 6.1 with Landlock ABI v2 available (confirmed
//! live during this build — `landlock_create_ruleset` reports ABI 2), so
//! the enforcement tests below run FOR REAL, unconditionally — no
//! `#[ignore]`/skip. They would only need gating on a kernel that lacks
//! Landlock entirely (<5.13); the "unavailable" fail-closed/escalation
//! branches are covered separately, exactly as honestly, as pure unit
//! tests in `crates/harness/src/sandbox.rs` (`fs_available`/`net_available`
//! are injected PARAMETERS there — see `decide_fs`'s doc comment — because
//! this box genuinely can't simulate a missing kernel primitive it
//! actually has).
//!
//! Live-agent-safety: every command here is a direct [`Tool::execute`]
//! call against a hand-built [`ToolContext`] — no `Agent`, no `Provider`,
//! no model, nothing billed. Every shell command is harmless
//! (`echo`/`cat`/`touch`/`env`) against a throwaway temp directory.

use supercode_harness::tools::{BashTool, NetworkPolicy, SandboxPolicy, Tool, ToolContext};
use supercode_harness::{SandboxEnvPolicy, SandboxEscalation};

fn tmp_dir(label: &str) -> std::path::PathBuf {
    use std::sync::atomic::{AtomicU64, Ordering};
    static N: AtomicU64 = AtomicU64::new(0);
    let d = std::env::temp_dir().join(format!(
        "sc-sandbox-{label}-{}-{}",
        std::process::id(),
        N.fetch_add(1, Ordering::SeqCst)
    ));
    std::fs::create_dir_all(&d).unwrap();
    d
}

/// A directory genuinely OUTSIDE both `cwd` and the system temp root
/// (`std::env::temp_dir()`, e.g. `/tmp`) — `workspace_write`'s grant is
/// "cwd + system temp" (COMPOSABLE-HARNESS-DESIGN.md build brief item 1),
/// so a test proving the KERNEL denies an out-of-workspace write must land
/// somewhere that grant doesn't cover. Uses `$HOME` (falling back to `/root`
/// if unset), matching the build brief's own example ("a HOME dotfile").
fn outside_workspace_and_temp_dir(label: &str) -> std::path::PathBuf {
    use std::sync::atomic::{AtomicU64, Ordering};
    static N: AtomicU64 = AtomicU64::new(0);
    let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
    let d = std::path::PathBuf::from(home).join(format!(
        ".sc-sandbox-outside-{label}-{}-{}",
        std::process::id(),
        N.fetch_add(1, Ordering::SeqCst)
    ));
    std::fs::create_dir_all(&d).expect("HOME must be writable to run this test");
    d
}

fn ctx_for(cwd: &std::path::Path, sandbox: SandboxPolicy) -> ToolContext {
    let mut ctx = ToolContext::new(cwd);
    ctx.sandbox = sandbox;
    ctx
}

async fn run_bash(ctx: &ToolContext, command: &str) -> supercode_harness::Result<String> {
    BashTool::default()
        .execute(serde_json::json!({ "command": command }), ctx)
        .await
}

// ---------------------------------------------------------------------------
// Core deliverable: REAL Landlock enforcement — a genuine kernel EPERM, not
// a path string comparison.
// ---------------------------------------------------------------------------

/// A write INSIDE the workspace under `workspace_write` succeeds, and a
/// write OUTSIDE the workspace (and outside system temp) is refused by the
/// KERNEL — proven by checking the shell's own reported exit code / error
/// text, which only a real `EPERM` from `write()`/`open()` produces (a
/// path-string check would instead have to run entirely inside supercode,
/// before ever spawning `sh` — this lets the kernel itself decide).
#[tokio::test]
async fn real_landlock_denies_write_outside_workspace_under_workspace_write() {
    let cwd = tmp_dir("wsw-inside");
    let outside = outside_workspace_and_temp_dir("wsw-outside");
    let ctx = ctx_for(&cwd, SandboxPolicy::WorkspaceWrite);

    // Inside the workspace: must succeed.
    let inside_target = cwd.join("ok.txt");
    let out = run_bash(&ctx, &format!("echo hello > {}", inside_target.display()))
        .await
        .expect("bash tool call itself must not error");
    assert!(
        out.starts_with("exit code: 0"),
        "write INSIDE the workspace must succeed under workspace_write, got: {out}"
    );
    assert_eq!(
        std::fs::read_to_string(&inside_target).unwrap().trim(),
        "hello",
        "the write must have actually landed on disk"
    );

    // Outside the workspace (a sibling temp dir, not under `cwd`, not the
    // system temp root itself): must be KERNEL-denied.
    let outside_target = outside.join("pwned.txt");
    let out = run_bash(
        &ctx,
        &format!("echo pwned > {} 2>&1", outside_target.display()),
    )
    .await
    .expect("bash tool call itself must not error (the SHELL fails, not the tool call)");
    assert!(
        !out.starts_with("exit code: 0"),
        "write OUTSIDE the workspace must be denied by the kernel, got: {out}"
    );
    let lower = out.to_lowercase();
    assert!(
        lower.contains("permission denied") || lower.contains("operation not permitted"),
        "the denial must be a real kernel EPERM (sh's own error text), got: {out}"
    );
    assert!(
        !outside_target.exists(),
        "the outside file must never have been created"
    );

    let _ = std::fs::remove_dir_all(&cwd);
    let _ = std::fs::remove_dir_all(&outside);
}

/// `read_only` denies ALL writes, even inside the workspace — reads stay
/// unrestricted (this crate never "handles" read access rights at all, see
/// `crate::sandbox::landlock_restrict_self`'s doc comment).
#[tokio::test]
async fn real_landlock_denies_all_writes_under_read_only() {
    let cwd = tmp_dir("ro");
    std::fs::write(cwd.join("preexisting.txt"), "seed").unwrap();
    let ctx = ctx_for(&cwd, SandboxPolicy::ReadOnly);

    let target = cwd.join("should-not-exist.txt");
    let out = run_bash(&ctx, &format!("echo x > {} 2>&1", target.display()))
        .await
        .unwrap();
    assert!(
        !out.starts_with("exit code: 0"),
        "a write under read_only, even inside cwd, must be kernel-denied, got: {out}"
    );
    assert!(!target.exists());

    // Reads must still work.
    let out = run_bash(
        &ctx,
        &format!("cat {}", cwd.join("preexisting.txt").display()),
    )
    .await
    .unwrap();
    assert!(
        out.contains("seed"),
        "reads must stay unrestricted under read_only, got: {out}"
    );

    let _ = std::fs::remove_dir_all(&cwd);
}

/// Confinement targets the CHILD, not supercode: after a confined `bash`
/// call runs, THIS test process (standing in for the `Agent`/supercode
/// process) must still be able to write anywhere it likes — Landlock's
/// `restrict_self()` only ever runs inside the forked child's `pre_exec`
/// closure (`crate::sandbox::apply_linux_confinement`'s doc comment), never
/// in the parent, so it can never leak onto the process running this test.
#[tokio::test]
async fn confinement_never_touches_the_calling_process() {
    let cwd = tmp_dir("child-only-a");
    let outside = outside_workspace_and_temp_dir("child-only-b");
    let ctx = ctx_for(&cwd, SandboxPolicy::WorkspaceWrite);

    // Run a confined call that itself gets denied outside its workspace...
    let denied_target = outside.join("denied.txt");
    let out = run_bash(&ctx, &format!("echo x > {} 2>&1", denied_target.display()))
        .await
        .unwrap();
    assert!(!out.starts_with("exit code: 0"));

    // ...then prove THIS process (never confined) can still write to that
    // exact same "outside" directory the child was just denied access to.
    let proof_path = outside.join("supercode_itself_can_still_write.txt");
    std::fs::write(&proof_path, "supercode session/checkpoint store write").expect(
        "the calling process must be completely unaffected by the child's Landlock restriction",
    );
    assert_eq!(
        std::fs::read_to_string(&proof_path).unwrap(),
        "supercode session/checkpoint store write"
    );

    let _ = std::fs::remove_dir_all(&cwd);
    let _ = std::fs::remove_dir_all(&outside);
}

// ---------------------------------------------------------------------------
// Default-off byte-identity.
// ---------------------------------------------------------------------------

/// `danger_full_access` (the default) applies NO confinement at all — a
/// write anywhere the OS-level user permissions already allow must succeed,
/// exactly like pre-P5-10 behavior.
#[tokio::test]
async fn danger_full_access_applies_no_confinement() {
    let cwd = tmp_dir("dfa-inside");
    let outside = tmp_dir("dfa-outside");
    let ctx = ctx_for(&cwd, SandboxPolicy::DangerFullAccess);

    let outside_target = outside.join("fine.txt");
    let out = run_bash(&ctx, &format!("echo ok > {}", outside_target.display()))
        .await
        .unwrap();
    assert!(
        out.starts_with("exit code: 0"),
        "danger_full_access must not confine anything, got: {out}"
    );
    assert!(outside_target.exists());

    let _ = std::fs::remove_dir_all(&cwd);
    let _ = std::fs::remove_dir_all(&outside);
}

/// An explicit `capabilities.permissions.sandbox.enabled = false` opts a
/// confining tier OUT of OS enforcement entirely (the file-tool
/// `SandboxPolicy` gate is a SEPARATE, always-on layer this doesn't
/// affect — this test only exercises the `bash` subprocess path).
#[tokio::test]
async fn explicit_enabled_false_disables_os_confinement_even_for_a_confining_tier() {
    let cwd = tmp_dir("disabled-inside");
    let outside = tmp_dir("disabled-outside");
    let mut ctx = ctx_for(&cwd, SandboxPolicy::WorkspaceWrite);
    ctx.sandbox_os_enabled = Some(false);

    let outside_target = outside.join("fine.txt");
    let out = run_bash(&ctx, &format!("echo ok > {}", outside_target.display()))
        .await
        .unwrap();
    assert!(
        out.starts_with("exit code: 0"),
        "enabled=false must disable OS confinement even for a confining tier, got: {out}"
    );

    let _ = std::fs::remove_dir_all(&cwd);
    let _ = std::fs::remove_dir_all(&outside);
}

// ---------------------------------------------------------------------------
// env_policy: real subprocess environment sanitization.
// ---------------------------------------------------------------------------

fn shell_env_with_secret() -> std::sync::Arc<std::collections::HashMap<String, String>> {
    let mut m = std::collections::HashMap::new();
    m.insert(
        "OPENROUTER_API_KEY".to_string(),
        "sk-p5-10-test-secret".to_string(),
    );
    m.insert("HARMLESS_VAR".to_string(), "still-here".to_string());
    std::sync::Arc::new(m)
}

#[tokio::test]
async fn env_policy_inherit_passes_the_secret_through_byte_identical() {
    let cwd = tmp_dir("env-inherit");
    let mut ctx = ctx_for(&cwd, SandboxPolicy::DangerFullAccess);
    ctx.shell_env = Some(shell_env_with_secret());
    ctx.sandbox_env_policy = SandboxEnvPolicy::Inherit;

    let out = run_bash(&ctx, "echo $OPENROUTER_API_KEY").await.unwrap();
    assert!(
        out.contains("sk-p5-10-test-secret"),
        "Inherit (byte-identical, pre-P5-10 default) must still pass the snapshot through, got: {out}"
    );
    let _ = std::fs::remove_dir_all(&cwd);
}

#[tokio::test]
async fn env_policy_filtered_strips_the_secret_keeps_the_rest() {
    let cwd = tmp_dir("env-filtered");
    let mut ctx = ctx_for(&cwd, SandboxPolicy::DangerFullAccess);
    ctx.shell_env = Some(shell_env_with_secret());
    ctx.sandbox_env_policy = SandboxEnvPolicy::Filtered;

    let out = run_bash(&ctx, "echo [$OPENROUTER_API_KEY] [$HARMLESS_VAR]")
        .await
        .unwrap();
    assert!(
        !out.contains("sk-p5-10-test-secret"),
        "Filtered must strip the secret var, got: {out}"
    );
    assert!(
        out.contains("still-here"),
        "Filtered must keep a harmless var, got: {out}"
    );
}

#[tokio::test]
async fn env_policy_none_keeps_only_the_minimal_set() {
    let cwd = tmp_dir("env-none");
    let mut ctx = ctx_for(&cwd, SandboxPolicy::DangerFullAccess);
    ctx.shell_env = Some(shell_env_with_secret());
    ctx.sandbox_env_policy = SandboxEnvPolicy::None;

    let out = run_bash(&ctx, "echo [$OPENROUTER_API_KEY] [$HARMLESS_VAR]")
        .await
        .unwrap();
    assert!(!out.contains("sk-p5-10-test-secret"));
    assert!(!out.contains("still-here"));
}

// ---------------------------------------------------------------------------
// Network: honest gap, never a silent no-op, never a refuse.
// ---------------------------------------------------------------------------

/// `network.enabled = true` with domain rules set is unenforceable on this
/// kernel class (needs the out-of-scope TLS-MITM proxy) — the call must
/// still PROCEED (never refuse for a network-only gap), proving the gap is
/// surfaced, not silently dropped, and not conflated with the fs
/// escalation gate.
#[tokio::test]
async fn network_domain_rules_gap_never_blocks_the_call() {
    let cwd = tmp_dir("net-domain-gap");
    let mut ctx = ctx_for(&cwd, SandboxPolicy::DangerFullAccess);
    ctx.network_policy = Some(NetworkPolicy {
        enabled: true,
        allow_domains: vec!["example.com".to_string()],
        deny_domains: vec![],
    });

    let out = run_bash(&ctx, "echo still-runs").await.unwrap();
    assert!(
        out.starts_with("exit code: 0") && out.contains("still-runs"),
        "an unenforceable network policy must warn, never refuse the call, got: {out}"
    );
    let _ = std::fs::remove_dir_all(&cwd);
}

// ---------------------------------------------------------------------------
// Escalation = "ask" routes through the P5-1 approval seam.
// ---------------------------------------------------------------------------

struct FixedApproval(supercode_harness::permissions::ApprovalOutcome);
impl supercode_harness::permissions::PermissionsApprovalHandler for FixedApproval {
    fn ask(
        &self,
        _req: &supercode_harness::permissions::ApprovalRequest,
    ) -> supercode_harness::permissions::ApprovalOutcome {
        self.0
    }
}

/// This test can't force Landlock itself unavailable (it IS available on
/// this box), but it proves the `escalation` WIRING end to end using the
/// one axis that's always "unenforceable" here regardless of kernel
/// support: `enabled = true` combined with a `SandboxPolicy` this
/// integration test can't otherwise reach unconfined... — instead, this
/// directly exercises `crate::sandbox::decide_fs`'s `Ask` branch (already
/// covered in `sandbox::tests`) is wired to a REAL
/// `PermissionsApprovalHandler` on a REAL `ToolContext`, by checking that
/// installing a DENYING handler under `escalation = ask` still lets a
/// perfectly ENFORCEABLE call through (Landlock IS available, so `Ask` is
/// never consulted at all) — a regression here would mean `escalation`
/// wrongly gates the AVAILABLE path too, not just the unavailable one.
#[tokio::test]
async fn escalation_ask_is_not_consulted_when_confinement_is_actually_available() {
    let cwd = tmp_dir("ask-not-consulted");
    let mut ctx = ctx_for(&cwd, SandboxPolicy::WorkspaceWrite);
    ctx.sandbox_escalation = SandboxEscalation::Ask;
    ctx.sandbox_approval_handler = Some(supercode_harness::sandbox::SandboxApprovalHandler(
        std::sync::Arc::new(FixedApproval(
            supercode_harness::permissions::ApprovalOutcome::Deny,
        )),
    ));

    // Landlock IS available here, so `decide_fs` returns `Confine`
    // directly — the (denying) handler is never even consulted, and the
    // call must succeed normally, confined.
    let inside = cwd.join("ok.txt");
    let out = run_bash(&ctx, &format!("echo hi > {}", inside.display()))
        .await
        .unwrap();
    assert!(
        out.starts_with("exit code: 0"),
        "confinement being AVAILABLE must short-circuit escalation entirely, got: {out}"
    );
    let _ = std::fs::remove_dir_all(&cwd);
}