Codex Handbook
core/tests/suite/windows_sandbox.rs 322 lines
use anyhow::Context;use codex_core::exec::ExecCapturePolicy;use codex_core::exec::ExecParams;use codex_core::exec::process_exec_tool_call;use codex_core::sandboxing::SandboxPermissions;use codex_core::windows_sandbox::sandbox_setup_is_complete;use codex_protocol::config_types::WindowsSandboxLevel;use codex_protocol::exec_output::ExecToolCallOutput;use codex_protocol::models::PermissionProfile;use codex_protocol::permissions::FileSystemAccessMode;use codex_protocol::permissions::FileSystemPath;use codex_protocol::permissions::FileSystemSandboxEntry;use codex_protocol::permissions::FileSystemSandboxPolicy;use codex_protocol::permissions::FileSystemSpecialPath;use codex_protocol::permissions::NetworkSandboxPolicy;use core_test_support::PathExt;use pretty_assertions::assert_eq;use serial_test::serial;use std::collections::HashMap;use std::ffi::OsString;use std::path::Path;use std::path::PathBuf;use tempfile::TempDir;struct EnvVarGuard {    key: &'static str,    original: Option<OsString>,}impl EnvVarGuard {    fn set(key: &'static str, value: &std::ffi::OsStr) -> Self {        let original = std::env::var_os(key);        unsafe {            std::env::set_var(key, value);        }        Self { key, original }    }}impl Drop for EnvVarGuard {    fn drop(&mut self) {        unsafe {            match &self.original {                Some(value) => std::env::set_var(self.key, value),                None => std::env::remove_var(self.key),            }        }    }}enum TestCodexHome {    Persistent(PathBuf),    Temporary(TempDir),}impl TestCodexHome {    fn path(&self) -> &Path {        match self {            Self::Persistent(path) => path.as_path(),            Self::Temporary(temp_dir) => temp_dir.path(),        }    }}fn codex_home_for_windows_sandbox_test(name: &str) -> anyhow::Result<TestCodexHome> {    if let Some(test_tmpdir) = std::env::var_os("TEST_TMPDIR") {        // The elevated backend provisions machine-local sandbox users. Bazel        // retries run in the same Windows VM, so keep CODEX_HOME stable within        // the test temp root and let setup reconcile its persisted ACL state.        let codex_home = PathBuf::from(test_tmpdir).join(name);        std::fs::create_dir_all(&codex_home)            .with_context(|| format!("create stable test CODEX_HOME {}", codex_home.display()))?;        return Ok(TestCodexHome::Persistent(codex_home));    }    Ok(TestCodexHome::Temporary(TempDir::new()?))}fn stage_windows_sandbox_helpers() -> anyhow::Result<()> {    let test_exe = std::env::current_exe().context("resolve current Windows test executable")?;    let test_exe_dir = test_exe        .parent()        .context("Windows test executable should have a parent directory")?;    let resources_dir = test_exe_dir.join("codex-resources");    match std::fs::create_dir_all(&resources_dir) {        Ok(()) => {}        Err(err)            if err.kind() == std::io::ErrorKind::PermissionDenied && resources_dir.is_dir() => {}        Err(err) => {            return Err(err)                .with_context(|| format!("create resources dir {}", resources_dir.display()));        }    }    for helper_name in ["codex-windows-sandbox-setup", "codex-command-runner"] {        let helper = codex_utils_cargo_bin::cargo_bin(helper_name)?;        let file_name = Path::new(helper_name).with_extension("exe");        let destination = resources_dir.join(file_name);        if let Err(err) = std::fs::copy(&helper, &destination) {            // A sandbox helper can briefly remain alive after the sandboxed            // command exits. Bazel may retry the test while that process still            // has the staged executable open, so keep the already-staged copy.            if err.kind() == std::io::ErrorKind::PermissionDenied && destination.exists() {                continue;            }            return Err(err).with_context(|| {                format!(                    "stage Windows sandbox helper {} at {}",                    helper.display(),                    destination.display()                )            });        }    }    Ok(())}#[tokio::test]#[serial(codex_home)]async fn windows_restricted_token_rejects_exact_and_glob_deny_read_policy() -> anyhow::Result<()> {    let codex_home =        codex_home_for_windows_sandbox_test("windows-restricted-token-deny-read-codex-home")?;    let _codex_home_guard = EnvVarGuard::set("CODEX_HOME", codex_home.path().as_os_str());    let workspace = TempDir::new()?;    let cwd = dunce::canonicalize(workspace.path())?.abs();    let secret = cwd.join("secret.env");    let future_secret = cwd.join("future.env");    let public = cwd.join("public.txt");    std::fs::write(&secret, "glob secret\n")?;    std::fs::write(&public, "public ok\n")?;    let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![        FileSystemSandboxEntry {            path: FileSystemPath::Special {                value: FileSystemSpecialPath::Root,            },            access: FileSystemAccessMode::Read,        },        FileSystemSandboxEntry {            path: FileSystemPath::Special {                value: FileSystemSpecialPath::project_roots(/*subpath*/ None),            },            access: FileSystemAccessMode::Write,        },        FileSystemSandboxEntry {            path: FileSystemPath::GlobPattern {                pattern: "**/*.env".to_string(),            },            access: FileSystemAccessMode::Deny,        },        FileSystemSandboxEntry {            path: FileSystemPath::Path {                path: future_secret,            },            access: FileSystemAccessMode::Deny,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_sandbox_policy,        NetworkSandboxPolicy::Restricted,    );    let err = process_exec_tool_call(        ExecParams {            command: vec![                "cmd.exe".to_string(),                "/D".to_string(),                "/C".to_string(),                "type secret.env >NUL 2>NUL & echo exact secret 1>future.env 2>NUL & type future.env 2>NUL & type public.txt & exit /B 0"                    .to_string(),            ],            cwd: cwd.clone(),            expiration: 10_000.into(),            capture_policy: ExecCapturePolicy::ShellTool,            env: HashMap::new(),            network: None,            sandbox_permissions: SandboxPermissions::UseDefault,            windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,            windows_sandbox_private_desktop: false,            justification: None,            arg0: None,        },        &permission_profile,        &cwd,        std::slice::from_ref(&cwd),        &None,        /*use_legacy_landlock*/ false,        /*stdout_stream*/ None,    )    .await    .expect_err("restricted-token sandbox should reject deny-read restrictions");    assert_eq!(        err.to_string(),        "unsupported operation: windows unelevated restricted-token sandbox cannot enforce deny-read restrictions directly; refusing to run unsandboxed"    );    Ok(())}#[tokio::test]#[serial(codex_home)]async fn windows_elevated_enforces_deny_read_and_protects_setup_marker() -> anyhow::Result<()> {    let codex_home = codex_home_for_windows_sandbox_test("windows-elevated-deny-read-codex-home")?;    let _codex_home_guard = EnvVarGuard::set("CODEX_HOME", codex_home.path().as_os_str());    stage_windows_sandbox_helpers()?;    let workspace = TempDir::new()?;    let cwd = dunce::canonicalize(workspace.path())?.abs();    let glob_secret = cwd.join("secret.env");    let exact_secret = cwd.join("exact-secret.txt");    let public = cwd.join("public.txt");    let setup_marker = codex_home.path().join(".sandbox").join("setup_marker.json");    std::fs::write(&glob_secret, "glob secret\n")?;    std::fs::write(&exact_secret, "exact secret\n")?;    std::fs::write(&public, "public ok\n")?;    let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![        FileSystemSandboxEntry {            path: FileSystemPath::Special {                value: FileSystemSpecialPath::Root,            },            access: FileSystemAccessMode::Read,        },        FileSystemSandboxEntry {            path: FileSystemPath::Special {                value: FileSystemSpecialPath::project_roots(/*subpath*/ None),            },            access: FileSystemAccessMode::Write,        },        FileSystemSandboxEntry {            path: FileSystemPath::GlobPattern {                pattern: "**/*.env".to_string(),            },            access: FileSystemAccessMode::Deny,        },        FileSystemSandboxEntry {            path: FileSystemPath::Path { path: exact_secret },            access: FileSystemAccessMode::Deny,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_sandbox_policy,        NetworkSandboxPolicy::Restricted,    );    let ExecToolCallOutput {        exit_code,        stdout,        ..    } = process_exec_tool_call(        ExecParams {            command: vec![                "cmd.exe".to_string(),                "/D".to_string(),                "/C".to_string(),                format!(                    "(type secret.env 1>NUL 2>NUL && echo GLOB-READ || echo GLOB-DENIED) & (type exact-secret.txt 1>NUL 2>NUL && echo EXACT-READ || echo EXACT-DENIED) & (type \"{}\" 1>NUL 2>NUL && echo MARKER-READ-ALLOWED || echo MARKER-READ-DENIED) & (echo tampered > \"{}\" 2>NUL && echo MARKER-WRITE-ALLOWED || echo MARKER-WRITE-DENIED) & type public.txt",                    setup_marker.display(),                    setup_marker.display()                ),            ],            cwd: cwd.clone(),            expiration: 10_000.into(),            capture_policy: ExecCapturePolicy::ShellTool,            env: HashMap::new(),            network: None,            sandbox_permissions: SandboxPermissions::UseDefault,            windows_sandbox_level: WindowsSandboxLevel::Elevated,            windows_sandbox_private_desktop: false,            justification: None,            arg0: None,        },        &permission_profile,        &cwd,        std::slice::from_ref(&cwd),        &None,        /*use_legacy_landlock*/ false,        /*stdout_stream*/ None,    )    .await?;    assert_eq!(exit_code, 0, "sandboxed command should complete");    assert!(        stdout.text.contains("GLOB-DENIED"),        "glob deny-read should block the secret: {stdout:?}"    );    assert!(        !stdout.text.contains("GLOB-READ"),        "glob deny-read should not allow the secret: {stdout:?}"    );    assert!(        stdout.text.contains("EXACT-DENIED"),        "exact deny-read should block the secret: {stdout:?}"    );    assert!(        !stdout.text.contains("EXACT-READ"),        "exact deny-read should not allow the secret: {stdout:?}"    );    assert!(        stdout.text.contains("public ok"),        "allowed reads should still work: {stdout:?}"    );    assert!(        stdout.text.contains("MARKER-READ-DENIED"),        "sandboxed command should not read setup readiness: {stdout:?}"    );    assert!(        stdout.text.contains("MARKER-WRITE-DENIED"),        "sandboxed command should not modify setup readiness: {stdout:?}"    );    assert!(        !stdout.text.contains("MARKER-READ-ALLOWED"),        "sandboxed command must not read setup readiness: {stdout:?}"    );    assert!(        !stdout.text.contains("MARKER-WRITE-ALLOWED"),        "sandboxed command must not modify setup readiness: {stdout:?}"    );    assert!(        sandbox_setup_is_complete(codex_home.path()),        "setup should remain ready after the tamper attempt"    );    Ok(())}