mirror of
https://github.com/openai/codex.git
synced 2026-10-23 18:08:45 +00:00
permissions: centralize legacy sandbox projection
This commit is contained in:
@@ -2307,7 +2307,11 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
}
|
||||
} else if let Some(policy) = sandbox_policy.map(|policy| policy.to_core()) {
|
||||
match self.config.permissions.sandbox_policy.can_set(&policy) {
|
||||
match self
|
||||
.config
|
||||
.permissions
|
||||
.can_set_legacy_sandbox_policy(&policy, &sandbox_cwd)
|
||||
{
|
||||
Ok(()) => {
|
||||
let file_system_sandbox_policy =
|
||||
codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&policy, &sandbox_cwd);
|
||||
@@ -8705,7 +8709,9 @@ impl CodexMessageProcessor {
|
||||
Ok(config) => {
|
||||
let setup_request = WindowsSandboxSetupRequest {
|
||||
mode,
|
||||
policy: config.permissions.sandbox_policy.get().clone(),
|
||||
policy: config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
policy_cwd: config.cwd.to_path_buf(),
|
||||
command_cwd,
|
||||
env_map: std::env::vars().collect(),
|
||||
|
||||
@@ -227,7 +227,9 @@ async fn run_command_under_sandbox(
|
||||
let args = create_linux_sandbox_command_args_for_policies(
|
||||
command,
|
||||
cwd.as_path(),
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(sandbox_policy_cwd.as_path()),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
sandbox_policy_cwd.as_path(),
|
||||
@@ -290,7 +292,10 @@ async fn run_command_under_windows_session(
|
||||
use codex_windows_sandbox::spawn_windows_sandbox_session_elevated;
|
||||
use codex_windows_sandbox::spawn_windows_sandbox_session_legacy;
|
||||
|
||||
let policy_str = match serde_json::to_string(config.permissions.sandbox_policy.get()) {
|
||||
let sandbox_policy = config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(sandbox_policy_cwd.as_path());
|
||||
let policy_str = match serde_json::to_string(&sandbox_policy) {
|
||||
Ok(policy_str) => policy_str,
|
||||
Err(err) => {
|
||||
eprintln!("windows sandbox failed to serialize policy: {err}");
|
||||
|
||||
@@ -237,6 +237,37 @@ impl Permissions {
|
||||
self.permission_profile.get().network_sandbox_policy()
|
||||
}
|
||||
|
||||
/// Legacy compatibility projection derived from the canonical profile.
|
||||
pub fn legacy_sandbox_policy(&self, cwd: &Path) -> SandboxPolicy {
|
||||
let permission_profile = self.permission_profile.get();
|
||||
let file_system_sandbox_policy = permission_profile.file_system_sandbox_policy();
|
||||
compatibility_sandbox_policy_for_permission_profile(
|
||||
permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
permission_profile.network_sandbox_policy(),
|
||||
cwd,
|
||||
)
|
||||
}
|
||||
|
||||
/// Check whether a legacy sandbox policy can be applied to this permission
|
||||
/// set under both legacy and canonical profile constraints.
|
||||
pub fn can_set_legacy_sandbox_policy(
|
||||
&self,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
cwd: &Path,
|
||||
) -> ConstraintResult<()> {
|
||||
self.sandbox_policy.can_set(sandbox_policy)?;
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(sandbox_policy);
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
self.permission_profile.can_set(&permission_profile)
|
||||
}
|
||||
|
||||
/// Replace permissions from a legacy sandbox policy and keep every
|
||||
/// permission projection in sync.
|
||||
pub fn set_legacy_sandbox_policy(
|
||||
@@ -244,7 +275,7 @@ impl Permissions {
|
||||
sandbox_policy: SandboxPolicy,
|
||||
cwd: &Path,
|
||||
) -> ConstraintResult<()> {
|
||||
self.sandbox_policy.can_set(&sandbox_policy)?;
|
||||
self.can_set_legacy_sandbox_policy(&sandbox_policy, cwd)?;
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, cwd);
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
@@ -253,7 +284,6 @@ impl Permissions {
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
self.permission_profile.can_set(&permission_profile)?;
|
||||
|
||||
self.sandbox_policy.set(sandbox_policy)?;
|
||||
self.permission_profile.set(permission_profile)?;
|
||||
|
||||
@@ -634,7 +634,9 @@ impl Session {
|
||||
config.model_context_window,
|
||||
config.model_auto_compact_token_limit,
|
||||
config.permissions.approval_policy.value(),
|
||||
config.permissions.sandbox_policy.get().clone(),
|
||||
config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(session_configuration.cwd.as_path()),
|
||||
mcp_servers.keys().map(String::as_str).collect(),
|
||||
config.active_profile.clone(),
|
||||
);
|
||||
|
||||
@@ -939,10 +939,14 @@ impl App {
|
||||
// On startup, if Agent mode (workspace-write) or ReadOnly is active, warn about world-writable dirs on Windows.
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let startup_sandbox_policy = app
|
||||
.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(app.config.cwd.as_path());
|
||||
let should_check = WindowsSandboxLevel::from_config(&app.config)
|
||||
!= WindowsSandboxLevel::Disabled
|
||||
&& matches!(
|
||||
app.config.permissions.sandbox_policy.get(),
|
||||
&startup_sandbox_policy,
|
||||
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_protocol::protocol::SandboxPolicy::ReadOnly { .. }
|
||||
)
|
||||
@@ -956,7 +960,7 @@ impl App {
|
||||
let env_map: std::collections::HashMap<String, String> = std::env::vars().collect();
|
||||
let tx = app.app_event_tx.clone();
|
||||
let logs_base_dir = app.config.codex_home.clone();
|
||||
let sandbox_policy = app.config.permissions.sandbox_policy.get().clone();
|
||||
let sandbox_policy = startup_sandbox_policy;
|
||||
Self::spawn_world_writable_scan(cwd, env_map, logs_base_dir, sandbox_policy, tx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,9 +300,11 @@ impl App {
|
||||
.set_approval_policy(self.config.permissions.approval_policy.value());
|
||||
}
|
||||
if sandbox_policy_override.is_some()
|
||||
&& let Err(err) = self
|
||||
.chat_widget
|
||||
.set_sandbox_policy(self.config.permissions.sandbox_policy.get().clone())
|
||||
&& let Err(err) = self.chat_widget.set_sandbox_policy(
|
||||
self.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(self.config.cwd.as_path()),
|
||||
)
|
||||
{
|
||||
tracing::error!(
|
||||
error = %err,
|
||||
@@ -312,8 +314,11 @@ impl App {
|
||||
.add_error_message(format!("Failed to enable Auto-review: {err}"));
|
||||
}
|
||||
if sandbox_policy_override.is_some() {
|
||||
self.runtime_sandbox_policy_override =
|
||||
Some(self.config.permissions.sandbox_policy.get().clone());
|
||||
self.runtime_sandbox_policy_override = Some(
|
||||
self.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(self.config.cwd.as_path()),
|
||||
);
|
||||
}
|
||||
|
||||
if approval_policy_override.is_some()
|
||||
|
||||
@@ -834,7 +834,10 @@ impl App {
|
||||
/*hint*/ None,
|
||||
));
|
||||
|
||||
let policy = self.config.permissions.sandbox_policy.get().clone();
|
||||
let policy = self
|
||||
.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(self.config.cwd.as_path());
|
||||
let policy_cwd = self.config.cwd.clone();
|
||||
let command_cwd = self.config.cwd.clone();
|
||||
let env_map: std::collections::HashMap<String, String> =
|
||||
@@ -1245,8 +1248,11 @@ impl App {
|
||||
.add_error_message(format!("Failed to set sandbox policy: {err}"));
|
||||
return Ok(AppRunControl::Continue);
|
||||
}
|
||||
self.runtime_sandbox_policy_override =
|
||||
Some(self.config.permissions.sandbox_policy.get().clone());
|
||||
self.runtime_sandbox_policy_override = Some(
|
||||
self.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(self.config.cwd.as_path()),
|
||||
);
|
||||
self.sync_active_thread_permission_settings_to_cached_session()
|
||||
.await;
|
||||
|
||||
@@ -1269,7 +1275,10 @@ impl App {
|
||||
std::env::vars().collect();
|
||||
let tx = self.app_event_tx.clone();
|
||||
let logs_base_dir = self.config.codex_home.clone();
|
||||
let sandbox_policy = self.config.permissions.sandbox_policy.get().clone();
|
||||
let sandbox_policy = self
|
||||
.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(self.config.cwd.as_path());
|
||||
Self::spawn_world_writable_scan(
|
||||
cwd,
|
||||
env_map,
|
||||
|
||||
@@ -12,7 +12,10 @@ impl App {
|
||||
|
||||
let approval_policy = self.config.permissions.approval_policy.value();
|
||||
let approvals_reviewer = self.config.approvals_reviewer;
|
||||
let sandbox_policy = self.config.permissions.sandbox_policy.get().clone();
|
||||
let sandbox_policy = self
|
||||
.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(self.config.cwd.as_path());
|
||||
let permission_profile = Some(
|
||||
self.chat_widget
|
||||
.config_ref()
|
||||
@@ -45,7 +48,10 @@ impl App {
|
||||
thread_id: ThreadId,
|
||||
thread: &Thread,
|
||||
) -> ThreadSessionState {
|
||||
let sandbox_policy = self.config.permissions.sandbox_policy.get().clone();
|
||||
let sandbox_policy = self
|
||||
.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(self.config.cwd.as_path());
|
||||
let mut session = self
|
||||
.primary_session_configured
|
||||
.clone()
|
||||
@@ -185,8 +191,10 @@ mod tests {
|
||||
app.chat_widget
|
||||
.set_sandbox_policy(expected_sandbox_policy.clone())
|
||||
.expect("set widget sandbox policy");
|
||||
app.config.permissions.sandbox_policy =
|
||||
codex_config::Constrained::allow_any(expected_sandbox_policy.clone());
|
||||
app.config
|
||||
.permissions
|
||||
.set_legacy_sandbox_policy(expected_sandbox_policy.clone(), app.config.cwd.as_path())
|
||||
.expect("set app sandbox policy");
|
||||
|
||||
app.sync_active_thread_permission_settings_to_cached_session()
|
||||
.await;
|
||||
|
||||
@@ -1143,7 +1143,13 @@ fn thread_start_params_from_config(
|
||||
let permission_profile = permission_profile_override_from_config(config, thread_params_mode);
|
||||
let sandbox = permission_profile
|
||||
.is_none()
|
||||
.then(|| sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()))
|
||||
.then(|| {
|
||||
sandbox_mode_from_policy(
|
||||
config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
)
|
||||
})
|
||||
.flatten();
|
||||
ThreadStartParams {
|
||||
model: config.model.clone(),
|
||||
@@ -1170,7 +1176,13 @@ fn thread_resume_params_from_config(
|
||||
let permission_profile = permission_profile_override_from_config(&config, thread_params_mode);
|
||||
let sandbox = permission_profile
|
||||
.is_none()
|
||||
.then(|| sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()))
|
||||
.then(|| {
|
||||
sandbox_mode_from_policy(
|
||||
config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
)
|
||||
})
|
||||
.flatten();
|
||||
ThreadResumeParams {
|
||||
thread_id: thread_id.to_string(),
|
||||
@@ -1196,7 +1208,13 @@ fn thread_fork_params_from_config(
|
||||
let permission_profile = permission_profile_override_from_config(&config, thread_params_mode);
|
||||
let sandbox = permission_profile
|
||||
.is_none()
|
||||
.then(|| sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()))
|
||||
.then(|| {
|
||||
sandbox_mode_from_policy(
|
||||
config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
)
|
||||
})
|
||||
.flatten();
|
||||
ThreadForkParams {
|
||||
thread_id: thread_id.to_string(),
|
||||
@@ -1522,8 +1540,11 @@ mod tests {
|
||||
let temp_dir = tempfile::tempdir().expect("tempdir");
|
||||
let config = build_config(&temp_dir).await;
|
||||
let thread_id = ThreadId::new();
|
||||
let expected_sandbox =
|
||||
sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone());
|
||||
let expected_sandbox = sandbox_mode_from_policy(
|
||||
config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
);
|
||||
|
||||
let start = thread_start_params_from_config(
|
||||
&config,
|
||||
@@ -1564,8 +1585,11 @@ mod tests {
|
||||
let config = build_config(&temp_dir).await;
|
||||
let thread_id = ThreadId::new();
|
||||
let remote_cwd = PathBuf::from("repo/on/server");
|
||||
let expected_sandbox =
|
||||
sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone());
|
||||
let expected_sandbox = sandbox_mode_from_policy(
|
||||
config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
);
|
||||
|
||||
let start = thread_start_params_from_config(
|
||||
&config,
|
||||
|
||||
@@ -6415,7 +6415,9 @@ impl ChatWidget {
|
||||
items,
|
||||
self.config.cwd.to_path_buf(),
|
||||
self.config.permissions.approval_policy.value(),
|
||||
self.config.permissions.sandbox_policy.get().clone(),
|
||||
self.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(self.config.cwd.as_path()),
|
||||
permission_profile,
|
||||
effective_mode.model().to_string(),
|
||||
effective_mode.reasoning_effort(),
|
||||
@@ -9466,7 +9468,10 @@ impl ChatWidget {
|
||||
pub(crate) fn open_permissions_popup(&mut self) {
|
||||
let include_read_only = cfg!(target_os = "windows");
|
||||
let current_approval = self.config.permissions.approval_policy.value();
|
||||
let current_sandbox = self.config.permissions.sandbox_policy.get();
|
||||
let current_sandbox = self
|
||||
.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(self.config.cwd.as_path());
|
||||
let guardian_approval_enabled = self.config.features.enabled(Feature::GuardianApproval);
|
||||
let current_review_policy = self.config.approvals_reviewer;
|
||||
let mut items: Vec<SelectionItem> = Vec::new();
|
||||
@@ -9600,7 +9605,11 @@ impl ChatWidget {
|
||||
name: base_name.clone(),
|
||||
description: base_description.clone(),
|
||||
is_current: current_review_policy == ApprovalsReviewer::User
|
||||
&& Self::preset_matches_current(current_approval, current_sandbox, &preset),
|
||||
&& Self::preset_matches_current(
|
||||
current_approval,
|
||||
¤t_sandbox,
|
||||
&preset,
|
||||
),
|
||||
actions: default_actions,
|
||||
dismiss_on_select: true,
|
||||
disabled_reason: default_disabled_reason,
|
||||
@@ -9617,7 +9626,7 @@ impl ChatWidget {
|
||||
is_current: current_review_policy == ApprovalsReviewer::AutoReview
|
||||
&& Self::preset_matches_current(
|
||||
current_approval,
|
||||
current_sandbox,
|
||||
¤t_sandbox,
|
||||
&preset,
|
||||
),
|
||||
actions: Self::approval_preset_actions(
|
||||
@@ -9638,7 +9647,7 @@ impl ChatWidget {
|
||||
description: base_description,
|
||||
is_current: Self::preset_matches_current(
|
||||
current_approval,
|
||||
current_sandbox,
|
||||
¤t_sandbox,
|
||||
&preset,
|
||||
),
|
||||
actions: default_actions,
|
||||
@@ -9774,7 +9783,10 @@ impl ChatWidget {
|
||||
self.config.codex_home.as_path(),
|
||||
cwd.as_path(),
|
||||
&env_map,
|
||||
self.config.permissions.sandbox_policy.get(),
|
||||
&self
|
||||
.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(self.config.cwd.as_path()),
|
||||
Some(self.config.codex_home.as_path()),
|
||||
) {
|
||||
Ok(_) => None,
|
||||
@@ -9892,7 +9904,14 @@ impl ChatWidget {
|
||||
let mode_label = preset
|
||||
.as_ref()
|
||||
.map(|p| describe_policy(&p.sandbox))
|
||||
.unwrap_or_else(|| describe_policy(self.config.permissions.sandbox_policy.get()));
|
||||
.unwrap_or_else(|| {
|
||||
describe_policy(
|
||||
&self
|
||||
.config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(self.config.cwd.as_path()),
|
||||
)
|
||||
});
|
||||
let info_line = if failed_scan {
|
||||
Line::from(vec![
|
||||
"We couldn't complete the world-writable scan, so protections cannot be verified. "
|
||||
|
||||
@@ -1313,7 +1313,9 @@ pub(crate) fn new_session_info(
|
||||
pub(crate) fn is_yolo_mode(config: &Config) -> bool {
|
||||
has_yolo_permissions(
|
||||
config.permissions.approval_policy.value(),
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -874,9 +874,12 @@ pub async fn run_main(
|
||||
|
||||
set_default_client_residency_requirement(config.enforce_residency.value());
|
||||
|
||||
if let Some(warning) =
|
||||
add_dir_warning_message(&cli.add_dir, config.permissions.sandbox_policy.get())
|
||||
{
|
||||
if let Some(warning) = add_dir_warning_message(
|
||||
&cli.add_dir,
|
||||
&config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
) {
|
||||
#[allow(clippy::print_stderr)]
|
||||
{
|
||||
eprintln!("Error adding directories: {warning}");
|
||||
@@ -2205,7 +2208,9 @@ mod tests {
|
||||
current_date: None,
|
||||
timezone: None,
|
||||
approval_policy: config.permissions.approval_policy.value(),
|
||||
sandbox_policy: config.permissions.sandbox_policy.get().clone(),
|
||||
sandbox_policy: config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
permission_profile: None,
|
||||
network: None,
|
||||
file_system_sandbox_policy: None,
|
||||
|
||||
@@ -254,7 +254,11 @@ impl StatusHistoryCell {
|
||||
),
|
||||
(
|
||||
"sandbox",
|
||||
summarize_sandbox_policy(config.permissions.sandbox_policy.get()),
|
||||
summarize_sandbox_policy(
|
||||
&config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
),
|
||||
),
|
||||
];
|
||||
if config.model_provider.wire_api == WireApi::Responses {
|
||||
@@ -277,7 +281,10 @@ impl StatusHistoryCell {
|
||||
.find(|(k, _)| *k == "approval")
|
||||
.map(|(_, v)| v.clone())
|
||||
.unwrap_or_else(|| "<unknown>".to_string());
|
||||
let sandbox = match config.permissions.sandbox_policy.get() {
|
||||
let sandbox_policy = config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path());
|
||||
let sandbox = match &sandbox_policy {
|
||||
SandboxPolicy::DangerFullAccess => "danger-full-access".to_string(),
|
||||
SandboxPolicy::ReadOnly { .. } => "read-only".to_string(),
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
@@ -294,12 +301,11 @@ impl StatusHistoryCell {
|
||||
}
|
||||
};
|
||||
let permissions = if config.permissions.approval_policy.value() == AskForApproval::OnRequest
|
||||
&& *config.permissions.sandbox_policy.get()
|
||||
== SandboxPolicy::new_workspace_write_policy()
|
||||
&& sandbox_policy == SandboxPolicy::new_workspace_write_policy()
|
||||
{
|
||||
"Default".to_string()
|
||||
} else if config.permissions.approval_policy.value() == AskForApproval::Never
|
||||
&& *config.permissions.sandbox_policy.get() == SandboxPolicy::DangerFullAccess
|
||||
&& sandbox_policy == SandboxPolicy::DangerFullAccess
|
||||
{
|
||||
"Full Access".to_string()
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,11 @@ pub fn create_config_summary_entries(config: &Config, model: &str) -> Vec<(&'sta
|
||||
),
|
||||
(
|
||||
"sandbox",
|
||||
summarize_sandbox_policy(config.permissions.sandbox_policy.get()),
|
||||
summarize_sandbox_policy(
|
||||
&config
|
||||
.permissions
|
||||
.legacy_sandbox_policy(config.cwd.as_path()),
|
||||
),
|
||||
),
|
||||
];
|
||||
if config.model_provider.wire_api == WireApi::Responses {
|
||||
|
||||
Reference in New Issue
Block a user