mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
feat: support allowed_sandbox_modes in requirements.toml
This commit is contained in:
@@ -416,7 +416,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
|
||||
@@ -452,7 +452,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;
|
||||
@@ -527,7 +527,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(),
|
||||
@@ -644,7 +644,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(),
|
||||
);
|
||||
@@ -694,7 +694,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,
|
||||
@@ -711,7 +711,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(),
|
||||
};
|
||||
@@ -898,7 +898,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,7 +81,11 @@ 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) {
|
||||
@@ -62,7 +98,46 @@ 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`?
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user