mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
sandboxing: materialize cwd-relative permission globs (#18867)
## Why #18275 anchors session-scoped `:cwd` and `:project_roots` grants to the request cwd before recording them for reuse. Relative deny glob entries need the same treatment. Without anchoring, a stored session permission can keep a pattern such as `**/*.env` relative, then reinterpret that deny against a later turn cwd. That makes the persisted profile depend on the cwd at reuse time instead of the cwd that was reviewed and approved. ## What changed `intersect_permission_profiles` now materializes retained `FileSystemPath::GlobPattern` entries against the request cwd, matching the existing materialization for cwd-sensitive special paths. Materialized accepted grants are now deduplicated before deny retention runs. This keeps the sticky-grant preapproval shape stable when a repeated request is merged with the stored grant and both `:cwd = write` and the materialized absolute cwd write are present. The preapproval check compares against the same materialized form, so a later request for the same cwd-relative deny glob still matches the stored anchored grant instead of re-prompting or rejecting. Tests cover both the storage path and the preapproval path: a session-scoped `:cwd = write` grant with `**/*.env = none` is stored with both the cwd write and deny glob anchored to the original request cwd, cannot be reused from a later cwd, and remains preapproved when re-requested from the original cwd after merging with the stored grant. ## Verification - `cargo test -p codex-sandboxing policy_transforms` - `cargo test -p codex-core --lib relative_deny_glob_grants_remain_preapproved_after_materialization` - `cargo clippy -p codex-sandboxing --tests -- -D clippy::redundant_clone` - `cargo clippy -p codex-core --lib -- -D clippy::redundant_clone` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18867). * #18288 * #18287 * #18286 * #18285 * #18284 * #18283 * #18282 * #18281 * #18280 * #18279 * #18278 * #18277 * #18276 * __->__ #18867
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,38 @@ 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_sandboxing::policy_transforms::merge_permission_profiles;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
@@ -326,4 +346,43 @@ 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(),
|
||||
);
|
||||
let effective_permissions =
|
||||
merge_permission_profiles(Some(&requested_permissions), Some(&stored_grant))
|
||||
.expect("merged permissions");
|
||||
|
||||
assert!(permissions_are_preapproved(
|
||||
&effective_permissions,
|
||||
stored_grant,
|
||||
cwd.path(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user