mirror of
https://github.com/openai/codex.git
synced 2026-05-01 01:47:18 +00:00
feat: Constrain values for approval_policy (#7778)
Constrain `approval_policy` through new `admin_policy` config. This PR will: 1. Add a `admin_policy` section to config, with a single field (for now) `allowed_approval_policies`. This list constrains the set of user-settable `approval_policy`s. 2. Introduce a new `Constrained<T>` type, which combines a current value and a validator function. The validator function ensures disallowed values are not set. 3. Change the type of `approval_policy` on `Config` and `SessionConfiguration` from `AskForApproval` to `Constrained<AskForApproval>`. The validator function is set by the values passed into `allowed_approval_policies`. 4. `GenericDisplayRow`: add a `disabled_reason: Option<String>`. When set, it disables selection of the value and indicates as such in the menu. This also makes it unselectable with arrow keys or numbers. This is used in the `/approvals` menu. Follow ups are: 1. Do the same thing to `sandbox_policy`. 2. Propagate the allowed set of values through app-server for the extension (though already this should prevent app-server from setting this values, it's just that we want to disable UI elements that are unsettable). Happy to split this PR up if you prefer, into the logical numbered areas above. Especially if there are parts we want to gavel on separately (e.g. admin_policy). Disabled full access: <img width="1680" height="380" alt="image" src="https://github.com/user-attachments/assets/1fb61c8c-1fcb-4dc4-8355-2293edb52ba0" /> Disabled `--yolo` on startup: <img width="749" height="76" alt="image" src="https://github.com/user-attachments/assets/0a1211a0-6eb1-40d6-a1d7-439c41e94ddb" /> CODEX-4087
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used)]
|
||||
|
||||
use anyhow::Result;
|
||||
use codex_core::config::Constrained;
|
||||
use codex_core::features::Feature;
|
||||
use codex_core::protocol::ApplyPatchApprovalRequestEvent;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
@@ -126,7 +127,7 @@ impl ActionKind {
|
||||
);
|
||||
|
||||
let command = format!("python3 -c \"{script}\"");
|
||||
let event = shell_event(call_id, &command, 3_000, sandbox_permissions)?;
|
||||
let event = shell_event(call_id, &command, 5_000, sandbox_permissions)?;
|
||||
Ok((event, Some(command)))
|
||||
}
|
||||
ActionKind::RunCommand { command } => {
|
||||
@@ -1462,7 +1463,7 @@ async fn run_scenario(scenario: &ScenarioSpec) -> Result<()> {
|
||||
let model = model_override.unwrap_or("gpt-5.1");
|
||||
|
||||
let mut builder = test_codex().with_model(model).with_config(move |config| {
|
||||
config.approval_policy = approval_policy;
|
||||
config.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config.sandbox_policy = sandbox_policy.clone();
|
||||
for feature in features {
|
||||
config.features.enable(feature);
|
||||
@@ -1568,7 +1569,7 @@ async fn approving_execpolicy_amendment_persists_policy_and_skips_future_prompts
|
||||
let sandbox_policy = SandboxPolicy::ReadOnly;
|
||||
let sandbox_policy_for_config = sandbox_policy.clone();
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.approval_policy = approval_policy;
|
||||
config.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config.sandbox_policy = sandbox_policy_for_config;
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use codex_core::config::Constrained;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::Op;
|
||||
@@ -61,7 +62,7 @@ async fn codex_delegate_forwards_exec_approval_and_proceeds_on_approval() {
|
||||
// Build a conversation configured to require approvals so the delegate
|
||||
// routes ExecApprovalRequest via the parent.
|
||||
let mut builder = test_codex().with_model("gpt-5.1").with_config(|config| {
|
||||
config.approval_policy = AskForApproval::OnRequest;
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.sandbox_policy = SandboxPolicy::ReadOnly;
|
||||
});
|
||||
let test = builder.build(&server).await.expect("build test codex");
|
||||
@@ -137,7 +138,7 @@ async fn codex_delegate_forwards_patch_approval_and_proceeds_on_decision() {
|
||||
mount_sse_sequence(&server, vec![sse1, sse2]).await;
|
||||
|
||||
let mut builder = test_codex().with_model("gpt-5.1").with_config(|config| {
|
||||
config.approval_policy = AskForApproval::OnRequest;
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
// Use a restricted sandbox so patch approval is required
|
||||
config.sandbox_policy = SandboxPolicy::ReadOnly;
|
||||
config.include_apply_patch_tool = true;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use codex_core::config::Constrained;
|
||||
use codex_core::features::Feature;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
@@ -933,7 +934,7 @@ async fn handle_container_exec_autoapprove_from_config_records_tool_decision() {
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::OnRequest;
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.sandbox_policy = SandboxPolicy::DangerFullAccess;
|
||||
})
|
||||
.build(&server)
|
||||
@@ -982,7 +983,7 @@ async fn handle_container_exec_user_approved_records_tool_decision() {
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -1040,7 +1041,7 @@ async fn handle_container_exec_user_approved_for_session_records_tool_decision()
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -1098,7 +1099,7 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() {
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -1156,7 +1157,7 @@ async fn handle_container_exec_user_denies_records_tool_decision() {
|
||||
.await;
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -1214,7 +1215,7 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision()
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -1273,7 +1274,7 @@ async fn handle_sandbox_error_user_denies_records_tool_decision() {
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
|
||||
@@ -593,7 +593,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
|
||||
.await?;
|
||||
|
||||
let default_cwd = config.cwd.clone();
|
||||
let default_approval_policy = config.approval_policy;
|
||||
let default_approval_policy = config.approval_policy.value();
|
||||
let default_sandbox_policy = config.sandbox_policy.clone();
|
||||
let default_model = session_configured.model;
|
||||
let default_effort = config.model_reasoning_effort;
|
||||
@@ -685,7 +685,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
|
||||
.await?;
|
||||
|
||||
let default_cwd = config.cwd.clone();
|
||||
let default_approval_policy = config.approval_policy;
|
||||
let default_approval_policy = config.approval_policy.value();
|
||||
let default_sandbox_policy = config.sandbox_policy.clone();
|
||||
let default_model = session_configured.model;
|
||||
let default_effort = config.model_reasoning_effort;
|
||||
|
||||
@@ -24,7 +24,7 @@ fn resume_history(
|
||||
) -> InitialHistory {
|
||||
let turn_ctx = TurnContextItem {
|
||||
cwd: config.cwd.clone(),
|
||||
approval_policy: config.approval_policy,
|
||||
approval_policy: config.approval_policy.value(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
model: previous_model.to_string(),
|
||||
effort: config.model_reasoning_effort,
|
||||
|
||||
Reference in New Issue
Block a user