mirror of
https://github.com/openai/codex.git
synced 2026-10-24 18:18:41 +00:00
sandboxing: materialize cwd-relative permission globs
This commit is contained in:
@@ -193,8 +193,7 @@ pub(super) async fn apply_granted_turn_permissions(
|
||||
);
|
||||
let permissions_preapproved = match (effective_permissions.as_ref(), granted_permissions) {
|
||||
(Some(effective_permissions), Some(granted_permissions)) => {
|
||||
intersect_permission_profiles(effective_permissions.clone(), granted_permissions, cwd)
|
||||
== *effective_permissions
|
||||
permissions_are_preapproved(effective_permissions, granted_permissions, cwd)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
@@ -213,17 +212,37 @@ pub(super) async fn apply_granted_turn_permissions(
|
||||
}
|
||||
}
|
||||
|
||||
fn permissions_are_preapproved(
|
||||
effective_permissions: &PermissionProfile,
|
||||
granted_permissions: PermissionProfile,
|
||||
cwd: &Path,
|
||||
) -> bool {
|
||||
let materialized_effective_permissions = intersect_permission_profiles(
|
||||
effective_permissions.clone(),
|
||||
effective_permissions.clone(),
|
||||
cwd,
|
||||
);
|
||||
intersect_permission_profiles(effective_permissions.clone(), granted_permissions, cwd)
|
||||
== materialized_effective_permissions
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EffectiveAdditionalPermissions;
|
||||
use super::implicit_granted_permissions;
|
||||
use super::normalize_and_validate_additional_permissions;
|
||||
use super::permissions_are_preapproved;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_sandboxing::policy_transforms::intersect_permission_profiles;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
@@ -326,4 +345,40 @@ mod tests {
|
||||
|
||||
assert_eq!(implicit_permissions, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relative_deny_glob_grants_remain_preapproved_after_materialization() {
|
||||
let cwd = tempdir().expect("tempdir");
|
||||
let requested_permissions = PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
entries: vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let stored_grant = intersect_permission_profiles(
|
||||
requested_permissions.clone(),
|
||||
requested_permissions.clone(),
|
||||
cwd.path(),
|
||||
);
|
||||
|
||||
assert!(permissions_are_preapproved(
|
||||
&requested_permissions,
|
||||
stored_grant,
|
||||
cwd.path(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,9 +383,15 @@ fn materialize_cwd_dependent_entry(
|
||||
access: entry.access,
|
||||
})
|
||||
.unwrap_or_else(|| entry.clone()),
|
||||
FileSystemPath::Path { .. }
|
||||
| FileSystemPath::GlobPattern { .. }
|
||||
| FileSystemPath::Special { .. } => entry.clone(),
|
||||
FileSystemPath::GlobPattern { pattern } => FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: AbsolutePathBuf::resolve_path_against_base(pattern, cwd)
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
},
|
||||
access: entry.access,
|
||||
},
|
||||
FileSystemPath::Path { .. } | FileSystemPath::Special { .. } => entry.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -509,6 +509,75 @@ fn intersect_permission_profiles_rejects_concrete_grants_matched_by_requested_de
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_permission_profiles_materializes_relative_deny_globs_for_reuse() {
|
||||
let temp_dir = TempDir::new().expect("create temp dir");
|
||||
let request_cwd = AbsolutePathBuf::from_absolute_path(temp_dir.path().join("request-cwd"))
|
||||
.expect("absolute request cwd");
|
||||
let later_cwd = AbsolutePathBuf::from_absolute_path(temp_dir.path().join("later-cwd"))
|
||||
.expect("absolute later cwd");
|
||||
let cwd_write = FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
};
|
||||
let deny_env_files = FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/*.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
};
|
||||
let permissions = PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
entries: vec![cwd_write, deny_env_files],
|
||||
glob_scan_max_depth: std::num::NonZeroUsize::new(2),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let intersected =
|
||||
intersect_permission_profiles(permissions.clone(), permissions, request_cwd.as_path());
|
||||
|
||||
assert_eq!(
|
||||
intersected,
|
||||
PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
entries: vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: request_cwd.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: request_cwd.join("**/*.env").to_string_lossy().into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: std::num::NonZeroUsize::new(2),
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
intersect_permission_profiles(
|
||||
PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions::from_read_write_roots(
|
||||
/*read*/ None,
|
||||
Some(vec![later_cwd.join("token.env")]),
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
intersected,
|
||||
later_cwd.as_path(),
|
||||
),
|
||||
PermissionProfile::default()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersect_permission_profiles_drops_broader_cwd_grant_for_requested_child_path() {
|
||||
let temp_dir = TempDir::new().expect("create temp dir");
|
||||
@@ -567,7 +636,7 @@ fn intersect_permission_profiles_uses_granted_bounded_glob_scan_depth() {
|
||||
};
|
||||
let granted = PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
entries: vec![root_write.clone(), deny_env_files.clone()],
|
||||
entries: vec![root_write.clone(), deny_env_files],
|
||||
glob_scan_max_depth: std::num::NonZeroUsize::new(4),
|
||||
}),
|
||||
..Default::default()
|
||||
@@ -577,7 +646,20 @@ fn intersect_permission_profiles_uses_granted_bounded_glob_scan_depth() {
|
||||
intersect_permission_profiles(requested, granted, cwd.as_path()),
|
||||
PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
entries: vec![root_write, deny_env_files],
|
||||
entries: vec![
|
||||
root_write,
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: AbsolutePathBuf::resolve_path_against_base(
|
||||
"**/*.env",
|
||||
cwd.as_path()
|
||||
)
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: std::num::NonZeroUsize::new(4),
|
||||
}),
|
||||
..Default::default()
|
||||
@@ -609,7 +691,7 @@ fn intersect_permission_profiles_uses_granted_unbounded_glob_scan_depth() {
|
||||
};
|
||||
let granted = PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
entries: vec![root_write.clone(), deny_env_files.clone()],
|
||||
entries: vec![root_write.clone(), deny_env_files],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
..Default::default()
|
||||
@@ -619,7 +701,20 @@ fn intersect_permission_profiles_uses_granted_unbounded_glob_scan_depth() {
|
||||
intersect_permission_profiles(requested, granted, cwd.as_path()),
|
||||
PermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
entries: vec![root_write, deny_env_files],
|
||||
entries: vec![
|
||||
root_write,
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: AbsolutePathBuf::resolve_path_against_base(
|
||||
"**/*.env",
|
||||
cwd.as_path()
|
||||
)
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
},
|
||||
],
|
||||
glob_scan_max_depth: None,
|
||||
}),
|
||||
..Default::default()
|
||||
|
||||
Reference in New Issue
Block a user