permissions: constrain requirements as profiles (#19736)

This commit is contained in:
Michael Bolin
2026-04-26 21:49:30 -07:00
committed by GitHub
Unverified
parent 0ccd659b4b
commit 523e4aa8e3
7 changed files with 288 additions and 201 deletions
+94 -65
View File
@@ -1,8 +1,8 @@
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use serde::Deserialize;
use serde::Serialize;
@@ -84,7 +84,7 @@ impl<T> std::ops::DerefMut for ConstrainedWithSource<T> {
pub struct ConfigRequirements {
pub approval_policy: ConstrainedWithSource<AskForApproval>,
pub approvals_reviewer: ConstrainedWithSource<ApprovalsReviewer>,
pub sandbox_policy: ConstrainedWithSource<SandboxPolicy>,
pub permission_profile: ConstrainedWithSource<PermissionProfile>,
pub web_search_mode: ConstrainedWithSource<WebSearchMode>,
pub feature_requirements: Option<Sourced<FeatureRequirementsToml>>,
pub managed_hooks: Option<ConstrainedWithSource<ManagedHooksRequirementsToml>>,
@@ -110,8 +110,8 @@ impl Default for ConfigRequirements {
Constrained::allow_any_from_default(),
/*source*/ None,
),
sandbox_policy: ConstrainedWithSource::new(
Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
permission_profile: ConstrainedWithSource::new(
Constrained::allow_any(PermissionProfile::read_only()),
/*source*/ None,
),
web_search_mode: ConstrainedWithSource::new(
@@ -967,15 +967,8 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
),
};
// TODO(gt): `ConfigRequirementsToml` should let the author specify the
// default `SandboxPolicy`? Should do this for `AskForApproval` too?
//
// Currently, we force ReadOnly as the default policy because two of
// the other variants (WorkspaceWrite, ExternalSandbox) require
// additional parameters. Ultimately, we should expand the config
// format to allow specifying those parameters.
let default_sandbox_policy = SandboxPolicy::new_read_only_policy();
let sandbox_policy = match allowed_sandbox_modes {
let default_permission_profile = PermissionProfile::read_only();
let permission_profile = match allowed_sandbox_modes {
Some(Sourced {
value: modes,
source: requirement_source,
@@ -984,23 +977,15 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
return Err(ConstraintError::InvalidValue {
field_name: "allowed_sandbox_modes",
candidate: format!("{modes:?}"),
allowed: "must include 'read-only' to allow any SandboxPolicy".to_string(),
allowed: "must include 'read-only' to allow any PermissionProfile"
.to_string(),
requirement_source,
});
};
let requirement_source_for_error = requirement_source.clone();
let constrained = Constrained::new(default_sandbox_policy, move |candidate| {
let mode = match candidate {
SandboxPolicy::ReadOnly { .. } => SandboxModeRequirement::ReadOnly,
SandboxPolicy::WorkspaceWrite { .. } => {
SandboxModeRequirement::WorkspaceWrite
}
SandboxPolicy::DangerFullAccess => SandboxModeRequirement::DangerFullAccess,
SandboxPolicy::ExternalSandbox { .. } => {
SandboxModeRequirement::ExternalSandbox
}
};
let constrained = Constrained::new(default_permission_profile, move |candidate| {
let mode = sandbox_mode_requirement_for_permission_profile(candidate);
if modes.contains(&mode) {
Ok(())
} else {
@@ -1014,12 +999,10 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
})?;
ConstrainedWithSource::new(constrained, Some(requirement_source))
}
None => {
ConstrainedWithSource::new(
Constrained::allow_any(default_sandbox_policy),
/*source*/ None,
)
}
None => ConstrainedWithSource::new(
Constrained::allow_any(default_permission_profile),
/*source*/ None,
),
};
let exec_policy = match rules {
Some(Sourced { value, source }) => {
@@ -1145,7 +1128,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
Ok(ConfigRequirements {
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
web_search_mode,
feature_requirements,
managed_hooks,
@@ -1159,6 +1142,29 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
}
}
pub fn sandbox_mode_requirement_for_permission_profile(
permission_profile: &PermissionProfile,
) -> SandboxModeRequirement {
match permission_profile {
PermissionProfile::Disabled => SandboxModeRequirement::DangerFullAccess,
PermissionProfile::External { .. } => SandboxModeRequirement::ExternalSandbox,
PermissionProfile::Managed { .. } => {
let file_system_policy = permission_profile.file_system_sandbox_policy();
if file_system_policy.has_full_disk_write_access() {
SandboxModeRequirement::DangerFullAccess
} else if file_system_policy
.entries
.iter()
.any(|entry| entry.access.can_write())
{
SandboxModeRequirement::WorkspaceWrite
} else {
SandboxModeRequirement::ReadOnly
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1168,6 +1174,7 @@ mod tests {
use codex_execpolicy::Evaluation;
use codex_execpolicy::RuleMatch;
use codex_protocol::protocol::NetworkAccess;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::AbsolutePathBufGuard;
use pretty_assertions::assert_eq;
@@ -1183,6 +1190,10 @@ mod tests {
)?)
}
fn profile_from_sandbox_policy(sandbox_policy: &SandboxPolicy) -> PermissionProfile {
PermissionProfile::from_legacy_sandbox_policy(sandbox_policy)
}
fn with_unknown_source(toml: ConfigRequirementsToml) -> ConfigRequirementsWithSources {
let ConfigRequirementsToml {
allowed_approval_policies,
@@ -1724,8 +1735,10 @@ allowed_approvals_reviewers = ["user"]
);
assert_eq!(
requirements
.sandbox_policy
.can_set(&SandboxPolicy::DangerFullAccess),
.permission_profile
.can_set(&profile_from_sandbox_policy(
&SandboxPolicy::DangerFullAccess,
)),
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: "DangerFullAccess".into(),
@@ -1803,7 +1816,7 @@ allowed_approvals_reviewers = ["user"]
Some(source_location.clone())
);
assert_eq!(
requirements.sandbox_policy.source,
requirements.permission_profile.source,
Some(source_location.clone())
);
assert_eq!(
@@ -1869,8 +1882,10 @@ allowed_approvals_reviewers = ["user"]
);
assert!(
requirements
.sandbox_policy
.can_set(&SandboxPolicy::new_read_only_policy())
.permission_profile
.can_set(&profile_from_sandbox_policy(
&SandboxPolicy::new_read_only_policy()
))
.is_ok()
);
@@ -1952,25 +1967,30 @@ allowed_approvals_reviewers = ["user"]
let root = if cfg!(windows) { "C:\\repo" } else { "/repo" };
assert!(
requirements
.sandbox_policy
.can_set(&SandboxPolicy::new_read_only_policy())
.permission_profile
.can_set(&profile_from_sandbox_policy(
&SandboxPolicy::new_read_only_policy()
))
.is_ok()
);
let workspace_write_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::from_absolute_path(root)?],
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
};
assert!(
requirements
.sandbox_policy
.can_set(&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::from_absolute_path(root)?],
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
})
.permission_profile
.can_set(&profile_from_sandbox_policy(&workspace_write_policy))
.is_ok()
);
assert_eq!(
requirements
.sandbox_policy
.can_set(&SandboxPolicy::DangerFullAccess),
.permission_profile
.can_set(&profile_from_sandbox_policy(
&SandboxPolicy::DangerFullAccess,
)),
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: "DangerFullAccess".into(),
@@ -1980,10 +2000,12 @@ allowed_approvals_reviewers = ["user"]
);
assert_eq!(
requirements
.sandbox_policy
.can_set(&SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Restricted,
}),
.permission_profile
.can_set(&profile_from_sandbox_policy(
&SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Restricted,
}
)),
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: "ExternalSandbox".into(),
@@ -2064,21 +2086,24 @@ allowed_approvals_reviewers = ["user"]
let requirements = ConfigRequirements::try_from(requirements_with_sources)?;
let root = if cfg!(windows) { "C:\\repo" } else { "/repo" };
let workspace_write_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::from_absolute_path(root)?],
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
};
assert!(
requirements
.sandbox_policy
.can_set(&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![AbsolutePathBuf::from_absolute_path(root)?],
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
})
.permission_profile
.can_set(&profile_from_sandbox_policy(&workspace_write_policy))
.is_ok()
);
assert_eq!(
requirements
.sandbox_policy
.can_set(&SandboxPolicy::DangerFullAccess),
.permission_profile
.can_set(&profile_from_sandbox_policy(
&SandboxPolicy::DangerFullAccess,
)),
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: "DangerFullAccess".into(),
@@ -2108,8 +2133,10 @@ allowed_approvals_reviewers = ["user"]
assert_eq!(
requirements
.sandbox_policy
.can_set(&SandboxPolicy::DangerFullAccess),
.permission_profile
.can_set(&profile_from_sandbox_policy(
&SandboxPolicy::DangerFullAccess,
)),
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: "DangerFullAccess".into(),
@@ -2147,8 +2174,10 @@ allowed_approvals_reviewers = ["user"]
assert_eq!(
requirements
.sandbox_policy
.can_set(&SandboxPolicy::new_workspace_write_policy()),
.permission_profile
.can_set(&profile_from_sandbox_policy(
&SandboxPolicy::new_workspace_write_policy(),
)),
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: "WorkspaceWrite".into(),
+7 -4
View File
@@ -47,6 +47,7 @@ use codex_protocol::config_types::Verbosity;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::config_types::WebSearchToolConfig;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
@@ -647,7 +648,7 @@ impl ConfigToml {
profile_sandbox_mode: Option<SandboxMode>,
windows_sandbox_level: WindowsSandboxLevel,
active_project: Option<&ProjectConfig>,
sandbox_policy_constraint: Option<&crate::Constrained<SandboxPolicy>>,
permission_profile_constraint: Option<&crate::Constrained<PermissionProfile>>,
) -> SandboxPolicy {
let sandbox_mode_was_explicit = sandbox_mode_override.is_some()
|| profile_sandbox_mode.is_some()
@@ -707,14 +708,16 @@ impl ConfigToml {
downgrade_workspace_write_if_unsupported(&mut sandbox_policy);
}
if !sandbox_mode_was_explicit
&& let Some(constraint) = sandbox_policy_constraint
&& let Err(err) = constraint.can_set(&sandbox_policy)
&& let Some(constraint) = permission_profile_constraint
&& let Err(err) = constraint.can_set(&PermissionProfile::from_legacy_sandbox_policy(
&sandbox_policy,
))
{
tracing::warn!(
error = %err,
"default sandbox policy is disallowed by requirements; falling back to required default"
);
sandbox_policy = constraint.get().clone();
sandbox_policy = SandboxPolicy::new_read_only_policy();
downgrade_workspace_write_if_unsupported(&mut sandbox_policy);
}
sandbox_policy
+1
View File
@@ -53,6 +53,7 @@ pub use config_requirements::ResidencyRequirement;
pub use config_requirements::SandboxModeRequirement;
pub use config_requirements::Sourced;
pub use config_requirements::WebSearchModeRequirement;
pub use config_requirements::sandbox_mode_requirement_for_permission_profile;
pub use constraint::Constrained;
pub use constraint::ConstraintError;
pub use constraint::ConstraintResult;
+109 -10
View File
@@ -27,8 +27,8 @@ use codex_config::version_for_toml;
use codex_exec_server::LOCAL_FS;
use codex_protocol::config_types::TrustLevel;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
#[cfg(target_os = "macos")]
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
@@ -577,8 +577,8 @@ allowed_sandbox_modes = ["read-only"]
AskForApproval::Never
);
assert_eq!(
*state.requirements().sandbox_policy.get(),
SandboxPolicy::new_read_only_policy()
state.requirements().permission_profile.get(),
&PermissionProfile::read_only()
);
assert!(
state
@@ -590,13 +590,15 @@ allowed_sandbox_modes = ["read-only"]
assert!(
state
.requirements()
.sandbox_policy
.can_set(&SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
})
.permission_profile
.can_set(&PermissionProfile::from_legacy_sandbox_policy(
&SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
network_access: false,
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
},
))
.is_err()
);
@@ -867,6 +869,55 @@ allowed_approval_policies = ["on-request"]
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn system_remote_sandbox_config_keeps_cloud_sandbox_modes() -> anyhow::Result<()> {
let tmp = tempdir()?;
let requirements_file = tmp.path().join("requirements.toml");
tokio::fs::write(
&requirements_file,
r#"
[[remote_sandbox_config]]
hostname_patterns = ["*"]
allowed_sandbox_modes = ["read-only", "workspace-write"]
"#,
)
.await?;
let cloud_source = RequirementSource::CloudRequirements;
let mut config_requirements_toml = ConfigRequirementsWithSources::default();
config_requirements_toml.merge_unset_fields(
cloud_source.clone(),
toml::from_str(
r#"
allowed_sandbox_modes = ["read-only"]
"#,
)?,
);
load_requirements_toml(
LOCAL_FS.as_ref(),
&mut config_requirements_toml,
&AbsolutePathBuf::try_from(requirements_file)?,
)
.await?;
let config_requirements: ConfigRequirements = config_requirements_toml.try_into()?;
assert_eq!(
config_requirements.permission_profile.can_set(
&PermissionProfile::from_legacy_sandbox_policy(
&SandboxPolicy::new_workspace_write_policy()
)
),
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: "WorkspaceWrite".into(),
allowed: "[ReadOnly]".into(),
requirement_source: cloud_source,
})
);
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn load_requirements_toml_resolves_deny_read_against_parent() -> anyhow::Result<()> {
let tmp = tempdir()?;
@@ -1088,6 +1139,54 @@ async fn load_config_layers_includes_cloud_hook_requirements() -> anyhow::Result
Ok(())
}
#[tokio::test]
async fn load_config_layers_applies_matching_remote_sandbox_config() -> anyhow::Result<()> {
let tmp = tempdir()?;
let codex_home = tmp.path().join("home");
tokio::fs::create_dir_all(&codex_home).await?;
let cwd = AbsolutePathBuf::from_absolute_path(tmp.path())?;
let requirements: ConfigRequirementsToml = toml::from_str(
r#"
allowed_sandbox_modes = ["read-only"]
[[remote_sandbox_config]]
hostname_patterns = ["*"]
allowed_sandbox_modes = ["read-only", "workspace-write"]
"#,
)?;
let cloud_requirements = CloudRequirementsLoader::new(async move { Ok(Some(requirements)) });
let layers = load_config_layers_state(
LOCAL_FS.as_ref(),
&codex_home,
Some(cwd),
&[] as &[(String, TomlValue)],
LoaderOverrides::default(),
cloud_requirements,
&codex_config::NoopThreadConfigLoader,
)
.await?;
assert_eq!(
layers.requirements_toml().allowed_sandbox_modes,
Some(vec![
codex_config::SandboxModeRequirement::ReadOnly,
codex_config::SandboxModeRequirement::WorkspaceWrite,
])
);
assert!(
layers
.requirements()
.permission_profile
.can_set(&PermissionProfile::from_legacy_sandbox_policy(
&SandboxPolicy::new_workspace_write_policy()
))
.is_ok()
);
Ok(())
}
#[tokio::test]
async fn load_config_layers_fails_when_cloud_requirements_loader_fails() -> anyhow::Result<()> {
let tmp = tempdir()?;
+34 -23
View File
@@ -1636,7 +1636,7 @@ network_access = false # This should be ignored.
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
/*active_project*/ None,
/*sandbox_policy_constraint*/ None,
/*permission_profile_constraint*/ None,
)
.await;
assert_eq!(resolution, SandboxPolicy::DangerFullAccess);
@@ -1657,7 +1657,7 @@ network_access = true # This should be ignored.
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
/*active_project*/ None,
/*sandbox_policy_constraint*/ None,
/*permission_profile_constraint*/ None,
)
.await;
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
@@ -1689,7 +1689,7 @@ trust_level = "trusted"
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
/*active_project*/ None,
/*sandbox_policy_constraint*/ None,
/*permission_profile_constraint*/ None,
)
.await;
if cfg!(target_os = "windows") {
@@ -1729,7 +1729,7 @@ exclude_slash_tmp = true
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
/*active_project*/ None,
/*sandbox_policy_constraint*/ None,
/*permission_profile_constraint*/ None,
)
.await;
if cfg!(target_os = "windows") {
@@ -6316,7 +6316,7 @@ trust_level = "untrusted"
/*profile_sandbox_mode*/ None,
WindowsSandboxLevel::Disabled,
Some(&active_project),
/*sandbox_policy_constraint*/ None,
/*permission_profile_constraint*/ None,
)
.await;
@@ -6337,8 +6337,8 @@ trust_level = "untrusted"
}
#[tokio::test]
async fn derive_sandbox_policy_falls_back_to_constraint_value_for_implicit_defaults()
-> anyhow::Result<()> {
async fn derive_sandbox_policy_falls_back_to_read_only_for_implicit_defaults() -> anyhow::Result<()>
{
let project_dir = TempDir::new()?;
let project_path = project_dir.path().to_path_buf();
let project_key = project_path.to_string_lossy().to_string();
@@ -6354,14 +6354,14 @@ async fn derive_sandbox_policy_falls_back_to_constraint_value_for_implicit_defau
let active_project = ProjectConfig {
trust_level: Some(TrustLevel::Trusted),
};
let constrained = Constrained::new(SandboxPolicy::DangerFullAccess, |candidate| {
if matches!(candidate, SandboxPolicy::DangerFullAccess) {
let constrained = Constrained::new(PermissionProfile::read_only(), |candidate| {
if candidate == &PermissionProfile::read_only() {
Ok(())
} else {
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: format!("{candidate:?}"),
allowed: "[DangerFullAccess]".to_string(),
allowed: "[ReadOnly]".to_string(),
requirement_source: RequirementSource::Unknown,
})
}
@@ -6377,7 +6377,7 @@ async fn derive_sandbox_policy_falls_back_to_constraint_value_for_implicit_defau
)
.await;
assert_eq!(resolution, SandboxPolicy::DangerFullAccess);
assert_eq!(resolution, SandboxPolicy::new_read_only_policy());
Ok(())
}
@@ -6399,18 +6399,29 @@ async fn derive_sandbox_policy_preserves_windows_downgrade_for_unsupported_fallb
let active_project = ProjectConfig {
trust_level: Some(TrustLevel::Trusted),
};
let constrained = Constrained::new(SandboxPolicy::new_workspace_write_policy(), |candidate| {
if matches!(candidate, SandboxPolicy::WorkspaceWrite { .. }) {
Ok(())
} else {
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: format!("{candidate:?}"),
allowed: "[WorkspaceWrite]".to_string(),
requirement_source: RequirementSource::Unknown,
})
}
})?;
let constrained = Constrained::new(
PermissionProfile::from_legacy_sandbox_policy(&SandboxPolicy::new_workspace_write_policy()),
|candidate| {
if matches!(
candidate,
PermissionProfile::Managed {
file_system: ManagedFileSystemPermissions::Restricted { entries, .. },
..
} if entries
.iter()
.any(|entry| entry.access.can_write())
) {
Ok(())
} else {
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: format!("{candidate:?}"),
allowed: "[WorkspaceWrite]".to_string(),
requirement_source: RequirementSource::Unknown,
})
}
},
)?;
let resolution = cfg
.derive_sandbox_policy(
+39 -95
View File
@@ -19,6 +19,7 @@ use codex_config::LoaderOverrides;
use codex_config::McpServerIdentity;
use codex_config::McpServerRequirement;
use codex_config::ResidencyRequirement;
use codex_config::SandboxModeRequirement;
use codex_config::Sourced;
use codex_config::ThreadConfigLoader;
use codex_config::config_toml::ConfigToml;
@@ -30,6 +31,7 @@ use codex_config::config_toml::validate_model_providers;
use codex_config::loader::load_config_layers_state;
use codex_config::loader::project_trust_key;
use codex_config::profile_toml::ConfigProfile;
use codex_config::sandbox_mode_requirement_for_permission_profile;
use codex_config::types::ApprovalsReviewer;
use codex_config::types::AuthCredentialsStoreMode;
use codex_config::types::DEFAULT_OTEL_ENVIRONMENT;
@@ -295,25 +297,6 @@ impl Permissions {
}
}
fn constrained_permission_profile_from_sandbox_projection(
initial_value: PermissionProfile,
sandbox_constraint: Constrained<SandboxPolicy>,
cwd: AbsolutePathBuf,
) -> std::io::Result<Constrained<PermissionProfile>> {
Constrained::new(initial_value, move |candidate| {
let (file_system_sandbox_policy, network_sandbox_policy) =
candidate.to_runtime_permissions();
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
candidate,
&file_system_sandbox_policy,
network_sandbox_policy,
cwd.as_path(),
);
sandbox_constraint.can_set(&sandbox_policy)
})
.map_err(std::io::Error::from)
}
/// Configured thread persistence backend.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum ThreadStoreConfig {
@@ -1709,7 +1692,7 @@ impl Config {
let ConfigRequirements {
approval_policy: mut constrained_approval_policy,
approvals_reviewer: mut constrained_approvals_reviewer,
sandbox_policy: mut constrained_sandbox_policy,
permission_profile: mut constrained_permission_profile,
web_search_mode: mut constrained_web_search_mode,
feature_requirements,
managed_hooks: _,
@@ -1881,9 +1864,7 @@ impl Config {
let (
configured_network_proxy_config,
permission_profile,
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
) = if let Some(mut permission_profile) = permission_profile {
let (mut file_system_sandbox_policy, network_sandbox_policy) =
permission_profile.to_runtime_permissions();
@@ -1910,7 +1891,7 @@ impl Config {
} else {
NetworkProxyConfig::default()
};
let mut sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
&permission_profile,
&file_system_sandbox_policy,
network_sandbox_policy,
@@ -1927,19 +1908,11 @@ impl Config {
&file_system_sandbox_policy,
network_sandbox_policy,
);
sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
&permission_profile,
&file_system_sandbox_policy,
network_sandbox_policy,
resolved_cwd.as_path(),
);
}
(
configured_network_proxy_config,
permission_profile,
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
)
} else if profiles_are_active {
let permissions = cfg.permissions.as_ref().ok_or_else(|| {
@@ -1968,7 +1941,7 @@ impl Config {
&file_system_sandbox_policy,
network_sandbox_policy,
);
let mut sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
&permission_profile,
&file_system_sandbox_policy,
network_sandbox_policy,
@@ -1984,19 +1957,11 @@ impl Config {
&file_system_sandbox_policy,
network_sandbox_policy,
);
sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
&permission_profile,
&file_system_sandbox_policy,
network_sandbox_policy,
resolved_cwd.as_path(),
);
}
(
configured_network_proxy_config,
permission_profile,
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
)
} else {
let configured_network_proxy_config = NetworkProxyConfig::default();
@@ -2006,7 +1971,7 @@ impl Config {
config_profile.sandbox_mode,
windows_sandbox_level,
Some(&active_project),
Some(&constrained_sandbox_policy),
Some(&constrained_permission_profile),
)
.await;
if let SandboxPolicy::WorkspaceWrite { writable_roots, .. } = &mut sandbox_policy {
@@ -2030,9 +1995,7 @@ impl Config {
(
configured_network_proxy_config,
permission_profile,
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
)
};
let approval_policy_was_explicit = approval_policy_override.is_some()
@@ -2324,8 +2287,7 @@ impl Config {
.map(AbsolutePathBuf::to_path_buf)
.or_else(|| resolve_sqlite_home_env(&resolved_cwd))
.unwrap_or_else(|| codex_home.to_path_buf());
let original_sandbox_policy = sandbox_policy.clone();
let original_permission_profile = permission_profile.clone();
apply_requirement_constrained_value(
"approval_policy",
approval_policy,
@@ -2339,17 +2301,22 @@ impl Config {
&& !filesystem_requirements.deny_read.is_empty()
{
let requirement_source = filesystem_requirements_source.clone();
constrained_sandbox_policy
constrained_permission_profile
.value
.add_validator(move |policy| match policy {
SandboxPolicy::ReadOnly { .. } | SandboxPolicy::WorkspaceWrite { .. } => Ok(()),
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: policy.to_string(),
allowed: "[read-only, workspace-write]".to_string(),
requirement_source: requirement_source.clone(),
})
.add_validator(move |permission_profile| {
let mode = sandbox_mode_requirement_for_permission_profile(permission_profile);
match mode {
SandboxModeRequirement::ReadOnly
| SandboxModeRequirement::WorkspaceWrite => Ok(()),
SandboxModeRequirement::DangerFullAccess
| SandboxModeRequirement::ExternalSandbox => {
Err(ConstraintError::InvalidValue {
field_name: "sandbox_mode",
candidate: format!("{mode:?}"),
allowed: "[read-only, workspace-write]".to_string(),
requirement_source: requirement_source.clone(),
})
}
}
})
.map_err(std::io::Error::from)?;
@@ -2367,9 +2334,9 @@ impl Config {
&mut startup_warnings,
)?;
apply_requirement_constrained_value(
"sandbox_mode",
sandbox_policy,
&mut constrained_sandbox_policy,
"permission_profile",
permission_profile,
&mut constrained_permission_profile,
&mut startup_warnings,
)?;
apply_requirement_constrained_value(
@@ -2387,13 +2354,7 @@ impl Config {
None => (None, None),
};
let has_network_requirements = network_requirements.is_some();
let network_permission_profile = if *constrained_sandbox_policy.get()
== original_sandbox_policy
{
permission_profile.clone()
} else {
PermissionProfile::from_legacy_sandbox_policy(constrained_sandbox_policy.get())
};
let network_permission_profile = constrained_permission_profile.get().clone();
let network = NetworkProxySpec::from_config_and_constraints(
configured_network_proxy_config,
network_requirements,
@@ -2419,17 +2380,13 @@ impl Config {
zsh_path.as_ref(),
main_execve_wrapper_exe.as_ref(),
);
let effective_sandbox_policy = constrained_sandbox_policy.value.get().clone();
let mut effective_file_system_sandbox_policy =
if effective_sandbox_policy == original_sandbox_policy {
file_system_sandbox_policy
} else {
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
&effective_sandbox_policy,
resolved_cwd.as_path(),
&file_system_sandbox_policy,
)
};
let effective_permission_profile = constrained_permission_profile.value.get().clone();
let (mut effective_file_system_sandbox_policy, effective_network_sandbox_policy) =
effective_permission_profile.to_runtime_permissions();
if effective_permission_profile != original_permission_profile {
effective_file_system_sandbox_policy
.preserve_deny_read_restrictions_from(&file_system_sandbox_policy);
}
if let Some(Sourced {
value: filesystem_requirements,
..
@@ -2442,28 +2399,15 @@ impl Config {
}
let effective_file_system_sandbox_policy = effective_file_system_sandbox_policy
.with_additional_readable_roots(resolved_cwd.as_path(), &helper_readable_roots);
let effective_network_sandbox_policy =
if effective_sandbox_policy == original_sandbox_policy {
network_sandbox_policy
} else {
NetworkSandboxPolicy::from(&effective_sandbox_policy)
};
let effective_enforcement = if effective_sandbox_policy == original_sandbox_policy {
permission_profile.enforcement()
} else {
SandboxEnforcement::from_legacy_sandbox_policy(&effective_sandbox_policy)
};
let effective_permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
effective_enforcement,
effective_permission_profile.enforcement(),
&effective_file_system_sandbox_policy,
effective_network_sandbox_policy,
);
let constrained_permission_profile =
constrained_permission_profile_from_sandbox_projection(
effective_permission_profile,
constrained_sandbox_policy.value.clone(),
resolved_cwd.clone(),
)?;
constrained_permission_profile
.value
.set(effective_permission_profile)
.map_err(std::io::Error::from)?;
let config = Self {
model,
service_tier,
@@ -2476,7 +2420,7 @@ impl Config {
startup_warnings,
permissions: Permissions {
approval_policy: constrained_approval_policy.value,
permission_profile: constrained_permission_profile,
permission_profile: constrained_permission_profile.value,
network,
allow_login_shell,
shell_environment_policy,
+4 -4
View File
@@ -126,7 +126,7 @@ fn render_debug_config_lines(stack: &ConfigLayerStack) -> Vec<Line<'static>> {
requirement_lines.push(requirement_line(
"allowed_sandbox_modes",
value,
requirements.sandbox_policy.source.as_ref(),
requirements.permission_profile.source.as_ref(),
));
}
@@ -531,8 +531,8 @@ mod tests {
use codex_config::WebSearchModeRequirement;
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use ratatui::text::Line;
use std::collections::BTreeMap;
@@ -622,8 +622,8 @@ mod tests {
Constrained::allow_any(ApprovalsReviewer::AutoReview),
Some(RequirementSource::LegacyManagedConfigTomlFromMdm),
),
sandbox_policy: ConstrainedWithSource::new(
Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
permission_profile: ConstrainedWithSource::new(
Constrained::allow_any(PermissionProfile::read_only()),
Some(RequirementSource::SystemRequirementsToml {
file: requirements_file.clone(),
}),