Codex Handbook
windows-sandbox-rs/src/resolved_permissions.rs 478 lines
use anyhow::Result;use codex_protocol::models::PermissionProfile;use codex_protocol::permissions::FileSystemPath;use codex_protocol::permissions::FileSystemSandboxEntry;use codex_protocol::permissions::FileSystemSandboxKind;use codex_protocol::permissions::FileSystemSandboxPolicy;use codex_protocol::permissions::NetworkSandboxPolicy;use codex_utils_absolute_path::AbsolutePathBuf;use std::collections::HashMap;use std::path::Path;use std::path::PathBuf;/// Windows-local view of the runtime permission profile.////// Most Windows sandbox code needs resolved runtime permissions plus a few/// Windows-specific path conventions, not the user/config-facing/// `PermissionProfile` enum itself.#[derive(Debug, Clone, PartialEq, Eq)]pub struct ResolvedWindowsSandboxPermissions {    file_system: FileSystemSandboxPolicy,    network: NetworkSandboxPolicy,}#[derive(Debug, Clone, PartialEq, Eq)]pub(crate) struct WindowsWritableRoot {    pub(crate) root: PathBuf,    pub(crate) read_only_subpaths: Vec<PathBuf>,}/// Restricted-token family needed to enforce a Windows permission profile.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum WindowsSandboxTokenMode {    ReadOnlyCapability,    WritableRootsCapability,}/// Chooses the restricted-token family needed for a managed permission profile.pub fn token_mode_for_permission_profile(    permission_profile: &PermissionProfile,    workspace_roots: &[AbsolutePathBuf],    cwd: &Path,    env_map: &HashMap<String, String>,) -> Result<WindowsSandboxTokenMode> {    let permissions =        ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots(            permission_profile,            workspace_roots,        )?;    if permissions.file_system.has_full_disk_write_access() {        anyhow::bail!(            "permission profile requests full-disk filesystem writes, which cannot be enforced by the Windows sandbox"        );    }    if permissions.writable_roots_for_cwd(cwd, env_map).is_empty() {        Ok(WindowsSandboxTokenMode::ReadOnlyCapability)    } else {        Ok(WindowsSandboxTokenMode::WritableRootsCapability)    }}impl ResolvedWindowsSandboxPermissions {    pub fn try_from_permission_profile(permission_profile: &PermissionProfile) -> Result<Self> {        if !matches!(permission_profile, PermissionProfile::Managed { .. }) {            anyhow::bail!(                "only managed permission profiles can be enforced by the Windows sandbox"            );        }        let (file_system, network) = permission_profile.to_runtime_permissions();        if !matches!(file_system.kind, FileSystemSandboxKind::Restricted) {            anyhow::bail!(                "only restricted managed filesystem permissions can be enforced by the Windows sandbox"            );        }        Ok(Self {            file_system,            network,        })    }    /// Resolves a managed permission profile and binds symbolic `:workspace_roots`    /// entries to the workspace roots supplied by the caller.    pub fn try_from_permission_profile_for_workspace_roots(        permission_profile: &PermissionProfile,        workspace_roots: &[AbsolutePathBuf],    ) -> Result<Self> {        let mut permissions = Self::try_from_permission_profile(permission_profile)?;        permissions.file_system = permissions            .file_system            .materialize_project_roots_with_workspace_roots(workspace_roots);        Ok(permissions)    }    pub(crate) fn should_apply_network_block(&self) -> bool {        !self.network.is_enabled()    }    pub(crate) fn network_policy(&self) -> NetworkSandboxPolicy {        self.network    }    pub(crate) fn is_enforceable_by_windows_sandbox(&self) -> bool {        matches!(self.file_system.kind, FileSystemSandboxKind::Restricted)    }    pub(crate) fn has_full_disk_read_access(&self) -> bool {        self.file_system.has_full_disk_read_access()    }    pub(crate) fn include_platform_defaults(&self) -> bool {        self.file_system.include_platform_defaults()    }    pub(crate) fn readable_roots_for_cwd(&self, cwd: &Path) -> Vec<PathBuf> {        self.file_system            .get_readable_roots_with_cwd(cwd)            .into_iter()            .map(AbsolutePathBuf::into_path_buf)            .collect()    }    pub(crate) fn uses_write_capabilities_for_cwd(        &self,        cwd: &Path,        env_map: &HashMap<String, String>,    ) -> bool {        !self.writable_roots_for_cwd(cwd, env_map).is_empty()    }    pub(crate) fn writable_roots_for_cwd(        &self,        cwd: &Path,        env_map: &HashMap<String, String>,    ) -> Vec<WindowsWritableRoot> {        let mut file_system = self.file_system.clone();        file_system            .entries            .retain(|FileSystemSandboxEntry { path, .. }| {                !matches!(                    path,                    FileSystemPath::Special {                        value: codex_protocol::permissions::FileSystemSpecialPath::Tmpdir                            | codex_protocol::permissions::FileSystemSpecialPath::SlashTmp,                    }                )            });        let mut roots = file_system            .get_writable_roots_with_cwd(cwd)            .into_iter()            .map(|root| WindowsWritableRoot {                root: root.root.into_path_buf(),                read_only_subpaths: root                    .read_only_subpaths                    .into_iter()                    .map(AbsolutePathBuf::into_path_buf)                    .collect(),            })            .collect::<Vec<_>>();        if self.has_writable_tmpdir_entry() {            roots.extend(windows_temp_env_roots(env_map).into_iter().map(|root| {                WindowsWritableRoot {                    root,                    read_only_subpaths: Vec::new(),                }            }));        }        roots    }    fn has_writable_tmpdir_entry(&self) -> bool {        self.file_system            .entries            .iter()            .any(|FileSystemSandboxEntry { path, access }| {                matches!(                    path,                    FileSystemPath::Special {                        value: codex_protocol::permissions::FileSystemSpecialPath::Tmpdir,                    }                ) && access.can_write()            })    }}fn windows_temp_env_roots(env_map: &HashMap<String, String>) -> Vec<PathBuf> {    ["TEMP", "TMP"]        .into_iter()        .filter_map(|key| {            env_map                .get(key)                .map(|value| PathBuf::from(value.as_str()))                .or_else(|| std::env::var_os(key).map(PathBuf::from))        })        .filter(|path| path.is_absolute())        .collect()}#[cfg(test)]mod tests {    use super::*;    use codex_protocol::models::ManagedFileSystemPermissions;    use codex_protocol::permissions::FileSystemAccessMode;    use codex_protocol::permissions::FileSystemSandboxEntry;    use codex_protocol::permissions::FileSystemSpecialPath;    use codex_protocol::permissions::project_roots_glob_pattern;    use pretty_assertions::assert_eq;    use tempfile::TempDir;    fn workspace_roots_for(root: &Path) -> Vec<AbsolutePathBuf> {        vec![AbsolutePathBuf::from_absolute_path(root).expect("absolute workspace root")]    }    #[test]    fn permission_profile_workspace_write_uses_windows_temp_env_vars() {        let tmp = TempDir::new().expect("tempdir");        let cwd = tmp.path().join("workspace");        let temp_dir = tmp.path().join("temp");        std::fs::create_dir_all(&cwd).expect("create cwd");        std::fs::create_dir_all(&temp_dir).expect("create temp dir");        let mut env_map = HashMap::new();        env_map.insert("TEMP".to_string(), temp_dir.to_string_lossy().to_string());        env_map.insert("TMP".to_string(), temp_dir.to_string_lossy().to_string());        let permissions = ResolvedWindowsSandboxPermissions::try_from_permission_profile(            &PermissionProfile::workspace_write(),        )        .expect("managed permission profile");        let roots = permissions            .writable_roots_for_cwd(&cwd, &env_map)            .into_iter()            .map(|root| root.root)            .collect::<std::collections::HashSet<_>>();        let expected_roots = [            temp_dir,            dunce::canonicalize(&cwd).expect("canonicalize cwd"),        ]        .into_iter()        .collect::<std::collections::HashSet<_>>();        assert_eq!(expected_roots, roots);    }    #[test]    fn permission_profile_workspace_root_uses_runtime_workspace_roots() {        let tmp = TempDir::new().expect("tempdir");        let workspace_root = tmp.path().join("workspace");        let command_cwd = workspace_root.join("subdir");        std::fs::create_dir_all(&command_cwd).expect("create command cwd");        let permission_profile = PermissionProfile::Managed {            file_system: ManagedFileSystemPermissions::Restricted {                entries: vec![FileSystemSandboxEntry {                    path: FileSystemPath::Special {                        value: FileSystemSpecialPath::project_roots(/*subpath*/ None),                    },                    access: FileSystemAccessMode::Write,                }],                glob_scan_max_depth: None,            },            network: NetworkSandboxPolicy::Restricted,        };        let workspace_roots = workspace_roots_for(workspace_root.as_path());        let permissions =            ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots(                &permission_profile,                workspace_roots.as_slice(),            )            .expect("managed permission profile");        let roots = permissions            .writable_roots_for_cwd(&command_cwd, &HashMap::new())            .into_iter()            .map(|root| root.root)            .collect::<Vec<_>>();        assert_eq!(            roots,            vec![dunce::canonicalize(&workspace_root).expect("canonical workspace root")]        );    }    #[test]    fn permission_profile_workspace_roots_expand_all_runtime_workspace_roots() {        let tmp = TempDir::new().expect("tempdir");        let first = AbsolutePathBuf::from_absolute_path(tmp.path().join("first"))            .expect("absolute first root");        let second = AbsolutePathBuf::from_absolute_path(tmp.path().join("second"))            .expect("absolute second root");        let permission_profile = PermissionProfile::Managed {            file_system: ManagedFileSystemPermissions::Restricted {                entries: vec![                    FileSystemSandboxEntry {                        path: FileSystemPath::Special {                            value: FileSystemSpecialPath::project_roots(/*subpath*/ None),                        },                        access: FileSystemAccessMode::Write,                    },                    FileSystemSandboxEntry {                        path: FileSystemPath::Special {                            value: FileSystemSpecialPath::project_roots(Some(".git".into())),                        },                        access: FileSystemAccessMode::Deny,                    },                    FileSystemSandboxEntry {                        path: FileSystemPath::GlobPattern {                            pattern: project_roots_glob_pattern(Path::new("**/*.env")),                        },                        access: FileSystemAccessMode::Deny,                    },                ],                glob_scan_max_depth: None,            },            network: NetworkSandboxPolicy::Restricted,        };        let permissions =            ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots(                &permission_profile,                &[first.clone(), second.clone()],            )            .expect("managed permission profile");        assert_eq!(            permissions.file_system,            FileSystemSandboxPolicy::restricted(vec![                FileSystemSandboxEntry {                    path: FileSystemPath::Path {                        path: first.clone(),                    },                    access: FileSystemAccessMode::Write,                },                FileSystemSandboxEntry {                    path: FileSystemPath::Path {                        path: second.clone(),                    },                    access: FileSystemAccessMode::Write,                },                FileSystemSandboxEntry {                    path: FileSystemPath::Path {                        path: first.join(".git"),                    },                    access: FileSystemAccessMode::Deny,                },                FileSystemSandboxEntry {                    path: FileSystemPath::Path {                        path: second.join(".git"),                    },                    access: FileSystemAccessMode::Deny,                },                FileSystemSandboxEntry {                    path: FileSystemPath::GlobPattern {                        pattern: AbsolutePathBuf::resolve_path_against_base(                            "**/*.env",                            first.as_path(),                        )                        .to_string_lossy()                        .into_owned(),                    },                    access: FileSystemAccessMode::Deny,                },                FileSystemSandboxEntry {                    path: FileSystemPath::GlobPattern {                        pattern: AbsolutePathBuf::resolve_path_against_base(                            "**/*.env",                            second.as_path(),                        )                        .to_string_lossy()                        .into_owned(),                    },                    access: FileSystemAccessMode::Deny,                },            ])        );    }    #[test]    fn token_mode_for_profile_without_writable_roots_uses_readonly_capability() {        let tmp = TempDir::new().expect("tempdir");        let cwd = tmp.path().join("workspace");        std::fs::create_dir_all(&cwd).expect("create cwd");        let workspace_roots = workspace_roots_for(cwd.as_path());        let token_mode = token_mode_for_permission_profile(            &PermissionProfile::read_only(),            workspace_roots.as_slice(),            &cwd,            &HashMap::new(),        )        .expect("token mode");        assert_eq!(WindowsSandboxTokenMode::ReadOnlyCapability, token_mode);    }    #[test]    fn token_mode_for_profile_with_writable_roots_uses_write_capabilities() {        let tmp = TempDir::new().expect("tempdir");        let cwd = tmp.path().join("workspace");        std::fs::create_dir_all(&cwd).expect("create cwd");        let workspace_roots = workspace_roots_for(cwd.as_path());        let token_mode = token_mode_for_permission_profile(            &PermissionProfile::workspace_write(),            workspace_roots.as_slice(),            &cwd,            &HashMap::new(),        )        .expect("token mode");        assert_eq!(WindowsSandboxTokenMode::WritableRootsCapability, token_mode);    }    #[test]    fn permission_profile_rejects_disabled_profiles() {        let err = ResolvedWindowsSandboxPermissions::try_from_permission_profile(            &PermissionProfile::Disabled,        )        .expect_err("disabled profile should not resolve for sandbox enforcement");        assert!(            err.to_string()                .contains("only managed permission profiles can be enforced")        );    }    #[test]    fn permission_profile_rejects_unrestricted_managed_filesystem() {        let permission_profile = PermissionProfile::Managed {            file_system: ManagedFileSystemPermissions::Unrestricted,            network: NetworkSandboxPolicy::Restricted,        };        let err =            ResolvedWindowsSandboxPermissions::try_from_permission_profile(&permission_profile)                .expect_err("unrestricted profile should not resolve for sandbox enforcement");        assert!(            err.to_string()                .contains("only restricted managed filesystem permissions can be enforced")        );    }    #[test]    fn token_mode_rejects_full_disk_write_entries() {        let tmp = TempDir::new().expect("tempdir");        let cwd = tmp.path().join("workspace");        std::fs::create_dir_all(&cwd).expect("create cwd");        let permission_profile = PermissionProfile::Managed {            file_system: ManagedFileSystemPermissions::Restricted {                entries: vec![FileSystemSandboxEntry {                    path: FileSystemPath::Special {                        value: FileSystemSpecialPath::Root,                    },                    access: FileSystemAccessMode::Write,                }],                glob_scan_max_depth: None,            },            network: NetworkSandboxPolicy::Restricted,        };        let workspace_roots = workspace_roots_for(cwd.as_path());        let err = token_mode_for_permission_profile(            &permission_profile,            workspace_roots.as_slice(),            &cwd,            &HashMap::new(),        )        .expect_err("full disk writes should not resolve to a token mode");        assert!(            err.to_string()                .contains("full-disk filesystem writes, which cannot be enforced")        );    }}