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
@@ -109,7 +109,8 @@ use codex_app_server_protocol::MockExperimentalMethodParams;
|
||||
use codex_app_server_protocol::MockExperimentalMethodResponse;
|
||||
use codex_app_server_protocol::ModelListParams;
|
||||
use codex_app_server_protocol::ModelListResponse;
|
||||
use codex_app_server_protocol::PermissionProfile as ApiPermissionProfile;
|
||||
use codex_app_server_protocol::PermissionProfileModificationParams;
|
||||
use codex_app_server_protocol::PermissionProfileSelectionParams;
|
||||
use codex_app_server_protocol::PluginDetail;
|
||||
use codex_app_server_protocol::PluginInstallParams;
|
||||
use codex_app_server_protocol::PluginInstallResponse;
|
||||
@@ -2438,7 +2439,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
config,
|
||||
service_name,
|
||||
base_instructions,
|
||||
@@ -2452,11 +2453,11 @@ impl CodexMessageProcessor {
|
||||
environments,
|
||||
persist_extended_history,
|
||||
} = params;
|
||||
if sandbox.is_some() && permission_profile.is_some() {
|
||||
if sandbox.is_some() && permissions.is_some() {
|
||||
self.outgoing
|
||||
.send_error(
|
||||
request_id,
|
||||
invalid_request("`permissionProfile` cannot be combined with `sandbox`"),
|
||||
invalid_request("`permissions` cannot be combined with `sandbox`"),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
@@ -2491,7 +2492,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
personality,
|
||||
@@ -2550,7 +2551,7 @@ impl CodexMessageProcessor {
|
||||
/*approval_policy*/ None,
|
||||
/*approvals_reviewer*/ None,
|
||||
/*sandbox*/ None,
|
||||
/*permission_profile*/ None,
|
||||
/*permissions*/ None,
|
||||
/*base_instructions*/ None,
|
||||
/*developer_instructions*/ None,
|
||||
/*personality*/ None,
|
||||
@@ -2861,8 +2862,9 @@ impl CodexMessageProcessor {
|
||||
&config_snapshot.permission_profile,
|
||||
config_snapshot.cwd.as_path(),
|
||||
);
|
||||
let permission_profile =
|
||||
thread_response_permission_profile(config_snapshot.permission_profile);
|
||||
let active_permission_profile = thread_response_active_permission_profile(
|
||||
config_snapshot.active_permission_profile,
|
||||
);
|
||||
|
||||
let response = ThreadStartResponse {
|
||||
thread: thread.clone(),
|
||||
@@ -2874,7 +2876,8 @@ impl CodexMessageProcessor {
|
||||
approval_policy: config_snapshot.approval_policy.into(),
|
||||
approvals_reviewer: config_snapshot.approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permission_profile: Some(config_snapshot.permission_profile.into()),
|
||||
active_permission_profile,
|
||||
reasoning_effort: config_snapshot.reasoning_effort,
|
||||
};
|
||||
Ok::<_, JSONRPCErrorError>((response, thread_started_notification(thread)))
|
||||
@@ -2920,12 +2923,12 @@ impl CodexMessageProcessor {
|
||||
approval_policy: Option<codex_app_server_protocol::AskForApproval>,
|
||||
approvals_reviewer: Option<codex_app_server_protocol::ApprovalsReviewer>,
|
||||
sandbox: Option<SandboxMode>,
|
||||
permission_profile: Option<ApiPermissionProfile>,
|
||||
permissions: Option<PermissionProfileSelectionParams>,
|
||||
base_instructions: Option<String>,
|
||||
developer_instructions: Option<String>,
|
||||
personality: Option<Personality>,
|
||||
) -> ConfigOverrides {
|
||||
ConfigOverrides {
|
||||
let mut overrides = ConfigOverrides {
|
||||
model,
|
||||
model_provider,
|
||||
service_tier,
|
||||
@@ -2935,14 +2938,15 @@ impl CodexMessageProcessor {
|
||||
approvals_reviewer: approvals_reviewer
|
||||
.map(codex_app_server_protocol::ApprovalsReviewer::to_core),
|
||||
sandbox_mode: sandbox.map(SandboxMode::to_core),
|
||||
permission_profile: permission_profile.map(Into::into),
|
||||
codex_linux_sandbox_exe: self.arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(),
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
personality,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
apply_permission_profile_selection_to_config_overrides(&mut overrides, permissions);
|
||||
overrides
|
||||
}
|
||||
|
||||
async fn thread_archive(&self, request_id: ConnectionRequestId, params: ThreadArchiveParams) {
|
||||
@@ -4223,11 +4227,11 @@ impl CodexMessageProcessor {
|
||||
return;
|
||||
}
|
||||
|
||||
if params.sandbox.is_some() && params.permission_profile.is_some() {
|
||||
if params.sandbox.is_some() && params.permissions.is_some() {
|
||||
self.outgoing
|
||||
.send_error(
|
||||
request_id,
|
||||
invalid_request("`permissionProfile` cannot be combined with `sandbox`"),
|
||||
invalid_request("`permissions` cannot be combined with `sandbox`"),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
@@ -4260,7 +4264,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
config: mut request_overrides,
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
@@ -4295,7 +4299,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
personality,
|
||||
@@ -4401,8 +4405,9 @@ impl CodexMessageProcessor {
|
||||
&config_snapshot.permission_profile,
|
||||
config_snapshot.cwd.as_path(),
|
||||
);
|
||||
let permission_profile =
|
||||
thread_response_permission_profile(config_snapshot.permission_profile.clone());
|
||||
let active_permission_profile = thread_response_active_permission_profile(
|
||||
config_snapshot.active_permission_profile,
|
||||
);
|
||||
|
||||
let response = ThreadResumeResponse {
|
||||
thread,
|
||||
@@ -4414,7 +4419,8 @@ impl CodexMessageProcessor {
|
||||
approval_policy: session_configured.approval_policy.into(),
|
||||
approvals_reviewer: session_configured.approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permission_profile: Some(config_snapshot.permission_profile.into()),
|
||||
active_permission_profile,
|
||||
reasoning_effort: session_configured.reasoning_effort,
|
||||
};
|
||||
|
||||
@@ -4813,7 +4819,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
config: cli_overrides,
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
@@ -4823,9 +4829,9 @@ impl CodexMessageProcessor {
|
||||
} = params;
|
||||
let include_turns = !exclude_turns;
|
||||
let result = async {
|
||||
if sandbox.is_some() && permission_profile.is_some() {
|
||||
if sandbox.is_some() && permissions.is_some() {
|
||||
return Err(invalid_request(
|
||||
"`permissionProfile` cannot be combined with `sandbox`",
|
||||
"`permissions` cannot be combined with `sandbox`",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -4878,7 +4884,7 @@ impl CodexMessageProcessor {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permissions,
|
||||
base_instructions,
|
||||
developer_instructions,
|
||||
/*personality*/ None,
|
||||
@@ -4993,8 +4999,9 @@ impl CodexMessageProcessor {
|
||||
&config_snapshot.permission_profile,
|
||||
config_snapshot.cwd.as_path(),
|
||||
);
|
||||
let permission_profile =
|
||||
thread_response_permission_profile(config_snapshot.permission_profile);
|
||||
let active_permission_profile = thread_response_active_permission_profile(
|
||||
config_snapshot.active_permission_profile,
|
||||
);
|
||||
|
||||
let response = ThreadForkResponse {
|
||||
thread: thread.clone(),
|
||||
@@ -5006,7 +5013,8 @@ impl CodexMessageProcessor {
|
||||
approval_policy: session_configured.approval_policy.into(),
|
||||
approvals_reviewer: session_configured.approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permission_profile: Some(config_snapshot.permission_profile.into()),
|
||||
active_permission_profile,
|
||||
reasoning_effort: session_configured.reasoning_effort,
|
||||
};
|
||||
|
||||
@@ -6581,7 +6589,7 @@ impl CodexMessageProcessor {
|
||||
|| params.approval_policy.is_some()
|
||||
|| params.approvals_reviewer.is_some()
|
||||
|| params.sandbox_policy.is_some()
|
||||
|| params.permission_profile.is_some()
|
||||
|| params.permissions.is_some()
|
||||
|| params.model.is_some()
|
||||
|| params.service_tier.is_some()
|
||||
|| params.effort.is_some()
|
||||
@@ -6589,9 +6597,9 @@ impl CodexMessageProcessor {
|
||||
|| collaboration_mode.is_some()
|
||||
|| params.personality.is_some();
|
||||
|
||||
if params.sandbox_policy.is_some() && params.permission_profile.is_some() {
|
||||
if params.sandbox_policy.is_some() && params.permissions.is_some() {
|
||||
return Err(invalid_request(
|
||||
"`permissionProfile` cannot be combined with `sandboxPolicy`",
|
||||
"`permissions` cannot be combined with `sandboxPolicy`",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -6601,7 +6609,45 @@ impl CodexMessageProcessor {
|
||||
.approvals_reviewer
|
||||
.map(codex_app_server_protocol::ApprovalsReviewer::to_core);
|
||||
let sandbox_policy = params.sandbox_policy.map(|p| p.to_core());
|
||||
let permission_profile = params.permission_profile.map(Into::into);
|
||||
let (permission_profile, active_permission_profile) =
|
||||
if let Some(permissions) = params.permissions {
|
||||
let snapshot = thread.config_snapshot().await;
|
||||
let mut overrides = ConfigOverrides {
|
||||
cwd: cwd.clone(),
|
||||
codex_linux_sandbox_exe: self.arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
apply_permission_profile_selection_to_config_overrides(
|
||||
&mut overrides,
|
||||
Some(permissions),
|
||||
);
|
||||
let config = self
|
||||
.config_manager
|
||||
.load_for_cwd(
|
||||
/*request_overrides*/ None,
|
||||
overrides,
|
||||
Some(snapshot.cwd.to_path_buf()),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| config_load_error(&err))?;
|
||||
// Startup config is allowed to fall back when requirements
|
||||
// disallow a configured profile. An explicit turn request
|
||||
// is different: reject it before accepting user input.
|
||||
if let Some(warning) = config.startup_warnings.iter().find(|warning| {
|
||||
warning.contains("Configured value for `permission_profile` is disallowed")
|
||||
}) {
|
||||
return Err(invalid_request(format!(
|
||||
"invalid turn context override: {warning}"
|
||||
)));
|
||||
}
|
||||
(
|
||||
Some(config.permissions.permission_profile()),
|
||||
config.permissions.active_permission_profile(),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let model = params.model;
|
||||
let effort = params.effort.map(Some);
|
||||
let summary = params.summary;
|
||||
@@ -6619,6 +6665,7 @@ impl CodexMessageProcessor {
|
||||
approvals_reviewer,
|
||||
sandbox_policy: sandbox_policy.clone(),
|
||||
permission_profile: permission_profile.clone(),
|
||||
active_permission_profile: active_permission_profile.clone(),
|
||||
windows_sandbox_level: None,
|
||||
model: model.clone(),
|
||||
effort,
|
||||
@@ -6645,6 +6692,7 @@ impl CodexMessageProcessor {
|
||||
approvals_reviewer,
|
||||
sandbox_policy,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
windows_sandbox_level: None,
|
||||
model,
|
||||
effort,
|
||||
@@ -8314,13 +8362,15 @@ async fn handle_pending_thread_resume_request(
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
permission_profile,
|
||||
active_permission_profile,
|
||||
cwd,
|
||||
reasoning_effort,
|
||||
..
|
||||
} = pending.config_snapshot;
|
||||
let instruction_sources = pending.instruction_sources;
|
||||
let sandbox = thread_response_sandbox_policy(&permission_profile, cwd.as_path());
|
||||
let permission_profile = thread_response_permission_profile(permission_profile);
|
||||
let active_permission_profile =
|
||||
thread_response_active_permission_profile(active_permission_profile);
|
||||
|
||||
let response = ThreadResumeResponse {
|
||||
thread,
|
||||
@@ -8332,7 +8382,8 @@ async fn handle_pending_thread_resume_request(
|
||||
approval_policy: approval_policy.into(),
|
||||
approvals_reviewer: approvals_reviewer.into(),
|
||||
sandbox,
|
||||
permission_profile,
|
||||
permission_profile: Some(permission_profile.into()),
|
||||
active_permission_profile,
|
||||
reasoning_effort,
|
||||
};
|
||||
let token_usage_thread = pending.include_turns.then(|| response.thread.clone());
|
||||
@@ -8559,15 +8610,11 @@ fn collect_resume_override_mismatches(
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(requested_permission_profile) = request.permission_profile.as_ref() {
|
||||
let requested_permission_profile =
|
||||
codex_protocol::models::PermissionProfile::from(requested_permission_profile.clone());
|
||||
if requested_permission_profile != config_snapshot.permission_profile {
|
||||
mismatch_details.push(format!(
|
||||
"permission_profile requested={requested_permission_profile:?} active={:?}",
|
||||
config_snapshot.permission_profile
|
||||
));
|
||||
}
|
||||
if request.permissions.is_some() {
|
||||
mismatch_details.push(format!(
|
||||
"permissions override was provided and ignored while running; active={:?}",
|
||||
config_snapshot.active_permission_profile
|
||||
));
|
||||
}
|
||||
if let Some(requested_personality) = request.personality.as_ref()
|
||||
&& config_snapshot.personality.as_ref() != Some(requested_personality)
|
||||
@@ -9474,10 +9521,29 @@ fn with_thread_spawn_agent_metadata(
|
||||
}
|
||||
}
|
||||
|
||||
fn thread_response_permission_profile(
|
||||
permission_profile: codex_protocol::models::PermissionProfile,
|
||||
) -> Option<codex_app_server_protocol::PermissionProfile> {
|
||||
Some(permission_profile.into())
|
||||
fn thread_response_active_permission_profile(
|
||||
active_permission_profile: Option<codex_protocol::models::ActivePermissionProfile>,
|
||||
) -> Option<codex_app_server_protocol::ActivePermissionProfile> {
|
||||
active_permission_profile.map(Into::into)
|
||||
}
|
||||
|
||||
fn apply_permission_profile_selection_to_config_overrides(
|
||||
overrides: &mut ConfigOverrides,
|
||||
permissions: Option<PermissionProfileSelectionParams>,
|
||||
) {
|
||||
let Some(PermissionProfileSelectionParams::Profile { id, modifications }) = permissions else {
|
||||
return;
|
||||
};
|
||||
overrides.default_permissions = Some(id);
|
||||
overrides
|
||||
.additional_writable_roots
|
||||
.extend(modifications.unwrap_or_default().into_iter().map(
|
||||
|modification| match modification {
|
||||
PermissionProfileModificationParams::AdditionalWritableRoot { path } => {
|
||||
path.to_path_buf()
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn thread_response_sandbox_policy(
|
||||
@@ -9505,6 +9571,13 @@ fn requested_permissions_trust_project(overrides: &ConfigOverrides, cwd: &Path)
|
||||
return true;
|
||||
}
|
||||
|
||||
if matches!(
|
||||
overrides.default_permissions.as_deref(),
|
||||
Some(":workspace" | ":danger-no-sandbox")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
overrides
|
||||
.permission_profile
|
||||
.as_ref()
|
||||
@@ -10021,23 +10094,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_response_permission_profile_preserves_enforcement() {
|
||||
let full_access_profile = codex_protocol::models::PermissionProfile::Disabled;
|
||||
let external_profile = codex_protocol::models::PermissionProfile::External {
|
||||
network: codex_protocol::permissions::NetworkSandboxPolicy::Restricted,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
thread_response_permission_profile(external_profile.clone()),
|
||||
Some(external_profile.into())
|
||||
);
|
||||
assert_eq!(
|
||||
thread_response_permission_profile(full_access_profile.clone()),
|
||||
Some(full_access_profile.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requested_permissions_trust_project_uses_permission_profile_intent() {
|
||||
let cwd = test_path_buf("/tmp/project").abs();
|
||||
@@ -10082,6 +10138,20 @@ mod tests {
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
assert!(requested_permissions_trust_project(
|
||||
&ConfigOverrides {
|
||||
default_permissions: Some(":workspace".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
assert!(requested_permissions_trust_project(
|
||||
&ConfigOverrides {
|
||||
default_permissions: Some(":danger-no-sandbox".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
assert!(!requested_permissions_trust_project(
|
||||
&ConfigOverrides {
|
||||
permission_profile: Some(read_only_profile),
|
||||
@@ -10089,6 +10159,13 @@ mod tests {
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
assert!(!requested_permissions_trust_project(
|
||||
&ConfigOverrides {
|
||||
default_permissions: Some(":read-only".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
cwd.as_path()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -10259,7 +10336,7 @@ mod tests {
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
sandbox: None,
|
||||
permission_profile: None,
|
||||
permissions: None,
|
||||
config: None,
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
@@ -10274,6 +10351,7 @@ mod tests {
|
||||
approval_policy: codex_protocol::protocol::AskForApproval::OnRequest,
|
||||
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
|
||||
permission_profile: codex_protocol::models::PermissionProfile::Disabled,
|
||||
active_permission_profile: None,
|
||||
cwd,
|
||||
ephemeral: false,
|
||||
reasoning_effort: None,
|
||||
|
||||
Reference in new issue
Block a user