mirror of
https://github.com/openai/codex.git
synced 2026-11-03 08:22:30 +00:00
permissions: expose active profile metadata (#20095)
This commit is contained in:
1 parent
ebe602d005
commit
ac4332c05b
71 files changed
+2480
-1657
No files matched your search
@@ -15,6 +15,7 @@ use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result as CodexResult;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
@@ -48,6 +49,7 @@ pub struct ThreadConfigSnapshot {
|
||||
pub approval_policy: AskForApproval,
|
||||
pub approvals_reviewer: ApprovalsReviewer,
|
||||
pub permission_profile: PermissionProfile,
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
pub ephemeral: bool,
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
@@ -75,6 +77,7 @@ pub struct CodexThreadTurnContextOverrides {
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
pub sandbox_policy: Option<SandboxPolicy>,
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub windows_sandbox_level: Option<WindowsSandboxLevel>,
|
||||
pub model: Option<String>,
|
||||
pub effort: Option<Option<ReasoningEffort>>,
|
||||
@@ -225,6 +228,7 @@ impl CodexThread {
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
model,
|
||||
effort,
|
||||
@@ -249,6 +253,7 @@ impl CodexThread {
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
collaboration_mode: Some(collaboration_mode),
|
||||
reasoning_summary: summary,
|
||||
|
||||
@@ -60,6 +60,8 @@ use codex_model_provider_info::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_model_provider_info::WireApi;
|
||||
use codex_models_manager::bundled_models_response;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::ActivePermissionProfileModification;
|
||||
use codex_protocol::models::ManagedFileSystemPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
@@ -907,6 +909,14 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Restricted
|
||||
);
|
||||
assert_eq!(
|
||||
config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.as_ref()
|
||||
.map(|active| active.id.as_str()),
|
||||
Some("workspace")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -928,6 +938,7 @@ async fn permission_profile_override_populates_runtime_permissions() -> std::io:
|
||||
.await?;
|
||||
|
||||
assert_eq!(config.permissions.permission_profile(), permission_profile);
|
||||
assert_eq!(config.permissions.active_permission_profile(), None);
|
||||
assert_eq!(
|
||||
&config.legacy_sandbox_policy(),
|
||||
&SandboxPolicy::DangerFullAccess
|
||||
@@ -1267,6 +1278,14 @@ async fn default_permissions_can_select_builtin_profile_without_permissions_tabl
|
||||
.await?;
|
||||
|
||||
let policy = config.permissions.file_system_sandbox_policy();
|
||||
assert_eq!(
|
||||
config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.as_ref()
|
||||
.map(|active| active.id.as_str()),
|
||||
Some(":workspace")
|
||||
);
|
||||
assert!(
|
||||
policy.can_write_path_with_cwd(cwd.path(), cwd.path()),
|
||||
"expected :workspace to allow writing the project root, policy: {policy:?}"
|
||||
@@ -1278,6 +1297,86 @@ async fn default_permissions_can_select_builtin_profile_without_permissions_tabl
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn default_permissions_read_only_applies_additional_writable_roots_as_modifications()
|
||||
-> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let extra_root = TempDir::new()?;
|
||||
let extra_root = extra_root.path().abs();
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
default_permissions: Some(":read-only".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
additional_writable_roots: vec![extra_root.to_path_buf()],
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let policy = config.permissions.file_system_sandbox_policy();
|
||||
assert!(
|
||||
policy.can_write_path_with_cwd(extra_root.as_path(), cwd.path()),
|
||||
"expected additional writable root to modify :read-only, policy: {policy:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.active_permission_profile(),
|
||||
Some(
|
||||
ActivePermissionProfile::new(":read-only").with_modifications(vec![
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot { path: extra_root },
|
||||
])
|
||||
)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn explicit_builtin_workspace_profile_ignores_legacy_workspace_write_settings()
|
||||
-> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
let extra_root = TempDir::new()?;
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
default_permissions: Some(":workspace".to_string()),
|
||||
sandbox_workspace_write: Some(SandboxWorkspaceWrite {
|
||||
writable_roots: vec![extra_root.path().abs()],
|
||||
network_access: true,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let policy = config.permissions.file_system_sandbox_policy();
|
||||
assert_eq!(
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Restricted
|
||||
);
|
||||
assert!(
|
||||
!policy.entries.iter().any(|entry| matches!(
|
||||
&entry.path,
|
||||
FileSystemPath::Path { path } if path.as_path() == extra_root.path()
|
||||
)),
|
||||
"explicit :workspace should not inherit sandbox_workspace_write roots as concrete grants, \
|
||||
policy: {policy:?}"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_config_defaults_to_builtin_profile_for_trusted_project() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
@@ -1303,6 +1402,18 @@ async fn empty_config_defaults_to_builtin_profile_for_trusted_project() -> std::
|
||||
.await?;
|
||||
|
||||
let policy = config.permissions.file_system_sandbox_policy();
|
||||
assert_eq!(
|
||||
config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.as_ref()
|
||||
.map(|active| active.id.as_str()),
|
||||
Some(if cfg!(target_os = "windows") {
|
||||
":read-only"
|
||||
} else {
|
||||
":workspace"
|
||||
})
|
||||
);
|
||||
if cfg!(target_os = "windows") {
|
||||
assert!(
|
||||
!policy.can_write_path_with_cwd(cwd.path(), cwd.path()),
|
||||
@@ -1367,6 +1478,12 @@ async fn implicit_builtin_workspace_profile_preserves_sandbox_workspace_write_se
|
||||
config.permissions.network_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Enabled
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.active_permission_profile(),
|
||||
None,
|
||||
"implicit :workspace cannot be faithfully re-selected when it includes \
|
||||
legacy sandbox_workspace_write settings"
|
||||
);
|
||||
match config.legacy_sandbox_policy() {
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots,
|
||||
@@ -1484,6 +1601,14 @@ async fn default_permissions_can_select_builtin_no_sandbox_profile() -> std::io:
|
||||
config.permissions.permission_profile(),
|
||||
PermissionProfile::Disabled
|
||||
);
|
||||
assert_eq!(
|
||||
config
|
||||
.permissions
|
||||
.active_permission_profile()
|
||||
.as_ref()
|
||||
.map(|active| active.id.as_str()),
|
||||
Some(":danger-no-sandbox")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6151,6 +6276,7 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::Never),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -6346,6 +6472,7 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -6495,6 +6622,7 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -6629,6 +6757,7 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
|
||||
permissions: Permissions {
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
|
||||
active_permission_profile: Some(ActivePermissionProfile::new(":read-only")),
|
||||
network: None,
|
||||
allow_login_shell: true,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -7518,6 +7647,41 @@ async fn permission_profile_override_falls_back_when_disallowed_by_requirements(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn active_profile_is_cleared_when_requirements_force_fallback() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let requirements = codex_config::ConfigRequirementsToml {
|
||||
allowed_sandbox_modes: Some(vec![codex_config::SandboxModeRequirement::ReadOnly]),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||
.codex_home(codex_home.path().to_path_buf())
|
||||
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
||||
.harness_overrides(ConfigOverrides {
|
||||
default_permissions: Some(":danger-no-sandbox".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.cloud_requirements(CloudRequirementsLoader::new(async move {
|
||||
Ok(Some(requirements))
|
||||
}))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(
|
||||
config.permissions.permission_profile(),
|
||||
PermissionProfile::read_only()
|
||||
);
|
||||
assert_eq!(config.permissions.active_permission_profile(), None);
|
||||
assert!(
|
||||
config.startup_warnings.iter().any(|warning| warning
|
||||
.contains("Configured value for `permission_profile` is disallowed by requirements")),
|
||||
"{:?}",
|
||||
config.startup_warnings
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permission_profile_override_preserves_split_write_roots() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
+115
-13
@@ -86,6 +86,8 @@ use codex_protocol::config_types::Verbosity;
|
||||
use codex_protocol::config_types::WebSearchConfig;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::ActivePermissionProfileModification;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
@@ -105,6 +107,7 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::permissions::BUILT_IN_WORKSPACE_PROFILE;
|
||||
use crate::config::permissions::builtin_permission_profile;
|
||||
use crate::config::permissions::compile_permission_profile_selection;
|
||||
use crate::config::permissions::default_builtin_permission_profile_name;
|
||||
@@ -229,6 +232,9 @@ pub struct Permissions {
|
||||
/// Canonical effective runtime permissions after config requirements and
|
||||
/// runtime readable-root additions have been applied.
|
||||
pub permission_profile: Constrained<PermissionProfile>,
|
||||
/// Named or implicit built-in profile selected by config, rather than an
|
||||
/// ad-hoc override.
|
||||
pub active_permission_profile: Option<ActivePermissionProfile>,
|
||||
/// Effective network configuration applied to all spawned processes.
|
||||
pub network: Option<NetworkProxySpec>,
|
||||
/// Whether the model may request a login shell for shell-based tools.
|
||||
@@ -256,6 +262,11 @@ impl Permissions {
|
||||
self.permission_profile.get().clone()
|
||||
}
|
||||
|
||||
/// Named profile selected by config, if the current profile has one.
|
||||
pub fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||
self.active_permission_profile.clone()
|
||||
}
|
||||
|
||||
/// Effective filesystem sandbox policy derived from the canonical profile.
|
||||
pub fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
|
||||
self.permission_profile.get().file_system_sandbox_policy()
|
||||
@@ -314,6 +325,7 @@ impl Permissions {
|
||||
);
|
||||
|
||||
self.permission_profile.set(permission_profile)?;
|
||||
self.active_permission_profile = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -321,10 +333,24 @@ impl Permissions {
|
||||
pub fn set_permission_profile(
|
||||
&mut self,
|
||||
permission_profile: PermissionProfile,
|
||||
) -> ConstraintResult<()> {
|
||||
self.set_permission_profile_with_active_profile(
|
||||
permission_profile,
|
||||
/*active_permission_profile*/ None,
|
||||
)
|
||||
}
|
||||
|
||||
/// Replace permissions from the canonical profile and record the named
|
||||
/// source profile, if one is known.
|
||||
pub fn set_permission_profile_with_active_profile(
|
||||
&mut self,
|
||||
permission_profile: PermissionProfile,
|
||||
active_permission_profile: Option<ActivePermissionProfile>,
|
||||
) -> ConstraintResult<()> {
|
||||
self.permission_profile.can_set(&permission_profile)?;
|
||||
|
||||
self.permission_profile.set(permission_profile)?;
|
||||
self.active_permission_profile = active_permission_profile;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1254,7 +1280,7 @@ fn apply_requirement_constrained_value<T>(
|
||||
configured_value: T,
|
||||
constrained_value: &mut ConstrainedWithSource<T>,
|
||||
startup_warnings: &mut Vec<String>,
|
||||
) -> std::io::Result<()>
|
||||
) -> std::io::Result<bool>
|
||||
where
|
||||
T: Clone + std::fmt::Debug + Send + Sync,
|
||||
{
|
||||
@@ -1279,9 +1305,10 @@ where
|
||||
),
|
||||
)
|
||||
})?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn mcp_server_matches_requirement(
|
||||
@@ -1681,6 +1708,7 @@ pub struct ConfigOverrides {
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
pub sandbox_mode: Option<SandboxMode>,
|
||||
pub permission_profile: Option<PermissionProfile>,
|
||||
pub default_permissions: Option<String>,
|
||||
pub model_provider: Option<String>,
|
||||
pub service_tier: Option<Option<ServiceTier>>,
|
||||
pub config_profile: Option<String>,
|
||||
@@ -1945,6 +1973,7 @@ impl Config {
|
||||
approvals_reviewer: approvals_reviewer_override,
|
||||
sandbox_mode,
|
||||
permission_profile,
|
||||
default_permissions: default_permissions_override,
|
||||
model_provider,
|
||||
service_tier: service_tier_override,
|
||||
config_profile: config_profile_key,
|
||||
@@ -1969,6 +1998,18 @@ impl Config {
|
||||
"`sandbox_mode` and `permission_profile` overrides cannot both be set",
|
||||
));
|
||||
}
|
||||
if sandbox_mode.is_some() && default_permissions_override.is_some() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"`sandbox_mode` and `default_permissions` overrides cannot both be set",
|
||||
));
|
||||
}
|
||||
if permission_profile.is_some() && default_permissions_override.is_some() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"`permission_profile` and `default_permissions` overrides cannot both be set",
|
||||
));
|
||||
}
|
||||
|
||||
let active_profile_name = config_profile_key
|
||||
.as_ref()
|
||||
@@ -2040,6 +2081,7 @@ impl Config {
|
||||
.into_iter()
|
||||
.map(|path| AbsolutePathBuf::resolve_path_against_base(path, resolved_cwd.as_path()))
|
||||
.collect();
|
||||
let requested_additional_writable_roots = additional_writable_roots.clone();
|
||||
let repo_root = resolve_root_git_project_for_trust(fs, &resolved_cwd).await;
|
||||
let active_project = cfg
|
||||
.get_active_project(
|
||||
@@ -2057,13 +2099,16 @@ impl Config {
|
||||
.permissions
|
||||
.as_ref()
|
||||
.is_some_and(|profiles| !profiles.is_empty());
|
||||
let default_permissions = default_permissions_override
|
||||
.as_deref()
|
||||
.or(cfg.default_permissions.as_deref());
|
||||
validate_user_permission_profile_names(cfg.permissions.as_ref())?;
|
||||
if has_permission_profiles
|
||||
&& !matches!(
|
||||
permission_config_syntax,
|
||||
Some(PermissionConfigSyntax::Legacy)
|
||||
)
|
||||
&& cfg.default_permissions.is_none()
|
||||
&& default_permissions.is_none()
|
||||
{
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
@@ -2085,15 +2130,19 @@ impl Config {
|
||||
additional_writable_roots.push(memories_root);
|
||||
}
|
||||
|
||||
let profiles_are_active = matches!(
|
||||
permission_config_syntax,
|
||||
Some(PermissionConfigSyntax::Profiles)
|
||||
) || permission_config_syntax.is_none();
|
||||
let using_implicit_builtin_profile = permission_config_syntax.is_none();
|
||||
let profiles_are_active = default_permissions_override.is_some()
|
||||
|| matches!(
|
||||
permission_config_syntax,
|
||||
Some(PermissionConfigSyntax::Profiles)
|
||||
)
|
||||
|| permission_config_syntax.is_none();
|
||||
let using_implicit_builtin_profile =
|
||||
permission_config_syntax.is_none() && default_permissions.is_none();
|
||||
let (
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
mut active_permission_profile,
|
||||
) = if let Some(mut permission_profile) = permission_profile {
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
@@ -2104,7 +2153,7 @@ impl Config {
|
||||
// PermissionProfile carries the active network sandbox bit, not the configured
|
||||
// proxy/allowlist policy. Keep that config so active profiles can round-trip
|
||||
// without broadening network behavior.
|
||||
let default_permissions = cfg.default_permissions.as_deref().unwrap_or_else(|| {
|
||||
let default_permissions = default_permissions.unwrap_or_else(|| {
|
||||
default_builtin_permission_profile_name(
|
||||
&active_project,
|
||||
windows_sandbox_level,
|
||||
@@ -2139,11 +2188,17 @@ impl Config {
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
None,
|
||||
)
|
||||
} else if profiles_are_active {
|
||||
let default_permissions = cfg.default_permissions.as_deref().unwrap_or_else(|| {
|
||||
let default_permissions = default_permissions.unwrap_or_else(|| {
|
||||
default_builtin_permission_profile_name(&active_project, windows_sandbox_level)
|
||||
});
|
||||
let builtin_workspace_write_settings = if using_implicit_builtin_profile {
|
||||
cfg.sandbox_workspace_write.as_ref()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let configured_network_proxy_config = network_proxy_config_for_profile_selection(
|
||||
cfg.permissions.as_ref(),
|
||||
default_permissions,
|
||||
@@ -2152,12 +2207,12 @@ impl Config {
|
||||
compile_permission_profile_selection(
|
||||
cfg.permissions.as_ref(),
|
||||
default_permissions,
|
||||
cfg.sandbox_workspace_write.as_ref(),
|
||||
builtin_workspace_write_settings,
|
||||
resolved_cwd.as_path(),
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
let mut permission_profile = if let Some(permission_profile) =
|
||||
builtin_permission_profile(default_permissions, cfg.sandbox_workspace_write.as_ref())
|
||||
builtin_permission_profile(default_permissions, builtin_workspace_write_settings)
|
||||
{
|
||||
permission_profile
|
||||
} else {
|
||||
@@ -2188,11 +2243,51 @@ impl Config {
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
} else if matches!(permission_profile, PermissionProfile::Managed { .. })
|
||||
&& !requested_additional_writable_roots.is_empty()
|
||||
{
|
||||
file_system_sandbox_policy = file_system_sandbox_policy.with_additional_writable_roots(
|
||||
resolved_cwd.as_path(),
|
||||
&requested_additional_writable_roots,
|
||||
);
|
||||
permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
}
|
||||
let active_permission_profile = if using_implicit_builtin_profile
|
||||
&& default_permissions == BUILT_IN_WORKSPACE_PROFILE
|
||||
&& cfg.sandbox_workspace_write.is_some()
|
||||
{
|
||||
// The implicit built-in profile preserves legacy
|
||||
// `[sandbox_workspace_write]` customizations, but explicitly
|
||||
// selecting `:workspace` intentionally ignores those legacy
|
||||
// settings. Do not advertise a re-selectable active profile
|
||||
// when doing so would lose roots, network, or tmp settings.
|
||||
None
|
||||
} else {
|
||||
let active_permission_profile = if !requested_additional_writable_roots.is_empty()
|
||||
&& matches!(permission_profile, PermissionProfile::Managed { .. })
|
||||
{
|
||||
ActivePermissionProfile::new(default_permissions).with_modifications(
|
||||
requested_additional_writable_roots
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|path| {
|
||||
ActivePermissionProfileModification::AdditionalWritableRoot { path }
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
ActivePermissionProfile::new(default_permissions)
|
||||
};
|
||||
Some(active_permission_profile)
|
||||
};
|
||||
(
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
active_permission_profile,
|
||||
)
|
||||
} else {
|
||||
let configured_network_proxy_config = NetworkProxyConfig::default();
|
||||
@@ -2254,6 +2349,7 @@ impl Config {
|
||||
configured_network_proxy_config,
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
None,
|
||||
)
|
||||
};
|
||||
let approval_policy_was_explicit = approval_policy_override.is_some()
|
||||
@@ -2633,12 +2729,17 @@ impl Config {
|
||||
&mut constrained_approvals_reviewer,
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
apply_requirement_constrained_value(
|
||||
let permission_profile_was_constrained = apply_requirement_constrained_value(
|
||||
"permission_profile",
|
||||
permission_profile,
|
||||
&mut constrained_permission_profile,
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
if permission_profile_was_constrained {
|
||||
// The selected profile no longer describes the effective
|
||||
// permissions after requirements forced a fallback.
|
||||
active_permission_profile = None;
|
||||
}
|
||||
apply_requirement_constrained_value(
|
||||
"web_search_mode",
|
||||
web_search_mode,
|
||||
@@ -2721,6 +2822,7 @@ impl Config {
|
||||
permissions: Permissions {
|
||||
approval_policy: constrained_approval_policy.value,
|
||||
permission_profile: constrained_permission_profile.value,
|
||||
active_permission_profile,
|
||||
network,
|
||||
allow_login_shell,
|
||||
shell_environment_policy,
|
||||
|
||||
@@ -161,6 +161,7 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
approvals_reviewer,
|
||||
sandbox_policy: Some(sandbox_policy),
|
||||
permission_profile,
|
||||
active_permission_profile: None,
|
||||
windows_sandbox_level: None,
|
||||
collaboration_mode,
|
||||
reasoning_summary: summary,
|
||||
@@ -180,6 +181,7 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
model,
|
||||
effort,
|
||||
@@ -211,6 +213,7 @@ pub(super) async fn user_input_or_turn_inner(
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level,
|
||||
collaboration_mode,
|
||||
reasoning_summary: summary,
|
||||
|
||||
@@ -91,6 +91,7 @@ use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::items::UserMessageItem;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::ActivePermissionProfile;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
@@ -603,6 +604,7 @@ impl Codex {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
|
||||
@@ -60,6 +60,9 @@ pub(crate) struct SessionConfiguration {
|
||||
pub(super) approvals_reviewer: ApprovalsReviewer,
|
||||
/// Canonical permission profile for the session.
|
||||
pub(super) permission_profile: Constrained<PermissionProfile>,
|
||||
/// Named or implicit built-in permissions profile selected from config, if
|
||||
/// any.
|
||||
pub(super) active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub(super) windows_sandbox_level: WindowsSandboxLevel,
|
||||
|
||||
/// Absolute working directory that should be treated as the *root* of the
|
||||
@@ -97,6 +100,10 @@ impl SessionConfiguration {
|
||||
self.permission_profile.get().clone()
|
||||
}
|
||||
|
||||
pub(super) fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
|
||||
self.active_permission_profile.clone()
|
||||
}
|
||||
|
||||
pub(super) fn sandbox_policy(&self) -> SandboxPolicy {
|
||||
self.permission_profile()
|
||||
.to_legacy_sandbox_policy(&self.cwd)
|
||||
@@ -127,6 +134,7 @@ impl SessionConfiguration {
|
||||
approval_policy: self.approval_policy.value(),
|
||||
approvals_reviewer: self.approvals_reviewer,
|
||||
permission_profile: self.permission_profile(),
|
||||
active_permission_profile: self.active_permission_profile(),
|
||||
cwd: self.cwd.clone(),
|
||||
ephemeral: self.original_config_do_not_use.ephemeral,
|
||||
reasoning_effort: self.collaboration_mode.reasoning_effort(),
|
||||
@@ -206,10 +214,19 @@ impl SessionConfiguration {
|
||||
}
|
||||
|
||||
if let Some(permission_profile) = updates.permission_profile.clone() {
|
||||
let active_permission_profile =
|
||||
updates.active_permission_profile.clone().or_else(|| {
|
||||
if permission_profile == self.permission_profile() {
|
||||
self.active_permission_profile.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
next_configuration.set_permission_profile_projection(
|
||||
permission_profile,
|
||||
Some(¤t_file_system_sandbox_policy),
|
||||
)?;
|
||||
next_configuration.active_permission_profile = active_permission_profile;
|
||||
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
@@ -225,6 +242,7 @@ impl SessionConfiguration {
|
||||
network_sandbox_policy,
|
||||
),
|
||||
)?;
|
||||
next_configuration.active_permission_profile = None;
|
||||
} else if cwd_changed
|
||||
&& file_system_policy_matches_legacy
|
||||
&& file_system_policy_has_rebindable_project_root_write
|
||||
@@ -285,6 +303,7 @@ pub(crate) struct SessionSettingsUpdate {
|
||||
pub(crate) approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
pub(crate) sandbox_policy: Option<SandboxPolicy>,
|
||||
pub(crate) permission_profile: Option<PermissionProfile>,
|
||||
pub(crate) active_permission_profile: Option<ActivePermissionProfile>,
|
||||
pub(crate) windows_sandbox_level: Option<WindowsSandboxLevel>,
|
||||
pub(crate) collaboration_mode: Option<CollaborationMode>,
|
||||
pub(crate) reasoning_summary: Option<ReasoningSummaryConfig>,
|
||||
@@ -887,6 +906,7 @@ impl Session {
|
||||
approval_policy: session_configuration.approval_policy.value(),
|
||||
approvals_reviewer: session_configuration.approvals_reviewer,
|
||||
permission_profile: session_configuration.permission_profile(),
|
||||
active_permission_profile: session_configuration.active_permission_profile(),
|
||||
cwd: session_configuration.cwd.clone(),
|
||||
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
|
||||
history_log_id,
|
||||
|
||||
@@ -2324,6 +2324,7 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -2426,6 +2427,7 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -2873,6 +2875,7 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -3309,6 +3312,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -3413,6 +3417,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -3625,6 +3630,7 @@ async fn make_session_with_config_and_rx(
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
@@ -4206,6 +4212,7 @@ fn op_kind_distinguishes_turn_ops() {
|
||||
approvals_reviewer: None,
|
||||
sandbox_policy: None,
|
||||
permission_profile: None,
|
||||
active_permission_profile: None,
|
||||
windows_sandbox_level: None,
|
||||
model: None,
|
||||
effort: None,
|
||||
@@ -4839,6 +4846,7 @@ where
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
permission_profile: config.permissions.permission_profile.clone(),
|
||||
active_permission_profile: config.permissions.active_permission_profile(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
|
||||
cwd: config.cwd.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
|
||||
Reference in new issue
Block a user