mirror of
https://github.com/openai/codex.git
synced 2026-11-10 08:43:33 +00:00
Preserve managed deny-read rules across permission updates (#40004)
## Why Runtime permission updates must not weaken managed filesystem `deny_read` requirements. ## What changed - Retain managed deny-read rules separately and merge them into updated permission profiles. - Reject permission profiles and legacy sandbox policies that conflict with a managed denied path. - Apply the same constraint when `command/exec` handles a request-specific sandbox policy. ## Testing - Cover thread permission updates with managed deny-read requirements. - Cover `command/exec` enforcement for managed and user-defined denies, including conflicting policy and profile overrides. GitOrigin-RevId: 5e387b9c1bf1650a21753a74a3338bd33df7d0ce
This commit is contained in:
1 parent
0f1a30b5c2
commit
ad9e8097fd
4 files changed
+290
-33
No files matched your search
@@ -114,8 +114,10 @@ use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::openai_models::ModelMessages;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::permissions::ReadDenyMatcher;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::MultiAgentVersion;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
@@ -297,6 +299,8 @@ pub struct Permissions {
|
||||
/// Constrained permission profile plus its selected profile identity, if
|
||||
/// the profile came from a built-in or named config profile.
|
||||
permission_profile_state: PermissionProfileState,
|
||||
/// Managed deny-read rules retained independently from user-defined denies.
|
||||
managed_deny_read_policy: Option<Arc<FileSystemSandboxPolicy>>,
|
||||
/// Thread-scoped runtime workspace roots. Symbolic `:workspace_roots`
|
||||
/// entries in the permission profile are materialized against these roots.
|
||||
workspace_roots: Vec<AbsolutePathBuf>,
|
||||
@@ -332,6 +336,7 @@ impl Permissions {
|
||||
permission_profile_state: PermissionProfileState::from_constrained_legacy(
|
||||
permission_profile,
|
||||
)?,
|
||||
managed_deny_read_policy: None,
|
||||
workspace_roots: Vec::new(),
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
@@ -388,8 +393,10 @@ impl Permissions {
|
||||
&self,
|
||||
permission_profile: &PermissionProfile,
|
||||
) -> ConstraintResult<()> {
|
||||
let permission_profile =
|
||||
self.permission_profile_preserving_managed_denied_reads(permission_profile.clone());
|
||||
self.permission_profile_state
|
||||
.can_set_legacy_permission_profile(permission_profile)
|
||||
.can_set_legacy_permission_profile(&permission_profile)
|
||||
}
|
||||
|
||||
pub fn set_workspace_roots(&mut self, workspace_roots: Vec<AbsolutePathBuf>) {
|
||||
@@ -455,8 +462,7 @@ impl Permissions {
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
self.permission_profile_state
|
||||
.can_set_legacy_permission_profile(&permission_profile)
|
||||
self.can_set_permission_profile(&permission_profile)
|
||||
}
|
||||
|
||||
/// Set permissions from a legacy sandbox policy and keep every permission
|
||||
@@ -496,9 +502,7 @@ impl Permissions {
|
||||
],
|
||||
};
|
||||
|
||||
self.permission_profile_state
|
||||
.set_legacy_permission_profile(permission_profile)?;
|
||||
Ok(())
|
||||
self.set_permission_profile(permission_profile)
|
||||
}
|
||||
|
||||
/// Set permissions from the canonical profile.
|
||||
@@ -506,9 +510,34 @@ impl Permissions {
|
||||
&mut self,
|
||||
permission_profile: PermissionProfile,
|
||||
) -> ConstraintResult<()> {
|
||||
let permission_profile =
|
||||
self.permission_profile_preserving_managed_denied_reads(permission_profile);
|
||||
self.permission_profile_state
|
||||
.set_legacy_permission_profile(permission_profile)
|
||||
}
|
||||
|
||||
fn permission_profile_preserving_managed_denied_reads(
|
||||
&self,
|
||||
permission_profile: PermissionProfile,
|
||||
) -> PermissionProfile {
|
||||
let Some(managed_deny_read_policy) = self.managed_deny_read_policy.as_ref() else {
|
||||
return permission_profile;
|
||||
};
|
||||
if matches!(
|
||||
sandbox_mode_requirement_for_permission_profile(&permission_profile),
|
||||
SandboxModeRequirement::DangerFullAccess | SandboxModeRequirement::ExternalSandbox
|
||||
) {
|
||||
return permission_profile;
|
||||
}
|
||||
let enforcement = permission_profile.enforcement();
|
||||
let (mut file_system_policy, network_policy) = permission_profile.to_runtime_permissions();
|
||||
file_system_policy.preserve_deny_read_restrictions_from(managed_deny_read_policy);
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
enforcement,
|
||||
&file_system_policy,
|
||||
network_policy,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// A profile override only inherits the selected profile's proxy/allowlist config
|
||||
@@ -3985,15 +4014,17 @@ impl Config {
|
||||
effective_file_system_sandbox_policy
|
||||
.preserve_deny_read_restrictions_from(&file_system_sandbox_policy);
|
||||
}
|
||||
if let Some(Sourced {
|
||||
value: filesystem_requirements,
|
||||
..
|
||||
}) = filesystem_requirements.as_ref()
|
||||
{
|
||||
apply_managed_filesystem_constraints(
|
||||
&mut effective_file_system_sandbox_policy,
|
||||
filesystem_requirements,
|
||||
);
|
||||
let managed_deny_read_policy = filesystem_requirements
|
||||
.as_ref()
|
||||
.filter(|Sourced { value, .. }| !value.deny_read.is_empty())
|
||||
.map(|Sourced { value, .. }| {
|
||||
let mut policy = FileSystemSandboxPolicy::restricted(Vec::new());
|
||||
apply_managed_filesystem_constraints(&mut policy, value);
|
||||
Arc::new(policy)
|
||||
});
|
||||
if let Some(managed_deny_read_policy) = managed_deny_read_policy.as_ref() {
|
||||
effective_file_system_sandbox_policy
|
||||
.preserve_deny_read_restrictions_from(managed_deny_read_policy);
|
||||
}
|
||||
let effective_file_system_sandbox_policy = effective_file_system_sandbox_policy
|
||||
.with_additional_readable_roots(resolved_cwd.as_path(), &helper_readable_roots);
|
||||
@@ -4006,6 +4037,58 @@ impl Config {
|
||||
.value
|
||||
.set(effective_permission_profile)
|
||||
.map_err(std::io::Error::from)?;
|
||||
if let Some(Sourced {
|
||||
source: requirement_source,
|
||||
..
|
||||
}) = filesystem_requirements.as_ref()
|
||||
&& let Some(managed_file_system_policy) = managed_deny_read_policy.as_ref()
|
||||
{
|
||||
let _initial_matcher =
|
||||
ReadDenyMatcher::try_new(managed_file_system_policy, resolved_cwd.as_path())
|
||||
.map_err(std::io::Error::other)?;
|
||||
let managed_file_system_policy = Arc::clone(managed_file_system_policy);
|
||||
let permission_cwd = resolved_cwd.clone();
|
||||
let requirement_source = requirement_source.clone();
|
||||
constrained_permission_profile
|
||||
.value
|
||||
.add_validator(move |permission_profile| {
|
||||
let managed_deny_matcher =
|
||||
ReadDenyMatcher::new(&managed_file_system_policy, permission_cwd.as_path());
|
||||
let file_system_policy = permission_profile.file_system_sandbox_policy();
|
||||
let missing_required_deny = managed_file_system_policy
|
||||
.entries
|
||||
.iter()
|
||||
.any(|entry| !file_system_policy.entries.contains(entry));
|
||||
let violating_root = file_system_policy
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|entry| entry.access.can_read())
|
||||
.find_map(|entry| {
|
||||
let FileSystemPath::Path { path } = &entry.path else {
|
||||
return None;
|
||||
};
|
||||
let path = path.to_abs_path().ok()?;
|
||||
managed_deny_matcher
|
||||
.as_ref()
|
||||
.is_some_and(|matcher| matcher.is_read_denied(path.as_path()))
|
||||
.then_some(path)
|
||||
});
|
||||
if missing_required_deny || violating_root.is_some() {
|
||||
return Err(ConstraintError::InvalidValue {
|
||||
field_name: "permissions.filesystem",
|
||||
candidate: violating_root
|
||||
.map_or_else(|| "missing managed deny".to_string(), |path| {
|
||||
path.to_string_lossy().into_owned()
|
||||
}),
|
||||
allowed: "all managed deny_read restrictions".to_string(),
|
||||
requirement_source: requirement_source.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(std::io::Error::from)?;
|
||||
}
|
||||
let permission_profile_state = PermissionProfileState::from_constrained_active_profile(
|
||||
constrained_permission_profile.value,
|
||||
active_permission_profile,
|
||||
@@ -4031,6 +4114,7 @@ impl Config {
|
||||
permissions: Permissions {
|
||||
approval_policy: constrained_approval_policy.value,
|
||||
permission_profile_state,
|
||||
managed_deny_read_policy,
|
||||
workspace_roots,
|
||||
network,
|
||||
allow_login_shell,
|
||||
|
||||
Reference in new issue
Block a user