mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat: support allowed_sandbox_modes in requirements.toml (#8298)
This adds support for `allowed_sandbox_modes` in `requirements.toml` and provides legacy support for constraining sandbox modes in `managed_config.toml`. This is converted to `Constrained<SandboxPolicy>` in `ConfigRequirements` and applied to `Config` such that constraints are enforced throughout the harness. Note that, because `managed_config.toml` is deprecated, we do not add support for the new `external-sandbox` variant recently introduced in https://github.com/openai/codex/pull/8290. As noted, that variant is not supported in `config.toml` today, but can be configured programmatically via app server.
This commit is contained in:
committed by
GitHub
Unverified
parent
ec3738b47e
commit
dc61fc5f50
@@ -1186,10 +1186,22 @@ impl CodexMessageProcessor {
|
||||
arg0: None,
|
||||
};
|
||||
|
||||
let effective_policy = params
|
||||
.sandbox_policy
|
||||
.map(|policy| policy.to_core())
|
||||
.unwrap_or_else(|| self.config.sandbox_policy.clone());
|
||||
let requested_policy = params.sandbox_policy.map(|policy| policy.to_core());
|
||||
let effective_policy = match requested_policy {
|
||||
Some(policy) => match self.config.sandbox_policy.can_set(&policy) {
|
||||
Ok(()) => policy,
|
||||
Err(err) => {
|
||||
let error = JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
message: format!("invalid sandbox policy: {err}"),
|
||||
data: None,
|
||||
};
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => self.config.sandbox_policy.get().clone(),
|
||||
};
|
||||
|
||||
let codex_linux_sandbox_exe = self.config.codex_linux_sandbox_exe.clone();
|
||||
let outgoing = self.outgoing.clone();
|
||||
|
||||
@@ -140,7 +140,7 @@ async fn run_command_under_sandbox(
|
||||
use codex_windows_sandbox::run_windows_sandbox_capture;
|
||||
use codex_windows_sandbox::run_windows_sandbox_capture_elevated;
|
||||
|
||||
let policy_str = serde_json::to_string(&config.sandbox_policy)?;
|
||||
let policy_str = serde_json::to_string(config.sandbox_policy.get())?;
|
||||
|
||||
let sandbox_cwd = sandbox_policy_cwd.clone();
|
||||
let cwd_clone = cwd.clone();
|
||||
@@ -216,7 +216,7 @@ async fn run_command_under_sandbox(
|
||||
spawn_command_under_seatbelt(
|
||||
command,
|
||||
cwd,
|
||||
&config.sandbox_policy,
|
||||
config.sandbox_policy.get(),
|
||||
sandbox_policy_cwd.as_path(),
|
||||
stdio_policy,
|
||||
env,
|
||||
@@ -232,7 +232,7 @@ async fn run_command_under_sandbox(
|
||||
codex_linux_sandbox_exe,
|
||||
command,
|
||||
cwd,
|
||||
&config.sandbox_policy,
|
||||
config.sandbox_policy.get(),
|
||||
sandbox_policy_cwd.as_path(),
|
||||
stdio_policy,
|
||||
env,
|
||||
|
||||
@@ -10,7 +10,10 @@ pub fn create_config_summary_entries(config: &Config, model: &str) -> Vec<(&'sta
|
||||
("model", model.to_string()),
|
||||
("provider", config.model_provider_id.clone()),
|
||||
("approval", config.approval_policy.value().to_string()),
|
||||
("sandbox", summarize_sandbox_policy(&config.sandbox_policy)),
|
||||
(
|
||||
"sandbox",
|
||||
summarize_sandbox_policy(config.sandbox_policy.get()),
|
||||
),
|
||||
];
|
||||
if config.model_provider.wire_api == WireApi::Responses {
|
||||
let reasoning_effort = config
|
||||
|
||||
@@ -415,7 +415,7 @@ pub(crate) struct SessionConfiguration {
|
||||
/// When to escalate for approval for execution
|
||||
approval_policy: Constrained<AskForApproval>,
|
||||
/// How to sandbox commands executed in the system
|
||||
sandbox_policy: SandboxPolicy,
|
||||
sandbox_policy: Constrained<SandboxPolicy>,
|
||||
|
||||
/// Working directory that should be treated as the *root* of the
|
||||
/// session. All relative paths supplied by the model as well as the
|
||||
@@ -451,7 +451,7 @@ impl SessionConfiguration {
|
||||
next_configuration.approval_policy.set(approval_policy)?;
|
||||
}
|
||||
if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
|
||||
next_configuration.sandbox_policy = sandbox_policy;
|
||||
next_configuration.sandbox_policy.set(sandbox_policy)?;
|
||||
}
|
||||
if let Some(cwd) = updates.cwd.clone() {
|
||||
next_configuration.cwd = cwd;
|
||||
@@ -526,7 +526,7 @@ impl Session {
|
||||
compact_prompt: session_configuration.compact_prompt.clone(),
|
||||
user_instructions: session_configuration.user_instructions.clone(),
|
||||
approval_policy: session_configuration.approval_policy.value(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.clone(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
|
||||
shell_environment_policy: per_turn_config.shell_environment_policy.clone(),
|
||||
tools_config,
|
||||
ghost_snapshot: per_turn_config.ghost_snapshot.clone(),
|
||||
@@ -643,7 +643,7 @@ impl Session {
|
||||
config.model_context_window,
|
||||
config.model_auto_compact_token_limit,
|
||||
config.approval_policy.value(),
|
||||
config.sandbox_policy.clone(),
|
||||
config.sandbox_policy.get().clone(),
|
||||
config.mcp_servers.keys().map(String::as_str).collect(),
|
||||
config.active_profile.clone(),
|
||||
);
|
||||
@@ -693,7 +693,7 @@ impl Session {
|
||||
model: session_configuration.model.clone(),
|
||||
model_provider_id: config.model_provider_id.clone(),
|
||||
approval_policy: session_configuration.approval_policy.value(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.clone(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
|
||||
cwd: session_configuration.cwd.clone(),
|
||||
reasoning_effort: session_configuration.model_reasoning_effort,
|
||||
history_log_id,
|
||||
@@ -710,7 +710,7 @@ impl Session {
|
||||
// Construct sandbox_state before initialize() so it can be sent to each
|
||||
// MCP server immediately after it becomes ready (avoiding blocking).
|
||||
let sandbox_state = SandboxState {
|
||||
sandbox_policy: session_configuration.sandbox_policy.clone(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: session_configuration.cwd.clone(),
|
||||
};
|
||||
@@ -891,7 +891,7 @@ impl Session {
|
||||
|
||||
if sandbox_policy_changed {
|
||||
let sandbox_state = SandboxState {
|
||||
sandbox_policy: per_turn_config.sandbox_policy.clone(),
|
||||
sandbox_policy: per_turn_config.sandbox_policy.get().clone(),
|
||||
codex_linux_sandbox_exe: per_turn_config.codex_linux_sandbox_exe.clone(),
|
||||
sandbox_cwd: per_turn_config.cwd.clone(),
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ pub struct Config {
|
||||
/// Approval policy for executing commands.
|
||||
pub approval_policy: Constrained<AskForApproval>,
|
||||
|
||||
pub sandbox_policy: SandboxPolicy,
|
||||
pub sandbox_policy: Constrained<SandboxPolicy>,
|
||||
|
||||
/// True if the user passed in an override or set a value in config.toml
|
||||
/// for either of approval_policy or sandbox_mode.
|
||||
@@ -1235,11 +1235,15 @@ impl Config {
|
||||
// Config.
|
||||
let ConfigRequirements {
|
||||
approval_policy: mut constrained_approval_policy,
|
||||
sandbox_policy: mut constrained_sandbox_policy,
|
||||
} = requirements;
|
||||
|
||||
constrained_approval_policy
|
||||
.set(approval_policy)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{e}")))?;
|
||||
constrained_sandbox_policy
|
||||
.set(sandbox_policy)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{e}")))?;
|
||||
|
||||
let config = Self {
|
||||
model,
|
||||
@@ -1250,7 +1254,7 @@ impl Config {
|
||||
model_provider,
|
||||
cwd: resolved_cwd,
|
||||
approval_policy: constrained_approval_policy,
|
||||
sandbox_policy,
|
||||
sandbox_policy: constrained_sandbox_policy,
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode,
|
||||
forced_auto_mode_downgraded_on_windows,
|
||||
shell_environment_policy,
|
||||
@@ -1672,12 +1676,12 @@ trust_level = "trusted"
|
||||
config.forced_auto_mode_downgraded_on_windows,
|
||||
"expected workspace-write request to be downgraded on Windows"
|
||||
);
|
||||
match config.sandbox_policy {
|
||||
SandboxPolicy::ReadOnly => {}
|
||||
match config.sandbox_policy.get() {
|
||||
&SandboxPolicy::ReadOnly => {}
|
||||
other => panic!("expected read-only policy on Windows, got {other:?}"),
|
||||
}
|
||||
} else {
|
||||
match config.sandbox_policy {
|
||||
match config.sandbox_policy.get() {
|
||||
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
|
||||
assert_eq!(
|
||||
writable_roots
|
||||
@@ -1809,8 +1813,8 @@ trust_level = "trusted"
|
||||
)?;
|
||||
|
||||
assert!(matches!(
|
||||
config.sandbox_policy,
|
||||
SandboxPolicy::DangerFullAccess
|
||||
config.sandbox_policy.get(),
|
||||
&SandboxPolicy::DangerFullAccess
|
||||
));
|
||||
assert!(config.did_user_set_custom_approval_policy_or_sandbox_mode);
|
||||
|
||||
@@ -1846,11 +1850,14 @@ trust_level = "trusted"
|
||||
)?;
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
assert!(matches!(config.sandbox_policy, SandboxPolicy::ReadOnly));
|
||||
assert!(matches!(
|
||||
config.sandbox_policy.get(),
|
||||
SandboxPolicy::ReadOnly
|
||||
));
|
||||
assert!(config.forced_auto_mode_downgraded_on_windows);
|
||||
} else {
|
||||
assert!(matches!(
|
||||
config.sandbox_policy,
|
||||
config.sandbox_policy.get(),
|
||||
SandboxPolicy::WorkspaceWrite { .. }
|
||||
));
|
||||
assert!(!config.forced_auto_mode_downgraded_on_windows);
|
||||
@@ -3048,7 +3055,7 @@ model_verbosity = "high"
|
||||
model_provider_id: "openai".to_string(),
|
||||
model_provider: fixture.openai_provider.clone(),
|
||||
approval_policy: Constrained::allow_any(AskForApproval::Never),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
forced_auto_mode_downgraded_on_windows: false,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -3123,7 +3130,7 @@ model_verbosity = "high"
|
||||
model_provider_id: "openai-chat-completions".to_string(),
|
||||
model_provider: fixture.openai_chat_completions_provider.clone(),
|
||||
approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
forced_auto_mode_downgraded_on_windows: false,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -3213,7 +3220,7 @@ model_verbosity = "high"
|
||||
model_provider_id: "openai".to_string(),
|
||||
model_provider: fixture.openai_provider.clone(),
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
forced_auto_mode_downgraded_on_windows: false,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -3289,7 +3296,7 @@ model_verbosity = "high"
|
||||
model_provider_id: "openai".to_string(),
|
||||
model_provider: fixture.openai_provider.clone(),
|
||||
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
|
||||
did_user_set_custom_approval_policy_or_sandbox_mode: true,
|
||||
forced_auto_mode_downgraded_on_windows: false,
|
||||
shell_environment_policy: ShellEnvironmentPolicy::default(),
|
||||
@@ -3634,12 +3641,15 @@ trust_level = "untrusted"
|
||||
// Verify that untrusted projects still get WorkspaceWrite sandbox (or ReadOnly on Windows)
|
||||
if cfg!(target_os = "windows") {
|
||||
assert!(
|
||||
matches!(config.sandbox_policy, SandboxPolicy::ReadOnly),
|
||||
matches!(config.sandbox_policy.get(), SandboxPolicy::ReadOnly),
|
||||
"Expected ReadOnly on Windows"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
matches!(config.sandbox_policy, SandboxPolicy::WorkspaceWrite { .. }),
|
||||
matches!(
|
||||
config.sandbox_policy.get(),
|
||||
SandboxPolicy::WorkspaceWrite { .. }
|
||||
),
|
||||
"Expected WorkspaceWrite sandbox for untrusted project"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::config::Constrained;
|
||||
@@ -9,12 +11,14 @@ use crate::config::ConstraintError;
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ConfigRequirements {
|
||||
pub approval_policy: Constrained<AskForApproval>,
|
||||
pub sandbox_policy: Constrained<SandboxPolicy>,
|
||||
}
|
||||
|
||||
impl Default for ConfigRequirements {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
approval_policy: Constrained::allow_any_from_default(),
|
||||
sandbox_policy: Constrained::allow_any(SandboxPolicy::ReadOnly),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +27,34 @@ impl Default for ConfigRequirements {
|
||||
#[derive(Deserialize, Debug, Clone, Default, PartialEq)]
|
||||
pub struct ConfigRequirementsToml {
|
||||
pub allowed_approval_policies: Option<Vec<AskForApproval>>,
|
||||
pub allowed_sandbox_modes: Option<Vec<SandboxModeRequirement>>,
|
||||
}
|
||||
|
||||
/// Currently, `external-sandbox` is not supported in config.toml, but it is
|
||||
/// supported through programmatic use.
|
||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
pub enum SandboxModeRequirement {
|
||||
#[serde(rename = "read-only")]
|
||||
ReadOnly,
|
||||
|
||||
#[serde(rename = "workspace-write")]
|
||||
WorkspaceWrite,
|
||||
|
||||
#[serde(rename = "danger-full-access")]
|
||||
DangerFullAccess,
|
||||
|
||||
#[serde(rename = "external-sandbox")]
|
||||
ExternalSandbox,
|
||||
}
|
||||
|
||||
impl From<SandboxMode> for SandboxModeRequirement {
|
||||
fn from(mode: SandboxMode) -> Self {
|
||||
match mode {
|
||||
SandboxMode::ReadOnly => SandboxModeRequirement::ReadOnly,
|
||||
SandboxMode::WorkspaceWrite => SandboxModeRequirement::WorkspaceWrite,
|
||||
SandboxMode::DangerFullAccess => SandboxModeRequirement::DangerFullAccess,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigRequirementsToml {
|
||||
@@ -41,7 +73,7 @@ impl ConfigRequirementsToml {
|
||||
};
|
||||
}
|
||||
|
||||
fill_missing_take!(self, other, { allowed_approval_policies });
|
||||
fill_missing_take!(self, other, { allowed_approval_policies, allowed_sandbox_modes });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,12 +81,13 @@ impl TryFrom<ConfigRequirementsToml> for ConfigRequirements {
|
||||
type Error = ConstraintError;
|
||||
|
||||
fn try_from(toml: ConfigRequirementsToml) -> Result<Self, Self::Error> {
|
||||
let approval_policy: Constrained<AskForApproval> = match toml.allowed_approval_policies {
|
||||
let ConfigRequirementsToml {
|
||||
allowed_approval_policies,
|
||||
allowed_sandbox_modes,
|
||||
} = toml;
|
||||
let approval_policy: Constrained<AskForApproval> = match allowed_approval_policies {
|
||||
Some(policies) => {
|
||||
let default_value = AskForApproval::default();
|
||||
if policies.contains(&default_value) {
|
||||
Constrained::allow_values(default_value, policies)?
|
||||
} else if let Some(first) = policies.first() {
|
||||
if let Some(first) = policies.first() {
|
||||
Constrained::allow_values(*first, policies)?
|
||||
} else {
|
||||
return Err(ConstraintError::empty_field("allowed_approval_policies"));
|
||||
@@ -62,7 +95,51 @@ impl TryFrom<ConfigRequirementsToml> for ConfigRequirements {
|
||||
}
|
||||
None => Constrained::allow_any_from_default(),
|
||||
};
|
||||
Ok(ConfigRequirements { approval_policy })
|
||||
|
||||
// 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::ReadOnly;
|
||||
let sandbox_policy: Constrained<SandboxPolicy> = match allowed_sandbox_modes {
|
||||
Some(modes) => {
|
||||
if !modes.contains(&SandboxModeRequirement::ReadOnly) {
|
||||
return Err(ConstraintError::invalid_value(
|
||||
"allowed_sandbox_modes",
|
||||
"must include 'read-only' to allow any SandboxPolicy",
|
||||
));
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
if modes.contains(&mode) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ConstraintError::invalid_value(
|
||||
format!("{candidate:?}"),
|
||||
format!("{modes:?}"),
|
||||
))
|
||||
}
|
||||
})?
|
||||
}
|
||||
None => Constrained::allow_any(default_sandbox_policy),
|
||||
};
|
||||
Ok(ConfigRequirements {
|
||||
approval_policy,
|
||||
sandbox_policy,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +147,8 @@ impl TryFrom<ConfigRequirementsToml> for ConfigRequirements {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
use codex_protocol::protocol::NetworkAccess;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use toml::from_str;
|
||||
|
||||
@@ -104,4 +183,105 @@ mod tests {
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_allowed_approval_policies() -> Result<()> {
|
||||
let toml_str = r#"
|
||||
allowed_approval_policies = ["untrusted", "on-request"]
|
||||
"#;
|
||||
let config: ConfigRequirementsToml = from_str(toml_str)?;
|
||||
let requirements = ConfigRequirements::try_from(config)?;
|
||||
|
||||
assert_eq!(
|
||||
requirements.approval_policy.value(),
|
||||
AskForApproval::UnlessTrusted,
|
||||
"currently, there is no way to specify the default value for approval policy in the toml, so it picks the first allowed value"
|
||||
);
|
||||
assert!(
|
||||
requirements
|
||||
.approval_policy
|
||||
.can_set(&AskForApproval::UnlessTrusted)
|
||||
.is_ok()
|
||||
);
|
||||
assert_eq!(
|
||||
requirements
|
||||
.approval_policy
|
||||
.can_set(&AskForApproval::OnFailure),
|
||||
Err(ConstraintError::InvalidValue {
|
||||
candidate: "OnFailure".into(),
|
||||
allowed: "[UnlessTrusted, OnRequest]".into(),
|
||||
})
|
||||
);
|
||||
assert!(
|
||||
requirements
|
||||
.approval_policy
|
||||
.can_set(&AskForApproval::OnRequest)
|
||||
.is_ok()
|
||||
);
|
||||
assert_eq!(
|
||||
requirements.approval_policy.can_set(&AskForApproval::Never),
|
||||
Err(ConstraintError::InvalidValue {
|
||||
candidate: "Never".into(),
|
||||
allowed: "[UnlessTrusted, OnRequest]".into(),
|
||||
})
|
||||
);
|
||||
assert!(
|
||||
requirements
|
||||
.sandbox_policy
|
||||
.can_set(&SandboxPolicy::ReadOnly)
|
||||
.is_ok()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_allowed_sandbox_modes() -> Result<()> {
|
||||
let toml_str = r#"
|
||||
allowed_sandbox_modes = ["read-only", "workspace-write"]
|
||||
"#;
|
||||
let config: ConfigRequirementsToml = from_str(toml_str)?;
|
||||
let requirements = ConfigRequirements::try_from(config)?;
|
||||
|
||||
let root = if cfg!(windows) { "C:\\repo" } else { "/repo" };
|
||||
assert!(
|
||||
requirements
|
||||
.sandbox_policy
|
||||
.can_set(&SandboxPolicy::ReadOnly)
|
||||
.is_ok()
|
||||
);
|
||||
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,
|
||||
})
|
||||
.is_ok()
|
||||
);
|
||||
assert_eq!(
|
||||
requirements
|
||||
.sandbox_policy
|
||||
.can_set(&SandboxPolicy::DangerFullAccess),
|
||||
Err(ConstraintError::InvalidValue {
|
||||
candidate: "DangerFullAccess".into(),
|
||||
allowed: "[ReadOnly, WorkspaceWrite]".into(),
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
requirements
|
||||
.sandbox_policy
|
||||
.can_set(&SandboxPolicy::ExternalSandbox {
|
||||
network_access: NetworkAccess::Restricted,
|
||||
}),
|
||||
Err(ConstraintError::InvalidValue {
|
||||
candidate: "ExternalSandbox { network_access: Restricted }".into(),
|
||||
allowed: "[ReadOnly, WorkspaceWrite]".into(),
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::config::CONFIG_TOML_FILE;
|
||||
use crate::config_loader::config_requirements::ConfigRequirementsToml;
|
||||
use crate::config_loader::layer_io::LoadedConfigLayers;
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use serde::Deserialize;
|
||||
@@ -238,17 +239,23 @@ async fn load_requirements_from_legacy_scheme(
|
||||
#[derive(Deserialize, Debug, Clone, Default, PartialEq)]
|
||||
struct LegacyManagedConfigToml {
|
||||
approval_policy: Option<AskForApproval>,
|
||||
sandbox_mode: Option<SandboxMode>,
|
||||
}
|
||||
|
||||
impl From<LegacyManagedConfigToml> for ConfigRequirementsToml {
|
||||
fn from(legacy: LegacyManagedConfigToml) -> Self {
|
||||
let mut config_requirements_toml = ConfigRequirementsToml::default();
|
||||
|
||||
let LegacyManagedConfigToml { approval_policy } = legacy;
|
||||
let LegacyManagedConfigToml {
|
||||
approval_policy,
|
||||
sandbox_mode,
|
||||
} = legacy;
|
||||
if let Some(approval_policy) = approval_policy {
|
||||
config_requirements_toml.allowed_approval_policies = Some(vec![approval_policy]);
|
||||
}
|
||||
|
||||
if let Some(sandbox_mode) = sandbox_mode {
|
||||
config_requirements_toml.allowed_sandbox_modes = Some(vec![sandbox_mode.into()]);
|
||||
}
|
||||
config_requirements_toml
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ allowed_approval_policies = ["never", "on-request"]
|
||||
let config_requirements: ConfigRequirements = config_requirements_toml.try_into()?;
|
||||
assert_eq!(
|
||||
config_requirements.approval_policy.value(),
|
||||
AskForApproval::OnRequest
|
||||
AskForApproval::Never
|
||||
);
|
||||
config_requirements
|
||||
.approval_policy
|
||||
|
||||
@@ -1464,7 +1464,7 @@ async fn run_scenario(scenario: &ScenarioSpec) -> Result<()> {
|
||||
|
||||
let mut builder = test_codex().with_model(model).with_config(move |config| {
|
||||
config.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config.sandbox_policy = sandbox_policy.clone();
|
||||
config.sandbox_policy = Constrained::allow_any(sandbox_policy.clone());
|
||||
for feature in features {
|
||||
config.features.enable(feature);
|
||||
}
|
||||
@@ -1570,7 +1570,7 @@ async fn approving_execpolicy_amendment_persists_policy_and_skips_future_prompts
|
||||
let sandbox_policy_for_config = sandbox_policy.clone();
|
||||
let mut builder = test_codex().with_config(move |config| {
|
||||
config.approval_policy = Constrained::allow_any(approval_policy);
|
||||
config.sandbox_policy = sandbox_policy_for_config;
|
||||
config.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
let allow_prefix_path = test.cwd.path().join("allow-prefix.txt");
|
||||
|
||||
@@ -63,7 +63,7 @@ async fn codex_delegate_forwards_exec_approval_and_proceeds_on_approval() {
|
||||
// routes ExecApprovalRequest via the parent.
|
||||
let mut builder = test_codex().with_model("gpt-5.1").with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.sandbox_policy = SandboxPolicy::ReadOnly;
|
||||
config.sandbox_policy = Constrained::allow_any(SandboxPolicy::ReadOnly);
|
||||
});
|
||||
let test = builder.build(&server).await.expect("build test codex");
|
||||
|
||||
@@ -140,7 +140,7 @@ async fn codex_delegate_forwards_patch_approval_and_proceeds_on_decision() {
|
||||
let mut builder = test_codex().with_model("gpt-5.1").with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
// Use a restricted sandbox so patch approval is required
|
||||
config.sandbox_policy = SandboxPolicy::ReadOnly;
|
||||
config.sandbox_policy = Constrained::allow_any(SandboxPolicy::ReadOnly);
|
||||
config.include_apply_patch_tool = true;
|
||||
});
|
||||
let test = builder.build(&server).await.expect("build test codex");
|
||||
|
||||
@@ -935,7 +935,7 @@ async fn handle_container_exec_autoapprove_from_config_records_tool_decision() {
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
config.sandbox_policy = SandboxPolicy::DangerFullAccess;
|
||||
config.sandbox_policy = Constrained::allow_any(SandboxPolicy::DangerFullAccess);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
|
||||
@@ -605,7 +605,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() -> a
|
||||
|
||||
let default_cwd = config.cwd.clone();
|
||||
let default_approval_policy = config.approval_policy.value();
|
||||
let default_sandbox_policy = config.sandbox_policy.clone();
|
||||
let default_sandbox_policy = config.sandbox_policy.get();
|
||||
let default_model = session_configured.model;
|
||||
let default_effort = config.model_reasoning_effort;
|
||||
let default_summary = config.model_reasoning_summary;
|
||||
@@ -695,7 +695,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
|
||||
|
||||
let default_cwd = config.cwd.clone();
|
||||
let default_approval_policy = config.approval_policy.value();
|
||||
let default_sandbox_policy = config.sandbox_policy.clone();
|
||||
let default_sandbox_policy = config.sandbox_policy.get();
|
||||
let default_model = session_configured.model;
|
||||
let default_effort = config.model_reasoning_effort;
|
||||
let default_summary = config.model_reasoning_summary;
|
||||
|
||||
@@ -24,7 +24,7 @@ fn resume_history(
|
||||
let turn_ctx = TurnContextItem {
|
||||
cwd: config.cwd.clone(),
|
||||
approval_policy: config.approval_policy.value(),
|
||||
sandbox_policy: config.sandbox_policy.clone(),
|
||||
sandbox_policy: config.sandbox_policy.get().clone(),
|
||||
model: previous_model.to_string(),
|
||||
effort: config.model_reasoning_effort,
|
||||
summary: config.model_reasoning_summary,
|
||||
|
||||
@@ -415,7 +415,10 @@ async fn shell_timeout_handles_background_grandchild_stdout() -> Result<()> {
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex().with_model("gpt-5.1").with_config(|config| {
|
||||
config.sandbox_policy = SandboxPolicy::DangerFullAccess;
|
||||
config
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::DangerFullAccess)
|
||||
.expect("set sandbox policy");
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
@@ -508,7 +511,9 @@ async fn shell_spawn_failure_truncates_exec_error() -> Result<()> {
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let mut builder = test_codex().with_config(|cfg| {
|
||||
cfg.sandbox_policy = SandboxPolicy::DangerFullAccess;
|
||||
cfg.sandbox_policy
|
||||
.set(SandboxPolicy::DangerFullAccess)
|
||||
.expect("set sandbox policy");
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
|
||||
@@ -259,7 +259,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
|
||||
|
||||
let default_cwd = config.cwd.to_path_buf();
|
||||
let default_approval_policy = config.approval_policy.value();
|
||||
let default_sandbox_policy = config.sandbox_policy.clone();
|
||||
let default_sandbox_policy = config.sandbox_policy.get();
|
||||
let default_effort = config.model_reasoning_effort;
|
||||
let default_summary = config.model_reasoning_summary;
|
||||
|
||||
@@ -411,7 +411,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
|
||||
items,
|
||||
cwd: default_cwd,
|
||||
approval_policy: default_approval_policy,
|
||||
sandbox_policy: default_sandbox_policy,
|
||||
sandbox_policy: default_sandbox_policy.clone(),
|
||||
model: default_model,
|
||||
effort: default_effort,
|
||||
summary: default_summary,
|
||||
|
||||
+17
-7
@@ -453,7 +453,7 @@ impl App {
|
||||
{
|
||||
let should_check = codex_core::get_platform_sandbox().is_some()
|
||||
&& matches!(
|
||||
app.config.sandbox_policy,
|
||||
app.config.sandbox_policy.get(),
|
||||
codex_core::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_core::protocol::SandboxPolicy::ReadOnly
|
||||
)
|
||||
@@ -467,7 +467,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.sandbox_policy.clone();
|
||||
let sandbox_policy = app.config.sandbox_policy.get().clone();
|
||||
Self::spawn_world_writable_scan(cwd, env_map, logs_base_dir, sandbox_policy, tx);
|
||||
}
|
||||
}
|
||||
@@ -904,19 +904,29 @@ impl App {
|
||||
AppEvent::UpdateSandboxPolicy(policy) => {
|
||||
#[cfg(target_os = "windows")]
|
||||
let policy_is_workspace_write_or_ro = matches!(
|
||||
policy,
|
||||
&policy,
|
||||
codex_core::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_core::protocol::SandboxPolicy::ReadOnly
|
||||
);
|
||||
|
||||
self.config.sandbox_policy = policy.clone();
|
||||
if let Err(err) = self.config.sandbox_policy.set(policy.clone()) {
|
||||
tracing::warn!(%err, "failed to set sandbox policy on app config");
|
||||
self.chat_widget
|
||||
.add_error_message(format!("Failed to set sandbox policy: {err}"));
|
||||
return Ok(true);
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
if !matches!(policy, codex_core::protocol::SandboxPolicy::ReadOnly)
|
||||
if !matches!(&policy, codex_core::protocol::SandboxPolicy::ReadOnly)
|
||||
|| codex_core::get_platform_sandbox().is_some()
|
||||
{
|
||||
self.config.forced_auto_mode_downgraded_on_windows = false;
|
||||
}
|
||||
self.chat_widget.set_sandbox_policy(policy);
|
||||
if let Err(err) = self.chat_widget.set_sandbox_policy(policy) {
|
||||
tracing::warn!(%err, "failed to set sandbox policy on chat config");
|
||||
self.chat_widget
|
||||
.add_error_message(format!("Failed to set sandbox policy: {err}"));
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// If sandbox policy becomes workspace-write or read-only, run the Windows world-writable scan.
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -936,7 +946,7 @@ 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.sandbox_policy.clone();
|
||||
let sandbox_policy = self.config.sandbox_policy.get().clone();
|
||||
Self::spawn_world_writable_scan(
|
||||
cwd,
|
||||
env_map,
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::time::Duration;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_backend_client::Client as BackendClient;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConstraintResult;
|
||||
use codex_core::config::types::Notifications;
|
||||
use codex_core::features::FEATURES;
|
||||
use codex_core::features::Feature;
|
||||
@@ -2725,12 +2726,12 @@ impl ChatWidget {
|
||||
/// Open a popup to choose the approvals mode (ask for approval policy + sandbox policy).
|
||||
pub(crate) fn open_approvals_popup(&mut self) {
|
||||
let current_approval = self.config.approval_policy.value();
|
||||
let current_sandbox = self.config.sandbox_policy.clone();
|
||||
let current_sandbox = self.config.sandbox_policy.get();
|
||||
let mut items: Vec<SelectionItem> = Vec::new();
|
||||
let presets: Vec<ApprovalPreset> = builtin_approval_presets();
|
||||
for preset in presets.into_iter() {
|
||||
let is_current =
|
||||
Self::preset_matches_current(current_approval, ¤t_sandbox, &preset);
|
||||
Self::preset_matches_current(current_approval, current_sandbox, &preset);
|
||||
let name = preset.label.to_string();
|
||||
let description = Some(preset.description.to_string());
|
||||
let disabled_reason = match self.config.approval_policy.can_set(&preset.approval) {
|
||||
@@ -2879,7 +2880,7 @@ impl ChatWidget {
|
||||
self.config.codex_home.as_path(),
|
||||
cwd.as_path(),
|
||||
&env_map,
|
||||
&self.config.sandbox_policy,
|
||||
self.config.sandbox_policy.get(),
|
||||
Some(self.config.codex_home.as_path()),
|
||||
) {
|
||||
Ok(_) => None,
|
||||
@@ -2978,7 +2979,7 @@ impl ChatWidget {
|
||||
let mode_label = preset
|
||||
.as_ref()
|
||||
.map(|p| describe_policy(&p.sandbox))
|
||||
.unwrap_or_else(|| describe_policy(&self.config.sandbox_policy));
|
||||
.unwrap_or_else(|| describe_policy(self.config.sandbox_policy.get()));
|
||||
let info_line = if failed_scan {
|
||||
Line::from(vec![
|
||||
"We couldn't complete the world-writable scan, so protections cannot be verified. "
|
||||
@@ -3151,17 +3152,19 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
/// Set the sandbox policy in the widget's config copy.
|
||||
pub(crate) fn set_sandbox_policy(&mut self, policy: SandboxPolicy) {
|
||||
pub(crate) fn set_sandbox_policy(&mut self, policy: SandboxPolicy) -> ConstraintResult<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let should_clear_downgrade = !matches!(policy, SandboxPolicy::ReadOnly)
|
||||
let should_clear_downgrade = !matches!(&policy, SandboxPolicy::ReadOnly)
|
||||
|| codex_core::get_platform_sandbox().is_some();
|
||||
|
||||
self.config.sandbox_policy = policy;
|
||||
self.config.sandbox_policy.set(policy)?;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if should_clear_downgrade {
|
||||
self.config.forced_auto_mode_downgraded_on_windows = false;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn set_feature_enabled(&mut self, feature: Feature, enabled: bool) {
|
||||
|
||||
@@ -217,7 +217,7 @@ pub async fn run_main(
|
||||
|
||||
let config = load_config_or_exit(cli_kv_overrides.clone(), overrides.clone()).await;
|
||||
|
||||
if let Some(warning) = add_dir_warning_message(&cli.add_dir, &config.sandbox_policy) {
|
||||
if let Some(warning) = add_dir_warning_message(&cli.add_dir, config.sandbox_policy.get()) {
|
||||
#[allow(clippy::print_stderr)]
|
||||
{
|
||||
eprintln!("Error adding directories: {warning}");
|
||||
|
||||
@@ -119,7 +119,7 @@ impl StatusHistoryCell {
|
||||
.find(|(k, _)| *k == "approval")
|
||||
.map(|(_, v)| v.clone())
|
||||
.unwrap_or_else(|| "<unknown>".to_string());
|
||||
let sandbox = match &config.sandbox_policy {
|
||||
let sandbox = match config.sandbox_policy.get() {
|
||||
SandboxPolicy::DangerFullAccess => "danger-full-access".to_string(),
|
||||
SandboxPolicy::ReadOnly => "read-only".to_string(),
|
||||
SandboxPolicy::WorkspaceWrite { .. } => "workspace-write".to_string(),
|
||||
|
||||
@@ -90,12 +90,15 @@ async fn status_snapshot_includes_reasoning_details() {
|
||||
config.model_provider_id = "openai".to_string();
|
||||
config.model_reasoning_effort = Some(ReasoningEffort::High);
|
||||
config.model_reasoning_summary = ReasoningSummary::Detailed;
|
||||
config.sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
};
|
||||
config
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
})
|
||||
.expect("set sandbox policy");
|
||||
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
|
||||
|
||||
@@ -510,7 +510,7 @@ impl App {
|
||||
{
|
||||
let should_check = codex_core::get_platform_sandbox().is_some()
|
||||
&& matches!(
|
||||
app.config.sandbox_policy,
|
||||
app.config.sandbox_policy.get(),
|
||||
codex_core::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_core::protocol::SandboxPolicy::ReadOnly
|
||||
)
|
||||
@@ -524,7 +524,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.sandbox_policy.clone();
|
||||
let sandbox_policy = app.config.sandbox_policy.get().clone();
|
||||
Self::spawn_world_writable_scan(cwd, env_map, logs_base_dir, sandbox_policy, tx);
|
||||
}
|
||||
}
|
||||
@@ -1746,19 +1746,29 @@ impl App {
|
||||
AppEvent::UpdateSandboxPolicy(policy) => {
|
||||
#[cfg(target_os = "windows")]
|
||||
let policy_is_workspace_write_or_ro = matches!(
|
||||
policy,
|
||||
&policy,
|
||||
codex_core::protocol::SandboxPolicy::WorkspaceWrite { .. }
|
||||
| codex_core::protocol::SandboxPolicy::ReadOnly
|
||||
);
|
||||
|
||||
self.config.sandbox_policy = policy.clone();
|
||||
if let Err(err) = self.config.sandbox_policy.set(policy.clone()) {
|
||||
tracing::warn!(%err, "failed to set sandbox policy on app config");
|
||||
self.chat_widget
|
||||
.add_error_message(format!("Failed to set sandbox policy: {err}"));
|
||||
return Ok(true);
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
if !matches!(policy, codex_core::protocol::SandboxPolicy::ReadOnly)
|
||||
if !matches!(&policy, codex_core::protocol::SandboxPolicy::ReadOnly)
|
||||
|| codex_core::get_platform_sandbox().is_some()
|
||||
{
|
||||
self.config.forced_auto_mode_downgraded_on_windows = false;
|
||||
}
|
||||
self.chat_widget.set_sandbox_policy(policy);
|
||||
if let Err(err) = self.chat_widget.set_sandbox_policy(policy) {
|
||||
tracing::warn!(%err, "failed to set sandbox policy on chat config");
|
||||
self.chat_widget
|
||||
.add_error_message(format!("Failed to set sandbox policy: {err}"));
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// If sandbox policy becomes workspace-write or read-only, run the Windows world-writable scan.
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -1778,7 +1788,7 @@ 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.sandbox_policy.clone();
|
||||
let sandbox_policy = self.config.sandbox_policy.get().clone();
|
||||
Self::spawn_world_writable_scan(
|
||||
cwd,
|
||||
env_map,
|
||||
|
||||
@@ -8,6 +8,7 @@ use std::time::Duration;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_backend_client::Client as BackendClient;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConstraintResult;
|
||||
use codex_core::config::types::Notifications;
|
||||
use codex_core::git_info::current_branch_name;
|
||||
use codex_core::git_info::local_git_branches;
|
||||
@@ -2554,12 +2555,12 @@ impl ChatWidget {
|
||||
/// Open a popup to choose the approvals mode (ask for approval policy + sandbox policy).
|
||||
pub(crate) fn open_approvals_popup(&mut self) {
|
||||
let current_approval = self.config.approval_policy.value();
|
||||
let current_sandbox = self.config.sandbox_policy.clone();
|
||||
let current_sandbox = self.config.sandbox_policy.get();
|
||||
let mut items: Vec<SelectionItem> = Vec::new();
|
||||
let presets: Vec<ApprovalPreset> = builtin_approval_presets();
|
||||
for preset in presets.into_iter() {
|
||||
let is_current =
|
||||
Self::preset_matches_current(current_approval, ¤t_sandbox, &preset);
|
||||
Self::preset_matches_current(current_approval, current_sandbox, &preset);
|
||||
let name = preset.label.to_string();
|
||||
let description_text = preset.description;
|
||||
let description = Some(description_text.to_string());
|
||||
@@ -2685,7 +2686,7 @@ impl ChatWidget {
|
||||
self.config.codex_home.as_path(),
|
||||
cwd.as_path(),
|
||||
&env_map,
|
||||
&self.config.sandbox_policy,
|
||||
self.config.sandbox_policy.get(),
|
||||
Some(self.config.codex_home.as_path()),
|
||||
) {
|
||||
Ok(_) => None,
|
||||
@@ -2784,7 +2785,7 @@ impl ChatWidget {
|
||||
let mode_label = preset
|
||||
.as_ref()
|
||||
.map(|p| describe_policy(&p.sandbox))
|
||||
.unwrap_or_else(|| describe_policy(&self.config.sandbox_policy));
|
||||
.unwrap_or_else(|| describe_policy(self.config.sandbox_policy.get()));
|
||||
let info_line = if failed_scan {
|
||||
Line::from(vec![
|
||||
"We couldn't complete the world-writable scan, so protections cannot be verified. "
|
||||
@@ -2957,17 +2958,19 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
/// Set the sandbox policy in the widget's config copy.
|
||||
pub(crate) fn set_sandbox_policy(&mut self, policy: SandboxPolicy) {
|
||||
pub(crate) fn set_sandbox_policy(&mut self, policy: SandboxPolicy) -> ConstraintResult<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let should_clear_downgrade = !matches!(policy, SandboxPolicy::ReadOnly)
|
||||
let should_clear_downgrade = !matches!(&policy, SandboxPolicy::ReadOnly)
|
||||
|| codex_core::get_platform_sandbox().is_some();
|
||||
|
||||
self.config.sandbox_policy = policy;
|
||||
self.config.sandbox_policy.set(policy)?;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if should_clear_downgrade {
|
||||
self.config.forced_auto_mode_downgraded_on_windows = false;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn set_full_access_warning_acknowledged(&mut self, acknowledged: bool) {
|
||||
|
||||
@@ -223,7 +223,7 @@ pub async fn run_main(
|
||||
|
||||
let config = load_config_or_exit(cli_kv_overrides.clone(), overrides.clone()).await;
|
||||
|
||||
if let Some(warning) = add_dir_warning_message(&cli.add_dir, &config.sandbox_policy) {
|
||||
if let Some(warning) = add_dir_warning_message(&cli.add_dir, config.sandbox_policy.get()) {
|
||||
#[allow(clippy::print_stderr)]
|
||||
{
|
||||
eprintln!("Error adding directories: {warning}");
|
||||
|
||||
@@ -119,7 +119,7 @@ impl StatusHistoryCell {
|
||||
.find(|(k, _)| *k == "approval")
|
||||
.map(|(_, v)| v.clone())
|
||||
.unwrap_or_else(|| "<unknown>".to_string());
|
||||
let sandbox = match &config.sandbox_policy {
|
||||
let sandbox = match config.sandbox_policy.get() {
|
||||
SandboxPolicy::DangerFullAccess => "danger-full-access".to_string(),
|
||||
SandboxPolicy::ReadOnly => "read-only".to_string(),
|
||||
SandboxPolicy::WorkspaceWrite { .. } => "workspace-write".to_string(),
|
||||
|
||||
@@ -90,12 +90,15 @@ async fn status_snapshot_includes_reasoning_details() {
|
||||
config.model_provider_id = "openai".to_string();
|
||||
config.model_reasoning_effort = Some(ReasoningEffort::High);
|
||||
config.model_reasoning_summary = ReasoningSummary::Detailed;
|
||||
config.sandbox_policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
};
|
||||
config
|
||||
.sandbox_policy
|
||||
.set(SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: false,
|
||||
})
|
||||
.expect("set sandbox policy");
|
||||
|
||||
config.cwd = PathBuf::from("/workspace/tests");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user