Codex Handbook
windows-sandbox-rs/src/allow.rs 380 lines
use crate::resolved_permissions::ResolvedWindowsSandboxPermissions;use dunce::canonicalize;use std::collections::HashMap;use std::collections::HashSet;use std::path::Path;use std::path::PathBuf;#[derive(Debug, Default, PartialEq, Eq)]pub struct AllowDenyPaths {    pub allow: HashSet<PathBuf>,    pub deny: HashSet<PathBuf>,}pub(crate) fn compute_allow_paths_for_permissions(    permissions: &ResolvedWindowsSandboxPermissions,    command_cwd: &Path,    env_map: &HashMap<String, String>,) -> AllowDenyPaths {    let mut allow: HashSet<PathBuf> = HashSet::new();    let mut deny: HashSet<PathBuf> = HashSet::new();    let mut add_allow_path = |p: PathBuf| {        if p.exists() {            allow.insert(p);        }    };    let mut add_deny_path = |p: PathBuf| {        if p.exists() {            deny.insert(p);        }    };    for writable_root in permissions.writable_roots_for_cwd(command_cwd, env_map) {        let canonical = canonicalize(&writable_root.root).unwrap_or(writable_root.root);        add_allow_path(canonical);        for read_only_subpath in writable_root.read_only_subpaths {            add_deny_path(read_only_subpath);        }    }    AllowDenyPaths { allow, deny }}#[cfg(test)]mod tests {    use super::*;    use codex_protocol::models::PermissionProfile;    use codex_protocol::permissions::NetworkSandboxPolicy;    use codex_utils_absolute_path::AbsolutePathBuf;    use std::fs;    use tempfile::TempDir;    fn workspace_write_profile(        writable_roots: &[AbsolutePathBuf],        exclude_tmpdir_env_var: bool,        exclude_slash_tmp: bool,    ) -> PermissionProfile {        PermissionProfile::workspace_write_with(            writable_roots,            NetworkSandboxPolicy::Restricted,            exclude_tmpdir_env_var,            exclude_slash_tmp,        )    }    fn workspace_roots_for(root: &Path) -> Vec<AbsolutePathBuf> {        vec![AbsolutePathBuf::from_absolute_path(root).expect("absolute workspace root")]    }    fn compute_allow_paths(        permission_profile: &PermissionProfile,        workspace_roots: &[AbsolutePathBuf],        command_cwd: &Path,        env_map: &HashMap<String, String>,    ) -> AllowDenyPaths {        let permissions =            ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots(                permission_profile,                workspace_roots,            )            .expect("managed permission profile");        compute_allow_paths_for_permissions(&permissions, command_cwd, env_map)    }    #[test]    fn includes_additional_writable_roots() {        let tmp = TempDir::new().expect("tempdir");        let command_cwd = tmp.path().join("workspace");        let extra_root = tmp.path().join("extra");        let _ = fs::create_dir_all(&command_cwd);        let _ = fs::create_dir_all(&extra_root);        let writable_roots = vec![AbsolutePathBuf::try_from(extra_root.as_path()).unwrap()];        let permission_profile = workspace_write_profile(            &writable_roots,            /*exclude_tmpdir_env_var*/ false,            /*exclude_slash_tmp*/ false,        );        let workspace_roots = workspace_roots_for(command_cwd.as_path());        let paths = compute_allow_paths(            &permission_profile,            workspace_roots.as_slice(),            &command_cwd,            &HashMap::new(),        );        assert!(            paths                .allow                .contains(&dunce::canonicalize(&command_cwd).unwrap())        );        assert!(            paths                .allow                .contains(&dunce::canonicalize(&extra_root).unwrap())        );        assert!(paths.deny.is_empty(), "no deny paths expected");    }    #[test]    fn uses_runtime_workspace_roots_for_workspace_root() {        let tmp = TempDir::new().expect("tempdir");        let workspace_root = tmp.path().join("workspace");        let command_cwd = workspace_root.join("subdir");        fs::create_dir_all(&command_cwd).expect("create command cwd");        let permission_profile = workspace_write_profile(            &[],            /*exclude_tmpdir_env_var*/ true,            /*exclude_slash_tmp*/ true,        );        let workspace_roots = workspace_roots_for(workspace_root.as_path());        let paths = compute_allow_paths(            &permission_profile,            workspace_roots.as_slice(),            &command_cwd,            &HashMap::new(),        );        assert!(            paths                .allow                .contains(&dunce::canonicalize(&workspace_root).unwrap())        );        assert!(            !paths                .allow                .contains(&dunce::canonicalize(&command_cwd).unwrap())        );        assert!(paths.deny.is_empty(), "no deny paths expected");    }    #[test]    fn excludes_tmp_env_vars_when_requested() {        let tmp = TempDir::new().expect("tempdir");        let command_cwd = tmp.path().join("workspace");        let temp_dir = tmp.path().join("temp");        let _ = fs::create_dir_all(&command_cwd);        let _ = fs::create_dir_all(&temp_dir);        let permission_profile = workspace_write_profile(            &[],            /*exclude_tmpdir_env_var*/ true,            /*exclude_slash_tmp*/ false,        );        let mut env_map = HashMap::new();        env_map.insert("TEMP".into(), temp_dir.to_string_lossy().to_string());        env_map.insert("TMP".into(), temp_dir.to_string_lossy().to_string());        let workspace_roots = workspace_roots_for(command_cwd.as_path());        let paths = compute_allow_paths(            &permission_profile,            workspace_roots.as_slice(),            &command_cwd,            &env_map,        );        assert!(            paths                .allow                .contains(&dunce::canonicalize(&command_cwd).unwrap())        );        assert!(            !paths                .allow                .contains(&dunce::canonicalize(&temp_dir).unwrap())        );        assert!(paths.deny.is_empty(), "no deny paths expected");    }    #[test]    fn includes_tmp_env_vars_when_requested() {        let tmp = TempDir::new().expect("tempdir");        let command_cwd = tmp.path().join("workspace");        let temp_dir = tmp.path().join("temp");        let _ = fs::create_dir_all(&command_cwd);        let _ = fs::create_dir_all(&temp_dir);        let permission_profile = workspace_write_profile(            &[],            /*exclude_tmpdir_env_var*/ false,            /*exclude_slash_tmp*/ false,        );        let mut env_map = HashMap::new();        env_map.insert("TEMP".into(), temp_dir.to_string_lossy().to_string());        env_map.insert("TMP".into(), temp_dir.to_string_lossy().to_string());        let workspace_roots = workspace_roots_for(command_cwd.as_path());        let paths = compute_allow_paths(            &permission_profile,            workspace_roots.as_slice(),            &command_cwd,            &env_map,        );        let expected_allow: HashSet<PathBuf> = [            dunce::canonicalize(&command_cwd).unwrap(),            dunce::canonicalize(&temp_dir).unwrap(),        ]        .into_iter()        .collect();        assert_eq!(expected_allow, paths.allow);        assert!(paths.deny.is_empty(), "no deny paths expected");    }    #[test]    fn ignores_unix_slash_tmp_for_windows_allow_roots() {        let tmp = TempDir::new().expect("tempdir");        let command_cwd = tmp.path().join("workspace");        let _ = fs::create_dir_all(&command_cwd);        let permission_profile = workspace_write_profile(            &[],            /*exclude_tmpdir_env_var*/ true,            /*exclude_slash_tmp*/ false,        );        let workspace_roots = workspace_roots_for(command_cwd.as_path());        let paths = compute_allow_paths(            &permission_profile,            workspace_roots.as_slice(),            &command_cwd,            &HashMap::new(),        );        let expected_allow: HashSet<PathBuf> = [dunce::canonicalize(&command_cwd).unwrap()]            .into_iter()            .collect();        assert_eq!(expected_allow, paths.allow);        assert!(paths.deny.is_empty(), "no deny paths expected");    }    #[test]    fn denies_git_dir_inside_writable_root() {        let tmp = TempDir::new().expect("tempdir");        let command_cwd = tmp.path().join("workspace");        let git_dir = command_cwd.join(".git");        let _ = fs::create_dir_all(&git_dir);        let permission_profile = workspace_write_profile(            &[],            /*exclude_tmpdir_env_var*/ true,            /*exclude_slash_tmp*/ false,        );        let workspace_roots = workspace_roots_for(command_cwd.as_path());        let paths = compute_allow_paths(            &permission_profile,            workspace_roots.as_slice(),            &command_cwd,            &HashMap::new(),        );        let expected_allow: HashSet<PathBuf> = [dunce::canonicalize(&command_cwd).unwrap()]            .into_iter()            .collect();        let expected_deny: HashSet<PathBuf> = [dunce::canonicalize(&git_dir).unwrap()]            .into_iter()            .collect();        assert_eq!(expected_allow, paths.allow);        assert_eq!(expected_deny, paths.deny);    }    #[test]    fn denies_git_file_inside_writable_root() {        let tmp = TempDir::new().expect("tempdir");        let command_cwd = tmp.path().join("workspace");        let git_file = command_cwd.join(".git");        let _ = fs::create_dir_all(&command_cwd);        let _ = fs::write(&git_file, "gitdir: .git/worktrees/example");        let permission_profile = workspace_write_profile(            &[],            /*exclude_tmpdir_env_var*/ true,            /*exclude_slash_tmp*/ false,        );        let workspace_roots = workspace_roots_for(command_cwd.as_path());        let paths = compute_allow_paths(            &permission_profile,            workspace_roots.as_slice(),            &command_cwd,            &HashMap::new(),        );        let expected_allow: HashSet<PathBuf> = [dunce::canonicalize(&command_cwd).unwrap()]            .into_iter()            .collect();        let expected_deny: HashSet<PathBuf> = [dunce::canonicalize(&git_file).unwrap()]            .into_iter()            .collect();        assert_eq!(expected_allow, paths.allow);        assert_eq!(expected_deny, paths.deny);    }    #[test]    fn denies_codex_and_agents_inside_writable_root() {        let tmp = TempDir::new().expect("tempdir");        let command_cwd = tmp.path().join("workspace");        let codex_dir = command_cwd.join(".codex");        let agents_dir = command_cwd.join(".agents");        let _ = fs::create_dir_all(&codex_dir);        let _ = fs::create_dir_all(&agents_dir);        let permission_profile = workspace_write_profile(            &[],            /*exclude_tmpdir_env_var*/ true,            /*exclude_slash_tmp*/ false,        );        let workspace_roots = workspace_roots_for(command_cwd.as_path());        let paths = compute_allow_paths(            &permission_profile,            workspace_roots.as_slice(),            &command_cwd,            &HashMap::new(),        );        let expected_allow: HashSet<PathBuf> = [dunce::canonicalize(&command_cwd).unwrap()]            .into_iter()            .collect();        let expected_deny: HashSet<PathBuf> = [            dunce::canonicalize(&codex_dir).unwrap(),            dunce::canonicalize(&agents_dir).unwrap(),        ]        .into_iter()        .collect();        assert_eq!(expected_allow, paths.allow);        assert_eq!(expected_deny, paths.deny);    }    #[test]    fn skips_protected_subdirs_when_missing() {        let tmp = TempDir::new().expect("tempdir");        let command_cwd = tmp.path().join("workspace");        let _ = fs::create_dir_all(&command_cwd);        let permission_profile = workspace_write_profile(            &[],            /*exclude_tmpdir_env_var*/ true,            /*exclude_slash_tmp*/ false,        );        let workspace_roots = workspace_roots_for(command_cwd.as_path());        let paths = compute_allow_paths(            &permission_profile,            workspace_roots.as_slice(),            &command_cwd,            &HashMap::new(),        );        assert_eq!(paths.allow.len(), 1);        assert!(            paths.deny.is_empty(),            "no deny when protected dirs are absent"        );    }}