Codex Handbook
core/src/exec_tests.rs 1269 lines
use super::*;use codex_protocol::config_types::WindowsSandboxLevel;use codex_protocol::models::PermissionProfile;use codex_sandboxing::SandboxType;use core_test_support::PathBufExt;use core_test_support::PathExt;use pretty_assertions::assert_eq;use std::collections::HashMap;use std::time::Duration;use tokio::io::AsyncWriteExt;use tokio::time::timeout;fn make_exec_output(    exit_code: i32,    stdout: &str,    stderr: &str,    aggregated: &str,) -> ExecToolCallOutput {    ExecToolCallOutput {        exit_code,        stdout: StreamOutput::new(stdout.to_string()),        stderr: StreamOutput::new(stderr.to_string()),        aggregated_output: StreamOutput::new(aggregated.to_string()),        duration: Duration::from_millis(1),        timed_out: false,    }}#[test]fn sandbox_detection_requires_keywords() {    let output = make_exec_output(/*exit_code*/ 1, "", "", "");    assert!(!is_likely_sandbox_denied(        SandboxType::LinuxSeccomp,        &output    ));}#[test]fn sandbox_detection_identifies_keyword_in_stderr() {    let output = make_exec_output(/*exit_code*/ 1, "", "Operation not permitted", "");    assert!(is_likely_sandbox_denied(SandboxType::LinuxSeccomp, &output));}#[test]fn sandbox_detection_respects_quick_reject_exit_codes() {    let output = make_exec_output(/*exit_code*/ 127, "", "command not found", "");    assert!(!is_likely_sandbox_denied(        SandboxType::LinuxSeccomp,        &output    ));}#[test]fn sandbox_detection_ignores_non_sandbox_mode() {    let output = make_exec_output(/*exit_code*/ 1, "", "Operation not permitted", "");    assert!(!is_likely_sandbox_denied(SandboxType::None, &output));}#[test]fn sandbox_detection_ignores_network_policy_text_in_non_sandbox_mode() {    let output = make_exec_output(        /*exit_code*/ 0,        "",        "",        r#"CODEX_NETWORK_POLICY_DECISION {"decision":"ask","reason":"not_allowed","source":"decider","protocol":"http","host":"google.com","port":80}"#,    );    assert!(!is_likely_sandbox_denied(SandboxType::None, &output));}#[test]fn sandbox_detection_uses_aggregated_output() {    let output = make_exec_output(        /*exit_code*/ 101,        "",        "",        "cargo failed: Read-only file system when writing target",    );    assert!(is_likely_sandbox_denied(        SandboxType::MacosSeatbelt,        &output    ));}#[test]fn sandbox_detection_ignores_network_policy_text_with_zero_exit_code() {    let output = make_exec_output(        /*exit_code*/ 0,        "",        "",        r#"CODEX_NETWORK_POLICY_DECISION {"decision":"ask","source":"decider","protocol":"http","host":"google.com","port":80}"#,    );    assert!(!is_likely_sandbox_denied(        SandboxType::LinuxSeccomp,        &output    ));}#[tokio::test]async fn read_output_limits_retained_bytes_for_shell_capture() {    let (mut writer, reader) = tokio::io::duplex(1024);    let bytes = vec![b'a'; EXEC_OUTPUT_MAX_BYTES.saturating_add(128 * 1024)];    tokio::spawn(async move {        writer.write_all(&bytes).await.expect("write");    });    let out = read_output(        reader,        /*stream*/ None,        /*is_stderr*/ false,        Some(EXEC_OUTPUT_MAX_BYTES),    )    .await    .expect("read");    assert_eq!(out.text.len(), EXEC_OUTPUT_MAX_BYTES);}#[test]fn aggregate_output_prefers_stderr_on_contention() {    let stdout = StreamOutput {        text: vec![b'a'; EXEC_OUTPUT_MAX_BYTES],        truncated_after_lines: None,    };    let stderr = StreamOutput {        text: vec![b'b'; EXEC_OUTPUT_MAX_BYTES],        truncated_after_lines: None,    };    let aggregated = aggregate_output(&stdout, &stderr, Some(EXEC_OUTPUT_MAX_BYTES));    let stdout_cap = EXEC_OUTPUT_MAX_BYTES / 3;    let stderr_cap = EXEC_OUTPUT_MAX_BYTES.saturating_sub(stdout_cap);    assert_eq!(aggregated.text.len(), EXEC_OUTPUT_MAX_BYTES);    assert_eq!(aggregated.text[..stdout_cap], vec![b'a'; stdout_cap]);    assert_eq!(aggregated.text[stdout_cap..], vec![b'b'; stderr_cap]);}#[test]fn aggregate_output_fills_remaining_capacity_with_stderr() {    let stdout_len = EXEC_OUTPUT_MAX_BYTES / 10;    let stdout = StreamOutput {        text: vec![b'a'; stdout_len],        truncated_after_lines: None,    };    let stderr = StreamOutput {        text: vec![b'b'; EXEC_OUTPUT_MAX_BYTES],        truncated_after_lines: None,    };    let aggregated = aggregate_output(&stdout, &stderr, Some(EXEC_OUTPUT_MAX_BYTES));    let stderr_cap = EXEC_OUTPUT_MAX_BYTES.saturating_sub(stdout_len);    assert_eq!(aggregated.text.len(), EXEC_OUTPUT_MAX_BYTES);    assert_eq!(aggregated.text[..stdout_len], vec![b'a'; stdout_len]);    assert_eq!(aggregated.text[stdout_len..], vec![b'b'; stderr_cap]);}#[test]fn aggregate_output_rebalances_when_stderr_is_small() {    let stdout = StreamOutput {        text: vec![b'a'; EXEC_OUTPUT_MAX_BYTES],        truncated_after_lines: None,    };    let stderr = StreamOutput {        text: vec![b'b'; 1],        truncated_after_lines: None,    };    let aggregated = aggregate_output(&stdout, &stderr, Some(EXEC_OUTPUT_MAX_BYTES));    let stdout_len = EXEC_OUTPUT_MAX_BYTES.saturating_sub(1);    assert_eq!(aggregated.text.len(), EXEC_OUTPUT_MAX_BYTES);    assert_eq!(aggregated.text[..stdout_len], vec![b'a'; stdout_len]);    assert_eq!(aggregated.text[stdout_len..], vec![b'b'; 1]);}#[test]fn aggregate_output_keeps_stdout_then_stderr_when_under_cap() {    let stdout = StreamOutput {        text: vec![b'a'; 4],        truncated_after_lines: None,    };    let stderr = StreamOutput {        text: vec![b'b'; 3],        truncated_after_lines: None,    };    let aggregated = aggregate_output(&stdout, &stderr, Some(EXEC_OUTPUT_MAX_BYTES));    let mut expected = Vec::new();    expected.extend_from_slice(&stdout.text);    expected.extend_from_slice(&stderr.text);    assert_eq!(aggregated.text, expected);    assert_eq!(aggregated.truncated_after_lines, None);}#[tokio::test]async fn read_output_retains_all_bytes_for_full_buffer_capture() {    let (mut writer, reader) = tokio::io::duplex(1024);    let bytes = vec![b'a'; EXEC_OUTPUT_MAX_BYTES.saturating_add(128 * 1024)];    let expected_len = bytes.len();    // The duplex pipe is smaller than `bytes`, so the writer must run concurrently    // with `read_output()` or `write_all()` will block once the buffer fills up.    tokio::spawn(async move {        writer.write_all(&bytes).await.expect("write");    });    let out = read_output(        reader, /*stream*/ None, /*is_stderr*/ false, /*max_bytes*/ None,    )    .await    .expect("read");    assert_eq!(out.text.len(), expected_len);}#[test]fn aggregate_output_keeps_all_bytes_when_uncapped() {    let stdout = StreamOutput {        text: vec![b'a'; EXEC_OUTPUT_MAX_BYTES],        truncated_after_lines: None,    };    let stderr = StreamOutput {        text: vec![b'b'; EXEC_OUTPUT_MAX_BYTES],        truncated_after_lines: None,    };    let aggregated = aggregate_output(&stdout, &stderr, /*max_bytes*/ None);    assert_eq!(aggregated.text.len(), EXEC_OUTPUT_MAX_BYTES * 2);    assert_eq!(        aggregated.text[..EXEC_OUTPUT_MAX_BYTES],        vec![b'a'; EXEC_OUTPUT_MAX_BYTES]    );    assert_eq!(        aggregated.text[EXEC_OUTPUT_MAX_BYTES..],        vec![b'b'; EXEC_OUTPUT_MAX_BYTES]    );}#[test]fn full_buffer_capture_policy_disables_caps_and_exec_expiration() {    assert_eq!(ExecCapturePolicy::FullBuffer.retained_bytes_cap(), None);    assert_eq!(        ExecCapturePolicy::FullBuffer.io_drain_timeout(),        Duration::from_millis(IO_DRAIN_TIMEOUT_MS)    );    assert!(!ExecCapturePolicy::FullBuffer.uses_expiration());}#[tokio::test]async fn exec_full_buffer_capture_ignores_expiration() -> Result<()> {    #[cfg(windows)]    let command = vec![        "powershell.exe".to_string(),        "-NonInteractive".to_string(),        "-NoLogo".to_string(),        "-Command".to_string(),        "Start-Sleep -Milliseconds 50; [Console]::Out.Write('hello')".to_string(),    ];    #[cfg(not(windows))]    let command = vec![        "/bin/sh".to_string(),        "-c".to_string(),        "sleep 0.05; printf hello".to_string(),    ];    let env: HashMap<String, String> = std::env::vars().collect();    let output = exec(        ExecParams {            command,            cwd: codex_utils_absolute_path::AbsolutePathBuf::current_dir()?,            expiration: 1.into(),            capture_policy: ExecCapturePolicy::FullBuffer,            env,            network: None,            sandbox_permissions: SandboxPermissions::UseDefault,            windows_sandbox_level: WindowsSandboxLevel::Disabled,            windows_sandbox_private_desktop: false,            justification: None,            arg0: None,        },        NetworkSandboxPolicy::Enabled,        /*stdout_stream*/ None,        /*after_spawn*/ None,    )    .await?;    assert_eq!(output.stdout.from_utf8_lossy().text.trim(), "hello");    assert!(!output.timed_out);    Ok(())}#[cfg(unix)]#[tokio::test]async fn exec_full_buffer_capture_keeps_io_drain_timeout_when_descendant_holds_pipe_open()-> Result<()> {    let output = tokio::time::timeout(        Duration::from_millis(IO_DRAIN_TIMEOUT_MS * 3),        exec(            ExecParams {                command: vec![                    "/bin/sh".to_string(),                    "-c".to_string(),                    "printf hello; sleep 30 &".to_string(),                ],                cwd: codex_utils_absolute_path::AbsolutePathBuf::current_dir()?,                expiration: 1.into(),                capture_policy: ExecCapturePolicy::FullBuffer,                env: std::env::vars().collect(),                network: None,                sandbox_permissions: SandboxPermissions::UseDefault,                windows_sandbox_level: WindowsSandboxLevel::Disabled,                windows_sandbox_private_desktop: false,                justification: None,                arg0: None,            },            NetworkSandboxPolicy::Enabled,            /*stdout_stream*/ None,            /*after_spawn*/ None,        ),    )    .await    .expect("full-buffer exec should return once the I/O drain guard fires")?;    assert!(!output.timed_out);    Ok(())}#[tokio::test]async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result<()> {    let byte_count = EXEC_OUTPUT_MAX_BYTES.saturating_add(128 * 1024);    #[cfg(windows)]    let command = vec![        "powershell.exe".to_string(),        "-NonInteractive".to_string(),        "-NoLogo".to_string(),        "-Command".to_string(),        format!("Start-Sleep -Milliseconds 50; [Console]::Out.Write('a' * {byte_count})"),    ];    #[cfg(not(windows))]    let command = vec![        "/bin/sh".to_string(),        "-c".to_string(),        format!("sleep 0.05; head -c {byte_count} /dev/zero | tr '\\0' 'a'"),    ];    let cwd = codex_utils_absolute_path::AbsolutePathBuf::current_dir()?;    let permission_profile = PermissionProfile::Disabled;    let output = process_exec_tool_call(        ExecParams {            command,            cwd: cwd.clone(),            expiration: 1.into(),            capture_policy: ExecCapturePolicy::FullBuffer,            env: std::env::vars().collect(),            network: None,            sandbox_permissions: SandboxPermissions::UseDefault,            windows_sandbox_level: WindowsSandboxLevel::Disabled,            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!(!output.timed_out);    assert_eq!(output.stdout.text.len(), byte_count);    Ok(())}#[test]fn windows_restricted_token_skips_external_sandbox_policies() {    let permission_profile = PermissionProfile::External {        network: NetworkSandboxPolicy::Restricted,    };    assert!(!permission_profile_supports_windows_restricted_token_sandbox(&permission_profile));}#[test]fn windows_restricted_token_supports_read_only_profiles() {    let permission_profile = PermissionProfile::read_only();    assert!(permission_profile_supports_windows_restricted_token_sandbox(&permission_profile));}#[test]fn windows_proxy_enforcement_uses_elevated_backend() {    assert!(!windows_sandbox_uses_elevated_backend(        WindowsSandboxLevel::RestrictedToken,        /*proxy_enforced*/ false,    ));    assert!(windows_sandbox_uses_elevated_backend(        WindowsSandboxLevel::RestrictedToken,        /*proxy_enforced*/ true,    ));    assert!(windows_sandbox_uses_elevated_backend(        WindowsSandboxLevel::Elevated,        /*proxy_enforced*/ false,    ));}#[test]fn windows_restricted_token_rejects_network_only_restrictions() {    let permission_profile = PermissionProfile::from_runtime_permissions(        &FileSystemSandboxPolicy::unrestricted(),        NetworkSandboxPolicy::Restricted,    );    let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd");    assert_eq!(            unsupported_windows_restricted_token_sandbox_reason(                SandboxType::WindowsRestrictedToken,                &permission_profile,                &sandbox_policy_cwd,                WindowsSandboxLevel::RestrictedToken,            ),            Some(                "windows sandbox backend cannot enforce file_system=Unrestricted, network=Restricted, permission_profile=Managed; refusing to run unsandboxed".to_string()            )        );}#[test]fn windows_restricted_token_rejects_managed_root_write_profiles() {    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::Root,            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_policy,        NetworkSandboxPolicy::Restricted,    );    let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd");    assert_eq!(        unsupported_windows_restricted_token_sandbox_reason(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &sandbox_policy_cwd,            WindowsSandboxLevel::RestrictedToken,        ),        Some(            "windows sandbox backend cannot enforce file_system=Restricted, network=Restricted, permission_profile=Managed; refusing to run unsandboxed"                .to_string()        )    );}#[test]fn windows_restricted_token_allows_read_only_profiles() {    let permission_profile = PermissionProfile::read_only();    let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd");    assert_eq!(        unsupported_windows_restricted_token_sandbox_reason(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &sandbox_policy_cwd,            WindowsSandboxLevel::RestrictedToken,        ),        None    );}#[test]fn windows_restricted_token_allows_workspace_write_profiles() {    let permission_profile = PermissionProfile::workspace_write_with(        &[],        NetworkSandboxPolicy::Restricted,        /*exclude_tmpdir_env_var*/ true,        /*exclude_slash_tmp*/ true,    );    let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd");    assert_eq!(        unsupported_windows_restricted_token_sandbox_reason(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &sandbox_policy_cwd,            WindowsSandboxLevel::RestrictedToken,        ),        None    );}#[test]fn windows_elevated_allows_split_restricted_read_policies() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let docs = codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(        temp_dir.path().join("docs"),    )    .expect("absolute docs");    std::fs::create_dir_all(docs.as_path()).expect("create docs");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path { path: docs },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_policy,        NetworkSandboxPolicy::Restricted,    );    assert_eq!(        unsupported_windows_restricted_token_sandbox_reason(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &temp_dir.path().abs(),            WindowsSandboxLevel::Elevated,        ),        None    );}#[test]fn windows_restricted_token_rejects_split_only_filesystem_policies() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let docs = temp_dir.path().join("docs");    std::fs::create_dir_all(&docs).expect("create docs");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(                    /*subpath*/ None,                ),            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path {                path: codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&docs)                    .expect("absolute docs"),            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_policy,        NetworkSandboxPolicy::Restricted,    );    assert_eq!(        unsupported_windows_restricted_token_sandbox_reason(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &temp_dir.path().abs(),            WindowsSandboxLevel::RestrictedToken,        ),        Some(            "windows unelevated restricted-token sandbox cannot enforce split filesystem read restrictions directly; refusing to run unsandboxed"                .to_string()        )    );}#[test]fn windows_restricted_token_rejects_root_write_read_only_carveouts() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let docs = temp_dir.path().join("docs");    std::fs::create_dir_all(&docs).expect("create docs");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::Root,            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path {                path: codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&docs)                    .expect("absolute docs"),            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_policy,        NetworkSandboxPolicy::Restricted,    );    assert_eq!(        unsupported_windows_restricted_token_sandbox_reason(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &temp_dir.path().abs(),            WindowsSandboxLevel::RestrictedToken,        ),        Some(            "windows unelevated restricted-token sandbox cannot enforce split writable root sets directly; refusing to run unsandboxed"                .to_string()        )    );}#[test]fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let cwd = dunce::canonicalize(temp_dir.path())        .expect("canonicalize temp dir")        .abs();    let docs = cwd.join("docs");    std::fs::create_dir_all(docs.as_path()).expect("create docs");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::Root,            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(                    /*subpath*/ None,                ),            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path { path: docs.clone() },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_policy,        NetworkSandboxPolicy::Restricted,    );    // The workspace-write compatibility projection already protects top-level    // `.codex`, so the restricted-token overlay only needs the extra read-only    // docs carveout.    let expected_deny_write_paths = vec![docs];    assert_eq!(        resolve_windows_restricted_token_filesystem_overrides(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &cwd,            WindowsSandboxLevel::RestrictedToken,        ),        Ok(Some(WindowsSandboxFilesystemOverrides {            read_roots_override: None,            read_roots_include_platform_defaults: false,            write_roots_override: None,            additional_deny_read_paths: vec![],            additional_deny_write_paths: expected_deny_write_paths,        }))    );}#[test]fn windows_restricted_token_rejects_unreadable_split_carveouts() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let cwd = dunce::canonicalize(temp_dir.path())        .expect("canonicalize temp dir")        .abs();    let blocked = cwd.join("blocked");    std::fs::create_dir_all(blocked.as_path()).expect("create blocked");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::Root,            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(                    /*subpath*/ None,                ),            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path { path: blocked },            access: codex_protocol::permissions::FileSystemAccessMode::Deny,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_policy,        NetworkSandboxPolicy::Restricted,    );    assert_eq!(        resolve_windows_restricted_token_filesystem_overrides(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &cwd,            WindowsSandboxLevel::RestrictedToken,        ),        Err(            "windows unelevated restricted-token sandbox cannot enforce deny-read restrictions directly; refusing to run unsandboxed"                .to_string()        )    );}#[test]fn windows_elevated_supports_split_restricted_read_roots() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let docs = temp_dir.path().join("docs");    std::fs::create_dir_all(&docs).expect("create docs");    let expected_docs = dunce::canonicalize(&docs).expect("canonical docs");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path {                path: codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&docs)                    .expect("absolute docs"),            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_policy,        NetworkSandboxPolicy::Restricted,    );    assert_eq!(        resolve_windows_elevated_filesystem_overrides(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &temp_dir.path().abs(),            /*use_windows_elevated_backend*/ true,        ),        Ok(Some(WindowsSandboxFilesystemOverrides {            read_roots_override: Some(vec![expected_docs]),            read_roots_include_platform_defaults: false,            write_roots_override: None,            additional_deny_read_paths: vec![],            additional_deny_write_paths: vec![],        }))    );}#[test]fn windows_elevated_supports_split_write_read_carveouts() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let docs = temp_dir.path().join("docs");    std::fs::create_dir_all(&docs).expect("create docs");    let expected_docs = dunce::canonicalize(&docs).expect("canonical docs");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::Root,            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(                    /*subpath*/ None,                ),            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path {                path: codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&docs)                    .expect("absolute docs"),            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_policy,        NetworkSandboxPolicy::Restricted,    );    assert_eq!(        resolve_windows_elevated_filesystem_overrides(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &temp_dir.path().abs(),            /*use_windows_elevated_backend*/ true,        ),        Ok(Some(WindowsSandboxFilesystemOverrides {            read_roots_override: None,            read_roots_include_platform_defaults: false,            write_roots_override: None,            additional_deny_read_paths: vec![],            additional_deny_write_paths: vec![                codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(expected_docs)                    .expect("absolute docs"),            ],        }))    );}#[test]fn windows_elevated_supports_unreadable_split_carveouts() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let blocked = temp_dir.path().join("blocked");    std::fs::create_dir_all(&blocked).expect("create blocked");    let expected_blocked = dunce::canonicalize(&blocked).expect("canonical blocked");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::Root,            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(                    /*subpath*/ None,                ),            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path {                path: codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&blocked)                    .expect("absolute blocked"),            },            access: codex_protocol::permissions::FileSystemAccessMode::Deny,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_policy,        NetworkSandboxPolicy::Restricted,    );    assert_eq!(        resolve_windows_elevated_filesystem_overrides(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &temp_dir.path().abs(),            /*use_windows_elevated_backend*/ true,        ),        Ok(Some(WindowsSandboxFilesystemOverrides {            read_roots_override: None,            read_roots_include_platform_defaults: false,            write_roots_override: None,            additional_deny_read_paths: vec![                codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(                    expected_blocked.clone(),                )                .expect("absolute blocked"),            ],            additional_deny_write_paths: vec![                codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(expected_blocked)                    .expect("absolute blocked"),            ],        }))    );}#[test]fn windows_elevated_supports_unreadable_globs() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let secret = temp_dir.path().join("app").join(".env");    std::fs::create_dir_all(secret.parent().expect("parent")).expect("create parent");    std::fs::write(&secret, "secret").expect("write secret");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::Root,            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(                    /*subpath*/ None,                ),            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::GlobPattern {                pattern: "**/*.env".to_string(),            },            access: codex_protocol::permissions::FileSystemAccessMode::Deny,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_policy,        NetworkSandboxPolicy::Restricted,    );    assert_eq!(        resolve_windows_elevated_filesystem_overrides(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &temp_dir.path().abs(),            /*use_windows_elevated_backend*/ true,        ),        Ok(Some(WindowsSandboxFilesystemOverrides {            read_roots_override: None,            read_roots_include_platform_defaults: false,            write_roots_override: None,            additional_deny_read_paths: vec![                codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(secret)                    .expect("absolute secret"),            ],            additional_deny_write_paths: vec![],        }))    );}#[test]fn windows_elevated_rejects_reopened_writable_descendants() {    let temp_dir = tempfile::TempDir::new().expect("tempdir");    let docs = temp_dir.path().join("docs");    let nested = docs.join("nested");    std::fs::create_dir_all(&nested).expect("create nested");    let file_system_policy = FileSystemSandboxPolicy::restricted(vec![        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::Root,            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Special {                value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(                    /*subpath*/ None,                ),            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path {                path: codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&docs)                    .expect("absolute docs"),            },            access: codex_protocol::permissions::FileSystemAccessMode::Read,        },        codex_protocol::permissions::FileSystemSandboxEntry {            path: codex_protocol::permissions::FileSystemPath::Path {                path: codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(&nested)                    .expect("absolute nested"),            },            access: codex_protocol::permissions::FileSystemAccessMode::Write,        },    ]);    let permission_profile = PermissionProfile::from_runtime_permissions(        &file_system_policy,        NetworkSandboxPolicy::Restricted,    );    assert_eq!(        unsupported_windows_restricted_token_sandbox_reason(            SandboxType::WindowsRestrictedToken,            &permission_profile,            &temp_dir.path().abs(),            WindowsSandboxLevel::Elevated,        ),        Some(            "windows elevated sandbox cannot reopen writable descendants under read-only carveouts directly; refusing to run unsandboxed"                .to_string()        )    );}#[test]fn process_exec_tool_call_uses_platform_sandbox_for_network_only_restrictions() {    let expected = codex_sandboxing::get_platform_sandbox(/*windows_sandbox_enabled*/ false)        .unwrap_or(SandboxType::None);    assert_eq!(        select_process_exec_tool_sandbox_type(            &FileSystemSandboxPolicy::unrestricted(),            NetworkSandboxPolicy::Restricted,            codex_protocol::config_types::WindowsSandboxLevel::Disabled,            /*enforce_managed_network*/ false,        ),        expected    );}#[test]fn build_exec_request_preserves_windows_workspace_roots() -> Result<()> {    let temp_dir = tempfile::TempDir::new()?;    let cwd = temp_dir.path().abs();    let additional_root = temp_dir.path().join("additional").abs();    let workspace_roots = vec![cwd.clone(), additional_root];    let exec_request = build_exec_request(        ExecParams {            command: vec!["echo".to_string(), "ok".to_string()],            cwd: cwd.clone(),            expiration: ExecExpiration::DefaultTimeout,            capture_policy: ExecCapturePolicy::ShellTool,            env: HashMap::new(),            network: None,            sandbox_permissions: SandboxPermissions::UseDefault,            windows_sandbox_level: WindowsSandboxLevel::Disabled,            windows_sandbox_private_desktop: false,            justification: None,            arg0: None,        },        &PermissionProfile::Disabled,        &cwd,        workspace_roots.as_slice(),        &None,        /*use_legacy_landlock*/ false,    )?;    assert_eq!(        exec_request.windows_sandbox_workspace_roots,        workspace_roots    );    Ok(())}#[cfg(unix)]#[test]fn sandbox_detection_flags_sigsys_exit_code() {    let exit_code = EXIT_CODE_SIGNAL_BASE + libc::SIGSYS;    let output = make_exec_output(exit_code, "", "", "");    assert!(is_likely_sandbox_denied(SandboxType::LinuxSeccomp, &output));}#[cfg(unix)]#[tokio::test]async fn kill_child_process_group_kills_grandchildren_on_timeout() -> Result<()> {    // On Linux/macOS, /bin/bash is typically present; on FreeBSD/OpenBSD,    // prefer /bin/sh to avoid NotFound errors.    #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]    let command = vec![        "/bin/sh".to_string(),        "-c".to_string(),        "sleep 60 & echo $!; sleep 60".to_string(),    ];    #[cfg(all(unix, not(any(target_os = "freebsd", target_os = "openbsd"))))]    let command = vec![        "/bin/bash".to_string(),        "-c".to_string(),        "sleep 60 & echo $!; sleep 60".to_string(),    ];    let cwd = codex_utils_absolute_path::AbsolutePathBuf::current_dir()?;    let env: HashMap<String, String> = std::env::vars().collect();    let params = ExecParams {        command,        cwd,        expiration: 500.into(),        capture_policy: ExecCapturePolicy::ShellTool,        env,        network: None,        sandbox_permissions: SandboxPermissions::UseDefault,        windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,        windows_sandbox_private_desktop: false,        justification: None,        arg0: None,    };    let output = exec(        params,        NetworkSandboxPolicy::Restricted,        /*stdout_stream*/ None,        /*after_spawn*/ None,    )    .await?;    assert!(output.timed_out);    let stdout = output.stdout.from_utf8_lossy().text;    let pid_line = stdout.lines().next().unwrap_or("").trim();    let pid: i32 = pid_line.parse().map_err(|error| {        io::Error::new(            io::ErrorKind::InvalidData,            format!("Failed to parse pid from stdout '{pid_line}': {error}"),        )    })?;    let mut killed = false;    for _ in 0..20 {        // Use kill(pid, 0) to check if the process is alive.        if unsafe { libc::kill(pid, 0) } == -1            && let Some(libc::ESRCH) = std::io::Error::last_os_error().raw_os_error()        {            killed = true;            break;        }        tokio::time::sleep(Duration::from_millis(100)).await;    }    assert!(killed, "grandchild process with pid {pid} is still alive");    Ok(())}#[tokio::test]async fn process_exec_tool_call_respects_cancellation_token() -> Result<()> {    let command = long_running_command();    let cwd = codex_utils_absolute_path::AbsolutePathBuf::current_dir()?;    let env: HashMap<String, String> = std::env::vars().collect();    let cancel_token = CancellationToken::new();    let cancel_tx = cancel_token.clone();    let params = ExecParams {        command,        cwd: cwd.clone(),        expiration: ExecExpiration::Cancellation(cancel_token),        capture_policy: ExecCapturePolicy::ShellTool,        env,        network: None,        sandbox_permissions: SandboxPermissions::UseDefault,        windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,        windows_sandbox_private_desktop: false,        justification: None,        arg0: None,    };    tokio::spawn(async move {        tokio::time::sleep(Duration::from_millis(1_000)).await;        cancel_tx.cancel();    });    let result = timeout(        Duration::from_secs(5),        process_exec_tool_call(            params,            &PermissionProfile::Disabled,            &cwd,            std::slice::from_ref(&cwd),            &None,            /*use_legacy_landlock*/ false,            /*stdout_stream*/ None,        ),    )    .await    .expect("cancellation should stop the process promptly");    let output = result.expect("cancellation should return a non-timeout exec result");    assert!(!output.timed_out);    assert_ne!(output.exit_code, 0);    assert_ne!(output.exit_code, EXEC_TIMEOUT_EXIT_CODE);    Ok(())}#[cfg(unix)]#[tokio::test]async fn process_exec_tool_call_cancellation_allows_sigterm_cleanup() -> Result<()> {    let temp_dir = tempfile::TempDir::new()?;    let ready_marker = temp_dir.path().join("ready");    let cleanup_marker = temp_dir.path().join("cleanup");    let descendant_pid_marker = temp_dir.path().join("descendant-pid");    // The parent handles TERM and records cleanup, while a TERM-ignoring child    // proves cancellation still escalates any survivors in the process group.    let command = vec![        "/bin/sh".to_string(),        "-c".to_string(),        r#"(trap '' TERM; sleep 60) &printf '%s' "$!" > "$DESCENDANT_PID_MARKER"trap 'printf cleaned > "$CLEANUP_MARKER"; exit 0' TERMprintf ready > "$READY_MARKER"while :; do sleep 1; done"#            .to_string(),    ];    let cwd = codex_utils_absolute_path::AbsolutePathBuf::current_dir()?;    let mut env: HashMap<String, String> = std::env::vars().collect();    env.insert(        "READY_MARKER".to_string(),        ready_marker.to_string_lossy().into_owned(),    );    env.insert(        "CLEANUP_MARKER".to_string(),        cleanup_marker.to_string_lossy().into_owned(),    );    env.insert(        "DESCENDANT_PID_MARKER".to_string(),        descendant_pid_marker.to_string_lossy().into_owned(),    );    let cancel_token = CancellationToken::new();    let cancel_tx = cancel_token.clone();    tokio::spawn(async move {        for _ in 0..50 {            if ready_marker.exists() {                cancel_tx.cancel();                return;            }            tokio::time::sleep(Duration::from_millis(20)).await;        }        cancel_tx.cancel();    });    let params = ExecParams {        command,        cwd: cwd.clone(),        expiration: ExecExpiration::DefaultTimeout.with_cancellation(cancel_token),        capture_policy: ExecCapturePolicy::ShellTool,        env,        network: None,        sandbox_permissions: SandboxPermissions::UseDefault,        windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,        windows_sandbox_private_desktop: false,        justification: None,        arg0: None,    };    let result = timeout(        Duration::from_secs(5),        process_exec_tool_call(            params,            &PermissionProfile::Disabled,            &cwd,            std::slice::from_ref(&cwd),            &None,            /*use_legacy_landlock*/ false,            /*stdout_stream*/ None,        ),    )    .await    .expect("cancellation should stop the process promptly");    let output = result.expect("cancellation should return a non-timeout exec result");    assert!(!output.timed_out);    assert_eq!(        std::fs::read_to_string(cleanup_marker)?,        "cleaned",        "SIGTERM cleanup trap should run before cancellation falls back to a hard kill"    );    let descendant_pid = std::fs::read_to_string(descendant_pid_marker)?        .parse::<i32>()        .map_err(|error| {            io::Error::new(                io::ErrorKind::InvalidData,                format!("failed to parse descendant pid: {error}"),            )        })?;    let mut killed = false;    for _ in 0..20 {        if unsafe { libc::kill(descendant_pid, 0) } == -1            && let Some(libc::ESRCH) = std::io::Error::last_os_error().raw_os_error()        {            killed = true;            break;        }        tokio::time::sleep(Duration::from_millis(100)).await;    }    if !killed {        unsafe {            libc::kill(descendant_pid, libc::SIGKILL);        }    }    assert!(        killed,        "TERM-ignoring descendant process with pid {descendant_pid} is still alive"    );    Ok(())}#[cfg(unix)]fn long_running_command() -> Vec<String> {    vec![        "/bin/sh".to_string(),        "-c".to_string(),        "sleep 30".to_string(),    ]}#[cfg(windows)]fn long_running_command() -> Vec<String> {    vec![        "powershell.exe".to_string(),        "-NonInteractive".to_string(),        "-NoLogo".to_string(),        "-Command".to_string(),        "Start-Sleep -Seconds 30".to_string(),    ]}