permissions: derive compatibility policies from profiles (#19392)

## Why

After #19391, `PermissionProfile` and the split filesystem/network
policies could still be stored in parallel. That creates drift risk: a
profile can preserve deny globs, external enforcement, or split
filesystem entries while a cached projection silently loses those
details. This PR makes the profile the runtime source and derives
compatibility views from it.

## What Changed

- Removes stored filesystem/network sandbox projections from
`Permissions` and `SessionConfiguration`; their accessors now derive
from the canonical `PermissionProfile`.
- Derives legacy `SandboxPolicy` snapshots from profiles only where an
older API still needs that field.
- Updates MCP connection and elicitation state to track
`PermissionProfile` instead of `SandboxPolicy` for auto-approval
decisions.
- Adds semantic filesystem-policy comparison so cwd changes can preserve
richer profiles while still recognizing equivalent legacy projections
independent of entry ordering.
- Updates config/session tests to assert profile-derived projections
instead of parallel stored fields.

## Verification

- `cargo test -p codex-core direct_write_roots`
- `cargo test -p codex-core runtime_roots_to_legacy_projection`
- `cargo test -p codex-app-server
requested_permissions_trust_project_uses_permission_profile_intent`



































































---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19392).
* #19395
* #19394
* #19393
* __->__ #19392
This commit is contained in:
Michael Bolin
2026-04-26 15:06:42 -07:00
committed by GitHub
Unverified
parent 4d7ce3447d
commit deaa307fb2
39 changed files with 568 additions and 439 deletions
+2 -1
View File
@@ -35,10 +35,11 @@ pub(crate) async fn apply_patch(
file_system_sandbox_policy: &FileSystemSandboxPolicy,
action: ApplyPatchAction,
) -> InternalApplyPatchInvocation {
let sandbox_policy = turn_context.sandbox_policy();
match assess_patch_safety(
&action,
turn_context.approval_policy.value(),
turn_context.sandbox_policy.get(),
&sandbox_policy,
file_system_sandbox_policy,
&turn_context.cwd,
turn_context.windows_sandbox_level,
+14 -30
View File
@@ -776,7 +776,7 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
let memories_root = codex_home.path().join("memories").abs();
assert_eq!(
config.permissions.file_system_sandbox_policy,
config.permissions.file_system_sandbox_policy(),
FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
@@ -814,7 +814,7 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
}
);
assert_eq!(
config.permissions.network_sandbox_policy,
config.permissions.network_sandbox_policy(),
NetworkSandboxPolicy::Restricted
);
Ok(())
@@ -1082,7 +1082,7 @@ async fn project_root_glob_none_compiles_to_filesystem_pattern_entry() -> std::i
assert_eq!(
config
.permissions
.file_system_sandbox_policy
.file_system_sandbox_policy()
.glob_scan_max_depth,
Some(2)
);
@@ -1092,7 +1092,7 @@ async fn project_root_glob_none_compiles_to_filesystem_pattern_entry() -> std::i
assert!(
config
.permissions
.file_system_sandbox_policy
.file_system_sandbox_policy()
.entries
.contains(&FileSystemSandboxEntry {
path: FileSystemPath::GlobPattern {
@@ -1104,7 +1104,7 @@ async fn project_root_glob_none_compiles_to_filesystem_pattern_entry() -> std::i
assert!(
!config
.permissions
.file_system_sandbox_policy
.file_system_sandbox_policy()
.entries
.iter()
.any(|entry| matches!(
@@ -1304,7 +1304,7 @@ async fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<(
.await?;
assert_eq!(
config.permissions.file_system_sandbox_policy,
config.permissions.file_system_sandbox_policy(),
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::unknown(
@@ -1350,7 +1350,7 @@ async fn permissions_profiles_allow_unknown_special_paths_with_nested_entries()
.await?;
assert_eq!(
config.permissions.file_system_sandbox_policy,
config.permissions.file_system_sandbox_policy(),
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::unknown(":future_special_path", Some("docs".into())),
@@ -1377,7 +1377,7 @@ async fn permissions_profiles_allow_missing_filesystem_with_warning() -> std::io
.await?;
assert_eq!(
config.permissions.file_system_sandbox_policy,
config.permissions.file_system_sandbox_policy(),
FileSystemSandboxPolicy::restricted(Vec::new())
);
assert_eq!(
@@ -1408,7 +1408,7 @@ async fn permissions_profiles_allow_empty_filesystem_with_warning() -> std::io::
.await?;
assert_eq!(
config.permissions.file_system_sandbox_policy,
config.permissions.file_system_sandbox_policy(),
FileSystemSandboxPolicy::restricted(Vec::new())
);
assert!(
@@ -1505,7 +1505,7 @@ async fn permissions_profiles_allow_network_enablement() -> std::io::Result<()>
.await?;
assert!(
config.permissions.network_sandbox_policy.is_enabled(),
config.permissions.network_sandbox_policy().is_enabled(),
"expected network sandbox policy to be enabled",
);
assert!(
@@ -1800,20 +1800,20 @@ exclude_slash_tmp = true
let sandbox_policy = config.permissions.sandbox_policy.get();
assert_eq!(
config.permissions.file_system_sandbox_policy,
config.permissions.file_system_sandbox_policy(),
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd.path()),
"case `{name}` should preserve filesystem semantics from legacy config"
);
assert_eq!(
config.permissions.network_sandbox_policy,
config.permissions.network_sandbox_policy(),
NetworkSandboxPolicy::from(sandbox_policy),
"case `{name}` should preserve network semantics from legacy config"
);
assert_eq!(
config
.permissions
.file_system_sandbox_policy
.to_legacy_sandbox_policy(config.permissions.network_sandbox_policy, cwd.path())
.file_system_sandbox_policy()
.to_legacy_sandbox_policy(config.permissions.network_sandbox_policy(), cwd.path())
.unwrap_or_else(|err| panic!("case `{name}` should round-trip: {err}")),
sandbox_policy.clone(),
"case `{name}` should round-trip through split policies without drift"
@@ -5449,10 +5449,6 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
approval_policy: Constrained::allow_any(AskForApproval::Never),
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
&SandboxPolicy::new_read_only_policy(),
),
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
network: None,
allow_login_shell: true,
shell_environment_policy: ShellEnvironmentPolicy::default(),
@@ -5647,10 +5643,6 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
approval_policy: Constrained::allow_any(AskForApproval::UnlessTrusted),
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
&SandboxPolicy::new_read_only_policy(),
),
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
network: None,
allow_login_shell: true,
shell_environment_policy: ShellEnvironmentPolicy::default(),
@@ -5799,10 +5791,6 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
&SandboxPolicy::new_read_only_policy(),
),
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
network: None,
allow_login_shell: true,
shell_environment_policy: ShellEnvironmentPolicy::default(),
@@ -5936,10 +5924,6 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
approval_policy: Constrained::allow_any(AskForApproval::OnFailure),
permission_profile: Constrained::allow_any(PermissionProfile::read_only()),
sandbox_policy: Constrained::allow_any(SandboxPolicy::new_read_only_policy()),
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
&SandboxPolicy::new_read_only_policy(),
),
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
network: None,
allow_login_shell: true,
shell_environment_policy: ShellEnvironmentPolicy::default(),
+4 -22
View File
@@ -200,18 +200,6 @@ pub struct Permissions {
/// Legacy projection retained while runtime call sites migrate to
/// `permission_profile`.
pub sandbox_policy: Constrained<SandboxPolicy>,
/// Effective filesystem sandbox policy, including entries that cannot yet
/// be fully represented by the legacy [`SandboxPolicy`] projection.
///
/// Runtime projection retained while callers migrate to
/// `permission_profile`.
pub file_system_sandbox_policy: FileSystemSandboxPolicy,
/// Effective network sandbox policy split out from the legacy
/// [`SandboxPolicy`] projection.
///
/// Runtime projection retained while callers migrate to
/// `permission_profile`.
pub network_sandbox_policy: NetworkSandboxPolicy,
/// Effective network configuration applied to all spawned processes.
pub network: Option<NetworkProxySpec>,
/// Whether the model may request a login shell for shell-based tools.
@@ -239,14 +227,14 @@ impl Permissions {
self.permission_profile.get().clone()
}
/// Effective filesystem sandbox policy projection.
/// Effective filesystem sandbox policy derived from the canonical profile.
pub fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
self.file_system_sandbox_policy.clone()
self.permission_profile.get().file_system_sandbox_policy()
}
/// Effective network sandbox policy projection.
/// Effective network sandbox policy derived from the canonical profile.
pub fn network_sandbox_policy(&self) -> NetworkSandboxPolicy {
self.network_sandbox_policy
self.permission_profile.get().network_sandbox_policy()
}
/// Replace permissions from a legacy sandbox policy and keep every
@@ -269,8 +257,6 @@ impl Permissions {
self.sandbox_policy.set(sandbox_policy)?;
self.permission_profile.set(permission_profile)?;
self.file_system_sandbox_policy = file_system_sandbox_policy;
self.network_sandbox_policy = network_sandbox_policy;
Ok(())
}
@@ -294,8 +280,6 @@ impl Permissions {
self.permission_profile.set(permission_profile)?;
self.sandbox_policy.set(sandbox_policy)?;
self.file_system_sandbox_policy = file_system_sandbox_policy;
self.network_sandbox_policy = network_sandbox_policy;
Ok(())
}
}
@@ -2493,8 +2477,6 @@ impl Config {
approval_policy: constrained_approval_policy.value,
permission_profile: constrained_permission_profile,
sandbox_policy: constrained_sandbox_policy.value,
file_system_sandbox_policy: effective_file_system_sandbox_policy,
network_sandbox_policy: effective_network_sandbox_policy,
network,
allow_login_shell,
shell_environment_policy,
@@ -89,7 +89,7 @@ async fn restricted_read_implicitly_allows_helper_executables() -> std::io::Resu
let expected_zsh = AbsolutePathBuf::try_from(zsh_path)?;
let expected_allowed_arg0_dir = AbsolutePathBuf::try_from(allowed_arg0_dir)?;
let expected_sibling_arg0_dir = AbsolutePathBuf::try_from(sibling_arg0_dir)?;
let policy = &config.permissions.file_system_sandbox_policy;
let policy = config.permissions.file_system_sandbox_policy();
assert!(
policy.can_read_path_with_cwd(expected_zsh.as_path(), &cwd),
+2 -2
View File
@@ -17,7 +17,7 @@ use codex_connectors::DirectoryListResponse;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::EnvironmentManagerArgs;
use codex_exec_server::ExecServerRuntimePaths;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::models::PermissionProfile;
use codex_tools::DiscoverableTool;
use rmcp::model::ToolAnnotations;
use serde::Deserialize;
@@ -274,7 +274,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_environment_manager(
&config.permissions.approval_policy,
INITIAL_SUBMIT_ID.to_owned(),
tx_event,
SandboxPolicy::new_read_only_policy(),
PermissionProfile::default(),
McpRuntimeEnvironment::new(environment, config.cwd.to_path_buf()),
config.codex_home.to_path_buf(),
codex_apps_tools_cache_key(auth.as_ref()),
+1 -1
View File
@@ -221,7 +221,7 @@ async fn should_install_mcp_dependencies(
) -> bool {
if mcp_permission_prompt_is_auto_approved(
turn_context.approval_policy.value(),
turn_context.sandbox_policy.get(),
&turn_context.permission_profile(),
) {
return true;
}
+2 -2
View File
@@ -524,7 +524,7 @@ async fn augment_mcp_tool_request_meta_with_sandbox_state(
let sandbox_state = serde_json::to_value(SandboxState {
permission_profile: Some(turn_context.permission_profile()),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
sandbox_policy: turn_context.sandbox_policy(),
codex_linux_sandbox_exe: turn_context.codex_linux_sandbox_exe.clone(),
sandbox_cwd: turn_context.cwd.to_path_buf(),
use_legacy_landlock: turn_context.features.use_legacy_landlock(),
@@ -830,7 +830,7 @@ async fn maybe_request_mcp_tool_approval(
) -> Option<McpToolApprovalDecision> {
if mcp_permission_prompt_is_auto_approved(
turn_context.approval_policy.value(),
turn_context.sandbox_policy.get(),
&turn_context.permission_profile(),
) {
return None;
}
+2 -5
View File
@@ -16,8 +16,8 @@ use codex_config::types::McpServerToolConfig;
use codex_hooks::Hooks;
use codex_hooks::HooksConfig;
use codex_model_provider::create_model_provider;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use core_test_support::PathExt;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
@@ -2162,10 +2162,7 @@ async fn full_access_mode_skips_arc_monitor_for_all_approval_modes() {
.approval_policy
.set(AskForApproval::Never)
.expect("test setup should allow updating approval policy");
turn_context
.sandbox_policy
.set(SandboxPolicy::DangerFullAccess)
.expect("test setup should allow updating sandbox policy");
turn_context.permission_profile = PermissionProfile::Disabled;
let mut config = (*turn_context.config).clone();
config.chatgpt_base_url = server.uri();
turn_context.config = Arc::new(config);
+33 -23
View File
@@ -432,6 +432,7 @@ mod phase2 {
use codex_login::CodexAuth;
use codex_protocol::AgentPath;
use codex_protocol::ThreadId;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
@@ -482,8 +483,14 @@ mod phase2 {
codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(codex_home.path())
.expect("codex home is absolute");
config.cwd = config.codex_home.clone();
config.permissions.file_system_sandbox_policy = FileSystemSandboxPolicy::unrestricted();
config.permissions.network_sandbox_policy = NetworkSandboxPolicy::Enabled;
let permission_profile = PermissionProfile::from_runtime_permissions(
&FileSystemSandboxPolicy::unrestricted(),
NetworkSandboxPolicy::Enabled,
);
config
.permissions
.set_permission_profile(permission_profile, config.cwd.as_path())
.expect("permissions are configurable");
configure(&mut config);
let config = Arc::new(config);
@@ -712,14 +719,16 @@ mod phase2 {
memory_root(&harness.config.codex_home).as_path()
);
match &config_snapshot.sandbox_policy {
SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access,
..
} => {
SandboxPolicy::WorkspaceWrite { network_access, .. } => {
assert!(!*network_access);
let effective_writable_roots: Vec<_> = config_snapshot
.sandbox_policy
.get_writable_roots_with_cwd(config_snapshot.cwd.as_path())
.into_iter()
.map(|root| root.root)
.collect();
pretty_assertions::assert_eq!(
writable_roots.as_slice(),
effective_writable_roots.as_slice(),
[memory_root(&harness.config.codex_home)],
"consolidation subagent should only be able to write the memory root"
);
@@ -740,34 +749,35 @@ mod phase2 {
"memory consolidation should not be registered in the root collab agent registry"
);
let turn_context = subagent.codex.session.new_default_turn().await;
pretty_assertions::assert_eq!(
turn_context.file_system_sandbox_policy,
let file_system_sandbox_policy = turn_context.file_system_sandbox_policy();
let legacy_file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
&config_snapshot.sandbox_policy,
config_snapshot.cwd.as_path(),
);
assert!(
file_system_sandbox_policy.is_semantically_equivalent_to(
&legacy_file_system_sandbox_policy,
config_snapshot.cwd.as_path(),
),
"consolidation subagent split filesystem policy should match the memory-root legacy policy"
);
assert!(
turn_context
.file_system_sandbox_policy
.can_write_path_with_cwd(
memory_root(&harness.config.codex_home).as_path(),
config_snapshot.cwd.as_path(),
),
file_system_sandbox_policy.can_write_path_with_cwd(
memory_root(&harness.config.codex_home).as_path(),
config_snapshot.cwd.as_path(),
),
"consolidation subagent should be able to write the memory root"
);
assert!(
!turn_context
.file_system_sandbox_policy
.can_write_path_with_cwd(
harness.config.codex_home.join("config.toml").as_path(),
config_snapshot.cwd.as_path(),
),
!file_system_sandbox_policy.can_write_path_with_cwd(
harness.config.codex_home.join("config.toml").as_path(),
config_snapshot.cwd.as_path(),
),
"consolidation subagent should not inherit codex_home write access"
);
pretty_assertions::assert_eq!(
turn_context.network_sandbox_policy,
turn_context.network_sandbox_policy(),
NetworkSandboxPolicy::Restricted,
"consolidation subagent split network policy should preserve no-network sandboxing"
);
+1 -1
View File
@@ -233,7 +233,7 @@ impl Session {
&turn_context.approval_policy,
turn_context.sub_id.clone(),
self.get_tx_event(),
turn_context.sandbox_policy.get().clone(),
turn_context.permission_profile(),
McpRuntimeEnvironment::new(
turn_context
.environment
+4 -8
View File
@@ -125,7 +125,6 @@ use codex_rollout::state_db;
use codex_rollout_trace::AgentResultTracePayload;
use codex_rollout_trace::ThreadStartedTraceMetadata;
use codex_rollout_trace::ThreadTraceContext;
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
use codex_sandboxing::policy_transforms::intersect_permission_profiles;
use codex_shell_command::parse_command::parse_command;
use codex_terminal_detection::user_agent;
@@ -606,9 +605,6 @@ impl Codex {
approval_policy: config.permissions.approval_policy.clone(),
approvals_reviewer: config.approvals_reviewer,
permission_profile: config.permissions.permission_profile.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -939,8 +935,7 @@ impl Session {
return;
};
let spec = match spec
.recompute_for_sandbox_policy(session_configuration.sandbox_policy.get())
let spec = match spec.recompute_for_sandbox_policy(&session_configuration.sandbox_policy())
{
Ok(spec) => spec,
Err(err) => {
@@ -1301,8 +1296,9 @@ impl Session {
};
let previous_cwd = state.session_configuration.cwd.clone();
let sandbox_policy_changed =
state.session_configuration.sandbox_policy != updated.sandbox_policy;
let previous_sandbox_policy = state.session_configuration.sandbox_policy();
let updated_sandbox_policy = updated.sandbox_policy();
let sandbox_policy_changed = previous_sandbox_policy != updated_sandbox_policy;
let next_cwd = updated.cwd.clone();
let codex_home = updated.codex_home.clone();
let session_source = updated.session_source.clone();
-3
View File
@@ -129,9 +129,6 @@ pub(super) async fn spawn_review_thread(
personality: parent_turn_context.personality,
approval_policy: parent_turn_context.approval_policy.clone(),
permission_profile: parent_turn_context.permission_profile(),
sandbox_policy: parent_turn_context.sandbox_policy.clone(),
file_system_sandbox_policy: parent_turn_context.file_system_sandbox_policy.clone(),
network_sandbox_policy: parent_turn_context.network_sandbox_policy,
network: parent_turn_context.network.clone(),
windows_sandbox_level: parent_turn_context.windows_sandbox_level,
shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),
@@ -67,7 +67,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -108,7 +108,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -918,7 +918,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -996,7 +996,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -1027,7 +1027,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -1142,7 +1142,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -1256,7 +1256,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -1408,7 +1408,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
+67 -48
View File
@@ -1,5 +1,7 @@
use super::*;
use crate::goals::GoalRuntimeState;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSpecialPath;
use tokio::sync::Semaphore;
/// Context for an initialized model agent
@@ -58,12 +60,6 @@ pub(crate) struct SessionConfiguration {
pub(super) approvals_reviewer: ApprovalsReviewer,
/// Canonical permission profile for the session.
pub(super) permission_profile: Constrained<PermissionProfile>,
/// Legacy sandbox projection retained while lower-level callers migrate.
pub(super) sandbox_policy: Constrained<SandboxPolicy>,
/// Filesystem sandbox projection of `permission_profile`.
pub(super) file_system_sandbox_policy: FileSystemSandboxPolicy,
/// Network sandbox projection of `permission_profile`.
pub(super) network_sandbox_policy: NetworkSandboxPolicy,
pub(super) windows_sandbox_level: WindowsSandboxLevel,
/// Absolute working directory that should be treated as the *root* of the
@@ -102,12 +98,25 @@ impl SessionConfiguration {
}
pub(super) fn sandbox_policy(&self) -> SandboxPolicy {
self.sandbox_policy.get().clone()
self.permission_profile()
.to_legacy_sandbox_policy(&self.cwd)
.unwrap_or_else(|_| {
let file_system_sandbox_policy = self.file_system_sandbox_policy();
codex_sandboxing::compatibility_sandbox_policy_for_permission_profile(
self.permission_profile.get(),
&file_system_sandbox_policy,
self.network_sandbox_policy(),
&self.cwd,
)
})
}
#[cfg(test)]
pub(super) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
self.file_system_sandbox_policy.clone()
self.permission_profile.get().file_system_sandbox_policy()
}
pub(super) fn network_sandbox_policy(&self) -> NetworkSandboxPolicy {
self.permission_profile.get().network_sandbox_policy()
}
pub(super) fn thread_config_snapshot(&self) -> ThreadConfigSnapshot {
@@ -129,11 +138,30 @@ impl SessionConfiguration {
pub(crate) fn apply(&self, updates: &SessionSettingsUpdate) -> ConstraintResult<Self> {
let mut next_configuration = self.clone();
let file_system_policy_matches_legacy = self.file_system_sandbox_policy
== FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
self.sandbox_policy.get(),
let current_sandbox_policy = self.sandbox_policy();
let current_file_system_sandbox_policy = self.file_system_sandbox_policy();
let current_network_sandbox_policy = self.network_sandbox_policy();
let legacy_file_system_projection =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
&current_sandbox_policy,
&self.cwd,
&current_file_system_sandbox_policy,
);
let file_system_policy_matches_legacy = current_file_system_sandbox_policy
.is_semantically_equivalent_to(&legacy_file_system_projection, &self.cwd);
let file_system_policy_has_rebindable_cwd_write = current_file_system_sandbox_policy
.entries
.iter()
.any(|entry| {
entry.access.can_write()
&& matches!(
&entry.path,
FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory
| FileSystemSpecialPath::ProjectRoots { subpath: None },
}
)
});
if let Some(collaboration_mode) = updates.collaboration_mode.clone() {
next_configuration.collaboration_mode = collaboration_mode;
}
@@ -181,42 +209,41 @@ impl SessionConfiguration {
if let Some(permission_profile) = updates.permission_profile.clone() {
next_configuration.set_permission_profile_projection(
permission_profile,
Some(&self.file_system_sandbox_policy),
Some(&current_file_system_sandbox_policy),
)?;
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
next_configuration.sandbox_policy.set(sandbox_policy)?;
next_configuration.file_system_sandbox_policy =
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
next_configuration.sandbox_policy.get(),
&sandbox_policy,
&next_configuration.cwd,
&self.file_system_sandbox_policy,
&current_file_system_sandbox_policy,
);
next_configuration.network_sandbox_policy =
NetworkSandboxPolicy::from(next_configuration.sandbox_policy.get());
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
next_configuration.permission_profile.set(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(
next_configuration.sandbox_policy.get(),
),
&next_configuration.file_system_sandbox_policy,
next_configuration.network_sandbox_policy,
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&file_system_sandbox_policy,
network_sandbox_policy,
),
)?;
} else if cwd_changed && file_system_policy_matches_legacy {
} else if cwd_changed
&& file_system_policy_matches_legacy
&& file_system_policy_has_rebindable_cwd_write
{
// Preserve richer split policies across cwd-only updates; only
// rederive when the session is already using the legacy bridge.
next_configuration.file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
next_configuration.sandbox_policy.get(),
// rederive when the session is already using a structurally
// cwd-bound legacy bridge.
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
&current_sandbox_policy,
&next_configuration.cwd,
&current_file_system_sandbox_policy,
);
next_configuration.permission_profile.set(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(
next_configuration.sandbox_policy.get(),
),
&next_configuration.file_system_sandbox_policy,
next_configuration.network_sandbox_policy,
SandboxEnforcement::from_legacy_sandbox_policy(&current_sandbox_policy),
&file_system_sandbox_policy,
current_network_sandbox_policy,
),
)?;
}
@@ -247,16 +274,7 @@ impl SessionConfiguration {
&file_system_sandbox_policy,
network_sandbox_policy,
);
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
&effective_permission_profile,
&file_system_sandbox_policy,
network_sandbox_policy,
self.cwd.as_path(),
);
self.permission_profile.set(effective_permission_profile)?;
self.sandbox_policy.set(sandbox_policy)?;
self.file_system_sandbox_policy = file_system_sandbox_policy;
self.network_sandbox_policy = network_sandbox_policy;
Ok(())
}
}
@@ -487,7 +505,7 @@ impl Session {
model: session_configuration.collaboration_mode.model().to_string(),
provider_name: config.model_provider_id.clone(),
approval_policy: session_configuration.approval_policy.value().to_string(),
sandbox_policy: format!("{:?}", session_configuration.sandbox_policy.get()),
sandbox_policy: format!("{:?}", session_configuration.sandbox_policy()),
};
let rollout_thread_trace = if matches!(
session_configuration.session_source,
@@ -768,7 +786,7 @@ impl Session {
// setup is straightforward enough and performs well.
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized(
&config.permissions.approval_policy,
&config.permissions.sandbox_policy,
&config.permissions.permission_profile,
))),
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
unified_exec_manager: UnifiedExecProcessManager::new(
@@ -847,6 +865,7 @@ impl Session {
// Dispatch the SessionConfiguredEvent first and then report any errors.
// If resuming, include converted initial messages in the payload so UIs can render them immediately.
let initial_messages = initial_history.get_event_msgs();
let session_sandbox_policy = session_configuration.sandbox_policy();
let events = std::iter::once(Event {
id: INITIAL_SUBMIT_ID.to_owned(),
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
@@ -858,7 +877,7 @@ impl Session {
service_tier: session_configuration.service_tier,
approval_policy: session_configuration.approval_policy.value(),
approvals_reviewer: session_configuration.approvals_reviewer,
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
sandbox_policy: session_sandbox_policy.clone(),
permission_profile: Some(session_configuration.permission_profile()),
cwd: session_configuration.cwd.clone(),
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
@@ -867,7 +886,7 @@ impl Session {
initial_messages,
network_proxy: session_network_proxy.filter(|_| {
Self::managed_network_proxy_active_for_sandbox_policy(
session_configuration.sandbox_policy.get(),
&session_sandbox_policy,
)
}),
rollout_path,
@@ -901,7 +920,7 @@ impl Session {
&session_configuration.approval_policy,
INITIAL_SUBMIT_ID.to_owned(),
tx_event.clone(),
session_configuration.sandbox_policy.get().clone(),
session_configuration.permission_profile(),
McpRuntimeEnvironment::new(
sess.services
.environment_manager
+148 -84
View File
@@ -38,6 +38,7 @@ use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::FunctionCallOutputBody;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::SandboxEnforcement;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
@@ -779,11 +780,19 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
let mut state = session.state.lock().await;
let mut config = (*state.session_configuration.original_config_do_not_use).clone();
config.permissions.network = Some(spec);
config.permissions.sandbox_policy =
codex_config::Constrained::allow_any(initial_policy.clone());
let cwd = config.cwd.clone();
config
.permissions
.set_legacy_sandbox_policy(initial_policy.clone(), cwd.as_path())
.expect("test setup should allow sandbox policy");
state.session_configuration.original_config_do_not_use = Arc::new(config);
state.session_configuration.sandbox_policy =
codex_config::Constrained::allow_any(initial_policy);
state
.session_configuration
.permission_profile
.set(PermissionProfile::from_legacy_sandbox_policy(
&initial_policy,
))
.expect("test setup should allow permission profile");
}
session.services.network_proxy = Some(started_proxy);
@@ -829,6 +838,8 @@ async fn danger_full_access_turns_do_not_expose_managed_network_proxy() -> anyho
let session = make_session_with_config(move |config| {
config.permissions.sandbox_policy =
codex_config::Constrained::allow_any(SandboxPolicy::DangerFullAccess);
config.permissions.permission_profile =
codex_config::Constrained::allow_any(PermissionProfile::Disabled);
config.permissions.network = Some(network_spec);
})
.await?;
@@ -892,6 +903,8 @@ async fn danger_full_access_tool_attempts_do_not_enforce_managed_network() -> an
let session = make_session_with_config(move |config| {
config.permissions.sandbox_policy =
codex_config::Constrained::allow_any(SandboxPolicy::DangerFullAccess);
config.permissions.permission_profile =
codex_config::Constrained::allow_any(PermissionProfile::Disabled);
config.permissions.network = Some(network_spec);
let layers = config
@@ -1492,12 +1505,10 @@ async fn session_configured_reports_permission_profile_for_external_sandbox() ->
let expected_sandbox_policy = sandbox_policy.clone();
let mut builder = test_codex().with_config(move |config| {
config.permissions.sandbox_policy = codex_config::Constrained::allow_any(sandbox_policy);
config.permissions.file_system_sandbox_policy = FileSystemSandboxPolicy::external_sandbox();
config.permissions.network_sandbox_policy = NetworkSandboxPolicy::Restricted;
config.permissions.permission_profile =
codex_config::Constrained::allow_any(PermissionProfile::from_runtime_permissions(
&config.permissions.file_system_sandbox_policy,
config.permissions.network_sandbox_policy,
&FileSystemSandboxPolicy::external_sandbox(),
NetworkSandboxPolicy::Restricted,
));
});
@@ -1654,7 +1665,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
current_date: turn_context.current_date.clone(),
timezone: turn_context.timezone.clone(),
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
sandbox_policy: turn_context.sandbox_policy(),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -2253,9 +2264,6 @@ async fn set_rate_limits_retains_previous_credits() {
approval_policy: config.permissions.approval_policy.clone(),
approvals_reviewer: config.approvals_reviewer,
permission_profile: config.permissions.permission_profile.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -2358,9 +2366,6 @@ async fn set_rate_limits_updates_plan_type_when_present() {
approval_policy: config.permissions.approval_policy.clone(),
approvals_reviewer: config.approvals_reviewer,
permission_profile: config.permissions.permission_profile.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -2808,9 +2813,6 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
approval_policy: config.permissions.approval_policy.clone(),
approvals_reviewer: config.approvals_reviewer,
permission_profile: config.permissions.permission_profile.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -2840,7 +2842,7 @@ fn turn_environments_for_tests(
}
#[tokio::test]
async fn session_configuration_apply_preserves_split_file_system_policy_on_cwd_only_update() {
async fn session_configuration_apply_preserves_profile_file_system_policy_on_cwd_only_update() {
let mut session_configuration = make_session_configuration_for_tests().await;
let workspace = tempfile::tempdir().expect("create temp dir");
let project_root = workspace.path().join("project");
@@ -2850,14 +2852,13 @@ async fn session_configuration_apply_preserves_split_file_system_policy_on_cwd_o
let docs_dir = docs_dir.abs();
session_configuration.cwd = original_cwd.abs();
session_configuration.sandbox_policy =
codex_config::Constrained::allow_any(SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
});
session_configuration.file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
};
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::CurrentWorkingDirectory,
@@ -2869,6 +2870,14 @@ async fn session_configuration_apply_preserves_split_file_system_policy_on_cwd_o
access: FileSystemAccessMode::Read,
},
]);
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
session_configuration.permission_profile = codex_config::Constrained::allow_any(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&file_system_sandbox_policy,
network_sandbox_policy,
),
);
let updated = session_configuration
.apply(&SessionSettingsUpdate {
@@ -2878,8 +2887,8 @@ async fn session_configuration_apply_preserves_split_file_system_policy_on_cwd_o
.expect("cwd-only update should succeed");
assert_eq!(
updated.file_system_sandbox_policy,
session_configuration.file_system_sandbox_policy
updated.file_system_sandbox_policy(),
file_system_sandbox_policy
);
}
@@ -2890,8 +2899,6 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_
session_configuration.cwd = cwd.path().abs();
let workspace_policy = SandboxPolicy::new_workspace_write_policy();
session_configuration.sandbox_policy =
codex_config::Constrained::allow_any(workspace_policy.clone());
let deny_entry = FileSystemSandboxEntry {
path: FileSystemPath::GlobPattern {
pattern: "**/*.env".to_string(),
@@ -2905,7 +2912,13 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_
);
existing_file_system_policy.glob_scan_max_depth = Some(2);
existing_file_system_policy.entries.push(deny_entry.clone());
session_configuration.file_system_sandbox_policy = existing_file_system_policy;
session_configuration.permission_profile = codex_config::Constrained::allow_any(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&workspace_policy),
&existing_file_system_policy,
NetworkSandboxPolicy::Restricted,
),
);
let requested_file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
&workspace_policy,
@@ -2926,7 +2939,7 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_
expected_file_system_policy.glob_scan_max_depth = Some(2);
expected_file_system_policy.entries.push(deny_entry);
assert_eq!(
updated.file_system_sandbox_policy,
updated.file_system_sandbox_policy(),
expected_file_system_policy
);
}
@@ -3070,18 +3083,23 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
let project_root = workspace.path().join("project");
let original_cwd = project_root.join("subdir");
session_configuration.cwd = original_cwd.abs();
session_configuration.sandbox_policy =
codex_config::Constrained::allow_any(SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
});
session_configuration.file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
session_configuration.sandbox_policy.get(),
&session_configuration.cwd,
);
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
};
let file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
&sandbox_policy,
&session_configuration.cwd,
);
session_configuration.permission_profile = codex_config::Constrained::allow_any(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&file_system_sandbox_policy,
NetworkSandboxPolicy::from(&sandbox_policy),
),
);
let updated = session_configuration
.apply(&SessionSettingsUpdate {
@@ -3090,12 +3108,73 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
})
.expect("cwd-only update should succeed");
let expected_file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
&updated.sandbox_policy(),
&project_root,
);
assert!(
updated
.file_system_sandbox_policy()
.is_semantically_equivalent_to(&expected_file_system_policy, &project_root),
"cwd-only update should rederive the legacy filesystem policy for the new cwd"
);
}
#[tokio::test]
async fn session_configuration_apply_preserves_absolute_cwd_write_root_on_cwd_update() {
let mut session_configuration = make_session_configuration_for_tests().await;
let workspace = tempfile::tempdir().expect("create temp dir");
let original_cwd = workspace.path().join("repo-a");
let next_cwd = workspace.path().join("repo-b");
std::fs::create_dir_all(&original_cwd).expect("create original cwd");
std::fs::create_dir_all(&next_cwd).expect("create next cwd");
let original_cwd = original_cwd.abs();
session_configuration.cwd = original_cwd.clone();
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: original_cwd.clone(),
},
access: FileSystemAccessMode::Write,
},
]);
session_configuration.permission_profile = codex_config::Constrained::allow_any(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::Managed,
&file_system_sandbox_policy,
NetworkSandboxPolicy::Restricted,
),
);
let updated = session_configuration
.apply(&SessionSettingsUpdate {
cwd: Some(next_cwd.clone()),
..Default::default()
})
.expect("cwd-only update should succeed");
assert_eq!(
updated.file_system_sandbox_policy,
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
updated.sandbox_policy.get(),
&project_root,
)
updated.file_system_sandbox_policy(),
file_system_sandbox_policy
);
assert!(
updated
.file_system_sandbox_policy()
.can_write_path_with_cwd(original_cwd.as_path(), updated.cwd.as_path()),
"absolute grant to the old cwd must remain writable"
);
assert!(
!updated
.file_system_sandbox_policy()
.can_write_path_with_cwd(next_cwd.as_path(), updated.cwd.as_path()),
"cwd-only update must not reinterpret an absolute old-cwd grant as :cwd"
);
}
@@ -3170,9 +3249,6 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
approval_policy: config.permissions.approval_policy.clone(),
approvals_reviewer: config.approvals_reviewer,
permission_profile: config.permissions.permission_profile.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -3277,9 +3353,6 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
approval_policy: config.permissions.approval_policy.clone(),
approvals_reviewer: config.approvals_reviewer,
permission_profile: config.permissions.permission_profile.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -3325,7 +3398,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
let services = SessionServices {
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized(
&config.permissions.approval_policy,
&config.permissions.sandbox_policy,
&config.permissions.permission_profile,
))),
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
unified_exec_manager: UnifiedExecProcessManager::new(
@@ -3492,9 +3565,6 @@ async fn make_session_with_config_and_rx(
approval_policy: config.permissions.approval_policy.clone(),
approvals_reviewer: config.approvals_reviewer,
permission_profile: config.permissions.permission_profile.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -4642,9 +4712,6 @@ where
approval_policy: config.permissions.approval_policy.clone(),
approvals_reviewer: config.approvals_reviewer,
permission_profile: config.permissions.permission_profile.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -4690,7 +4757,7 @@ where
let services = SessionServices {
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized(
&config.permissions.approval_policy,
&config.permissions.sandbox_policy,
&config.permissions.permission_profile,
))),
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
unified_exec_manager: UnifiedExecProcessManager::new(
@@ -5572,7 +5639,7 @@ async fn build_initial_context_restates_realtime_start_when_reference_context_is
fn file_system_policy_with_unreadable_glob(turn_context: &TurnContext) -> FileSystemSandboxPolicy {
let mut policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
turn_context.sandbox_policy.get(),
&turn_context.sandbox_policy(),
&turn_context.cwd,
);
policy.entries.push(FileSystemSandboxEntry {
@@ -5586,12 +5653,7 @@ fn file_system_policy_with_unreadable_glob(turn_context: &TurnContext) -> FileSy
#[tokio::test]
async fn turn_context_item_omits_legacy_equivalent_file_system_sandbox_policy() {
let (_session, mut turn_context) = make_session_and_context().await;
turn_context.file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
turn_context.sandbox_policy.get(),
&turn_context.cwd,
);
let (_session, turn_context) = make_session_and_context().await;
let item = turn_context.to_turn_context_item();
@@ -5606,7 +5668,11 @@ async fn turn_context_item_omits_legacy_equivalent_file_system_sandbox_policy()
async fn turn_context_item_stores_split_file_system_sandbox_policy_when_different() {
let (_session, mut turn_context) = make_session_and_context().await;
let file_system_sandbox_policy = file_system_policy_with_unreadable_glob(&turn_context);
turn_context.file_system_sandbox_policy = file_system_sandbox_policy.clone();
turn_context.permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
turn_context.permission_profile.enforcement(),
&file_system_sandbox_policy,
turn_context.network_sandbox_policy(),
);
let item = turn_context.to_turn_context_item();
@@ -5743,7 +5809,11 @@ async fn record_context_updates_and_set_reference_context_item_persists_split_fi
{
let (mut session, mut turn_context) = make_session_and_context().await;
let file_system_sandbox_policy = file_system_policy_with_unreadable_glob(&turn_context);
turn_context.file_system_sandbox_policy = file_system_sandbox_policy.clone();
turn_context.permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
turn_context.permission_profile.enforcement(),
&file_system_sandbox_policy,
turn_context.network_sandbox_policy(),
);
let rollout_path = attach_thread_persistence(&mut session).await;
session
@@ -7674,7 +7744,6 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
use crate::tools::sandboxing::ExecApprovalRequirement;
use crate::turn_diff_tracker::TurnDiffTracker;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use std::collections::HashMap;
let (session, mut turn_context_raw) = make_session_and_context().await;
@@ -7761,23 +7830,18 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
// command. Force DangerFullAccess so this check stays focused on approval
// policy rather than platform-specific sandbox behavior.
let turn_context_mut = Arc::get_mut(&mut turn_context).expect("unique turn context Arc");
turn_context_mut
.sandbox_policy
.set(SandboxPolicy::DangerFullAccess)
.expect("test setup should allow updating sandbox policy");
turn_context_mut.file_system_sandbox_policy =
FileSystemSandboxPolicy::from(turn_context_mut.sandbox_policy.get());
turn_context_mut.network_sandbox_policy =
NetworkSandboxPolicy::from(turn_context_mut.sandbox_policy.get());
turn_context_mut.permission_profile = PermissionProfile::Disabled;
let file_system_sandbox_policy = turn_context.file_system_sandbox_policy();
let sandbox_policy = turn_context.sandbox_policy();
let exec_approval_requirement = session
.services
.exec_policy
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
command: &params.command,
approval_policy: turn_context.approval_policy.value(),
sandbox_policy: turn_context.sandbox_policy.get(),
file_system_sandbox_policy: &turn_context.file_system_sandbox_policy,
sandbox_policy: &sandbox_policy,
file_system_sandbox_policy: &file_system_sandbox_policy,
sandbox_permissions: SandboxPermissions::UseDefault,
prefix_rule: None,
})
@@ -25,8 +25,6 @@ use codex_protocol::models::ContentItem;
use codex_protocol::models::NetworkPermissions;
use codex_protocol::models::ResponseItem;
use codex_protocol::models::function_call_output_content_items_to_text;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::request_permissions::PermissionGrantScope;
use codex_protocol::request_permissions::RequestPermissionProfile;
@@ -274,17 +272,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid
.features
.enable(Feature::ExecPermissionApprovals)
.expect("test setup should allow enabling request permissions");
turn_context_raw
.sandbox_policy
.set(SandboxPolicy::DangerFullAccess)
.expect("test setup should allow updating sandbox policy");
// This test is about request-permissions validation, not managed sandbox
// policy enforcement. Widen the derived sandbox policies directly so the
// command runs without depending on a platform sandbox binary.
turn_context_raw.file_system_sandbox_policy =
FileSystemSandboxPolicy::from(turn_context_raw.sandbox_policy.get());
turn_context_raw.network_sandbox_policy =
NetworkSandboxPolicy::from(turn_context_raw.sandbox_policy.get());
turn_context_raw.permission_profile = codex_protocol::models::PermissionProfile::Disabled;
let mut config = (*turn_context_raw.config).clone();
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
let config = Arc::new(config);
@@ -429,14 +417,7 @@ async fn strict_auto_review_turn_grant_forces_guardian_for_shell_policy_skip() {
.approval_policy
.set(AskForApproval::OnFailure)
.expect("test setup should allow updating approval policy");
turn_context_raw
.sandbox_policy
.set(SandboxPolicy::DangerFullAccess)
.expect("test setup should allow updating sandbox policy");
turn_context_raw.file_system_sandbox_policy =
FileSystemSandboxPolicy::from(turn_context_raw.sandbox_policy.get());
turn_context_raw.network_sandbox_policy =
NetworkSandboxPolicy::from(turn_context_raw.sandbox_policy.get());
turn_context_raw.permission_profile = codex_protocol::models::PermissionProfile::Disabled;
let mut config = (*turn_context_raw.config).clone();
config.approvals_reviewer = ApprovalsReviewer::User;
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
+3 -3
View File
@@ -692,13 +692,13 @@ async fn track_turn_resolved_config_analytics(
session_source: thread_config.session_source,
model: turn_context.model_info.slug.clone(),
model_provider: turn_context.config.model_provider_id.clone(),
sandbox_policy: turn_context.sandbox_policy.get().clone(),
sandbox_policy: turn_context.sandbox_policy(),
reasoning_effort: turn_context.reasoning_effort,
reasoning_summary: Some(turn_context.reasoning_summary),
service_tier: turn_context.config.service_tier,
approval_policy: turn_context.approval_policy.value(),
approvals_reviewer: turn_context.config.approvals_reviewer,
sandbox_network_access: turn_context.network_sandbox_policy.is_enabled(),
sandbox_network_access: turn_context.network_sandbox_policy().is_enabled(),
collaboration_mode: turn_context.collaboration_mode.mode,
personality: turn_context.personality,
is_first_turn,
@@ -1871,7 +1871,7 @@ async fn try_run_sampling_request(
feedback_tags!(
model = turn_context.model_info.slug.clone(),
approval_policy = turn_context.approval_policy.value(),
sandbox_policy = turn_context.sandbox_policy.get(),
sandbox_policy = &turn_context.sandbox_policy(),
effort = turn_context.reasoning_effort,
auth_mode = sess.services.auth_manager.auth_mode(),
features = sess.features.enabled_features(),
+40 -28
View File
@@ -3,6 +3,7 @@ use codex_model_provider::SharedModelProvider;
use codex_model_provider::create_model_provider;
use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
use codex_sandboxing::policy_transforms::effective_file_system_sandbox_policy;
use codex_sandboxing::policy_transforms::effective_network_sandbox_policy;
use std::sync::atomic::AtomicBool;
@@ -73,9 +74,6 @@ pub(crate) struct TurnContext {
pub(crate) personality: Option<Personality>,
pub(crate) approval_policy: Constrained<AskForApproval>,
pub(crate) permission_profile: PermissionProfile,
pub(crate) sandbox_policy: Constrained<SandboxPolicy>,
pub(crate) file_system_sandbox_policy: FileSystemSandboxPolicy,
pub(crate) network_sandbox_policy: NetworkSandboxPolicy,
pub(crate) network: Option<NetworkProxy>,
pub(crate) windows_sandbox_level: WindowsSandboxLevel,
pub(crate) shell_environment_policy: ShellEnvironmentPolicy,
@@ -99,6 +97,25 @@ impl TurnContext {
self.permission_profile.clone()
}
pub(crate) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
self.permission_profile.file_system_sandbox_policy()
}
pub(crate) fn network_sandbox_policy(&self) -> NetworkSandboxPolicy {
self.permission_profile.network_sandbox_policy()
}
pub(crate) fn sandbox_policy(&self) -> SandboxPolicy {
let file_system_sandbox_policy = self.file_system_sandbox_policy();
let network_sandbox_policy = self.network_sandbox_policy();
compatibility_sandbox_policy_for_permission_profile(
&self.permission_profile,
&file_system_sandbox_policy,
network_sandbox_policy,
&self.cwd,
)
}
pub(crate) fn model_context_window(&self) -> Option<i64> {
let effective_context_window_percent = self.model_info.effective_context_window_percent;
self.model_info
@@ -210,9 +227,6 @@ impl TurnContext {
personality: self.personality,
approval_policy: self.approval_policy.clone(),
permission_profile: self.permission_profile.clone(),
sandbox_policy: self.sandbox_policy.clone(),
file_system_sandbox_policy: self.file_system_sandbox_policy.clone(),
network_sandbox_policy: self.network_sandbox_policy,
network: self.network.clone(),
windows_sandbox_level: self.windows_sandbox_level,
shell_environment_policy: self.shell_environment_policy.clone(),
@@ -246,12 +260,14 @@ impl TurnContext {
&self,
additional_permissions: Option<AdditionalPermissionProfile>,
) -> FileSystemSandboxContext {
let (base_file_system_sandbox_policy, base_network_sandbox_policy) =
self.permission_profile.to_runtime_permissions();
let file_system_sandbox_policy = effective_file_system_sandbox_policy(
&self.file_system_sandbox_policy,
&base_file_system_sandbox_policy,
additional_permissions.as_ref(),
);
let network_sandbox_policy = effective_network_sandbox_policy(
self.network_sandbox_policy,
base_network_sandbox_policy,
additional_permissions.as_ref(),
);
let permissions = PermissionProfile::from_runtime_permissions_with_enforcement(
@@ -278,11 +294,12 @@ impl TurnContext {
// this comparison and the legacy projection should go away.
let legacy_file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
self.sandbox_policy.get(),
&self.sandbox_policy(),
&self.cwd,
);
(self.file_system_sandbox_policy != legacy_file_system_sandbox_policy)
.then(|| self.file_system_sandbox_policy.clone())
let file_system_sandbox_policy = self.file_system_sandbox_policy();
(file_system_sandbox_policy != legacy_file_system_sandbox_policy)
.then_some(file_system_sandbox_policy)
}
pub(crate) fn compact_prompt(&self) -> &str {
@@ -299,7 +316,7 @@ impl TurnContext {
current_date: self.current_date.clone(),
timezone: self.timezone.clone(),
approval_policy: self.approval_policy.value(),
sandbox_policy: self.sandbox_policy.get().clone(),
sandbox_policy: self.sandbox_policy(),
permission_profile: Some(self.permission_profile()),
network: self.turn_context_network_item(),
file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(),
@@ -366,15 +383,11 @@ impl Session {
per_turn_config.approvals_reviewer = session_configuration.approvals_reviewer;
per_turn_config.permissions.permission_profile =
session_configuration.permission_profile.clone();
per_turn_config.permissions.sandbox_policy = session_configuration.sandbox_policy.clone();
per_turn_config.permissions.file_system_sandbox_policy =
session_configuration.file_system_sandbox_policy.clone();
per_turn_config.permissions.network_sandbox_policy =
session_configuration.network_sandbox_policy;
let resolved_web_search_mode = resolve_web_search_mode_for_turn(
&per_turn_config.web_search_mode,
session_configuration.sandbox_policy.get(),
);
let sandbox_policy = session_configuration.sandbox_policy();
per_turn_config.permissions.sandbox_policy =
Constrained::allow_only(sandbox_policy.clone());
let resolved_web_search_mode =
resolve_web_search_mode_for_turn(&per_turn_config.web_search_mode, &sandbox_policy);
if let Err(err) = per_turn_config
.web_search_mode
.set(resolved_web_search_mode)
@@ -489,9 +502,6 @@ impl Session {
personality: session_configuration.personality,
approval_policy: session_configuration.approval_policy.clone(),
permission_profile: session_configuration.permission_profile(),
sandbox_policy: session_configuration.sandbox_policy.clone(),
file_system_sandbox_policy: session_configuration.file_system_sandbox_policy.clone(),
network_sandbox_policy: session_configuration.network_sandbox_policy,
network,
windows_sandbox_level: session_configuration.windows_sandbox_level,
shell_environment_policy: per_turn_config.permissions.shell_environment_policy.clone(),
@@ -528,8 +538,9 @@ impl Session {
let turn_environments =
self.resolve_turn_environments(&effective_environments)?;
let previous_cwd = state.session_configuration.cwd.clone();
let sandbox_policy_changed =
state.session_configuration.sandbox_policy != next.sandbox_policy;
let previous_sandbox_policy = state.session_configuration.sandbox_policy();
let next_sandbox_policy = next.sandbox_policy();
let sandbox_policy_changed = previous_sandbox_policy != next_sandbox_policy;
let codex_home = next.codex_home.clone();
let session_source = next.session_source.clone();
state.session_configuration = next.clone();
@@ -635,7 +646,8 @@ impl Session {
{
let mcp_connection_manager = self.services.mcp_connection_manager.read().await;
mcp_connection_manager.set_approval_policy(&session_configuration.approval_policy);
mcp_connection_manager.set_sandbox_policy(session_configuration.sandbox_policy.get());
mcp_connection_manager
.set_permission_profile(session_configuration.permission_profile());
}
let model_info = self
@@ -680,7 +692,7 @@ impl Session {
.as_ref()
.and_then(|started_proxy| {
Self::managed_network_proxy_active_for_sandbox_policy(
session_configuration.sandbox_policy.get(),
&session_configuration.sandbox_policy(),
)
.then(|| started_proxy.proxy())
}),
@@ -272,8 +272,9 @@ async fn effective_patch_permissions(
session.granted_session_permissions().await.as_ref(),
session.granted_turn_permissions().await.as_ref(),
);
let base_file_system_sandbox_policy = turn.file_system_sandbox_policy();
let file_system_sandbox_policy = effective_file_system_sandbox_policy(
&turn.file_system_sandbox_policy,
&base_file_system_sandbox_policy,
granted_permissions.as_ref(),
);
let effective_additional_permissions = apply_granted_turn_permissions(
+2 -1
View File
@@ -99,7 +99,8 @@ impl ToolHandler for ListDirHandler {
"dir_path must be an absolute path".to_string(),
));
}
let read_deny_matcher = ReadDenyMatcher::new(&turn.file_system_sandbox_policy, &turn.cwd);
let file_system_sandbox_policy = turn.file_system_sandbox_policy();
let read_deny_matcher = ReadDenyMatcher::new(&file_system_sandbox_policy, &turn.cwd);
if read_deny_matcher
.as_ref()
.is_some_and(|matcher| matcher.is_read_denied(&path))
@@ -37,6 +37,9 @@ use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::AgentStatus;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::FileSystemAccessMode;
use codex_protocol::protocol::FileSystemPath;
use codex_protocol::protocol::FileSystemSandboxEntry;
use codex_protocol::protocol::FileSystemSandboxPolicy;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::InterAgentCommunication;
@@ -2074,21 +2077,6 @@ async fn multi_agent_v2_spawn_surfaces_task_name_validation_errors() {
#[tokio::test]
async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
fn pick_allowed_sandbox_policy(
constraint: &crate::config::Constrained<SandboxPolicy>,
base: SandboxPolicy,
) -> SandboxPolicy {
let candidates = [
SandboxPolicy::DangerFullAccess,
SandboxPolicy::new_workspace_write_policy(),
SandboxPolicy::new_read_only_policy(),
];
candidates
.into_iter()
.find(|candidate| *candidate != base && constraint.can_set(candidate).is_ok())
.unwrap_or(base)
}
#[derive(Debug, Deserialize)]
struct SpawnAgentResult {
agent_id: String,
@@ -2098,12 +2086,17 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
let (mut session, mut turn) = make_session_and_context().await;
let manager = thread_manager();
session.services.agent_control = manager.agent_control();
let expected_sandbox = pick_allowed_sandbox_policy(
&turn.config.permissions.sandbox_policy,
turn.config.permissions.sandbox_policy.get().clone(),
);
let expected_file_system_sandbox_policy =
let expected_sandbox = turn.config.permissions.sandbox_policy.get().clone();
let mut expected_file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&expected_sandbox, &turn.cwd);
expected_file_system_sandbox_policy
.entries
.push(FileSystemSandboxEntry {
path: FileSystemPath::GlobPattern {
pattern: "**/.env".to_string(),
},
access: FileSystemAccessMode::None,
});
let expected_network_sandbox_policy = NetworkSandboxPolicy::from(&expected_sandbox);
let expected_permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&expected_sandbox),
@@ -2113,16 +2106,11 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
turn.approval_policy
.set(AskForApproval::OnRequest)
.expect("approval policy should be set");
turn.sandbox_policy
.set(expected_sandbox.clone())
.expect("sandbox policy should be set");
turn.file_system_sandbox_policy = expected_file_system_sandbox_policy.clone();
turn.network_sandbox_policy = expected_network_sandbox_policy;
turn.permission_profile = expected_permission_profile.clone();
assert_ne!(
expected_sandbox,
turn.config.permissions.sandbox_policy.get().clone(),
"test requires a runtime sandbox override that differs from base config"
expected_permission_profile,
turn.config.permissions.permission_profile(),
"test requires a runtime profile override that differs from base config"
);
let invocation = invocation(
@@ -2164,11 +2152,11 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
.expect("spawned agent thread should exist");
let child_turn = child_thread.codex.session.new_default_turn().await;
assert_eq!(
child_turn.file_system_sandbox_policy,
child_turn.file_system_sandbox_policy(),
expected_file_system_sandbox_policy
);
assert_eq!(
child_turn.network_sandbox_policy,
child_turn.network_sandbox_policy(),
expected_network_sandbox_policy
);
assert_eq!(child_turn.permission_profile(), expected_permission_profile);
@@ -3637,11 +3625,6 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
&file_system_sandbox_policy,
network_sandbox_policy,
);
turn.sandbox_policy
.set(sandbox_policy)
.expect("sandbox policy set");
turn.file_system_sandbox_policy = file_system_sandbox_policy;
turn.network_sandbox_policy = network_sandbox_policy;
turn.permission_profile = permission_profile.clone();
turn.approval_policy
.set(AskForApproval::OnRequest)
@@ -3718,7 +3701,7 @@ async fn build_agent_resume_config_clears_base_instructions() {
expected
.permissions
.sandbox_policy
.set(turn.sandbox_policy.get().clone())
.set(turn.sandbox_policy())
.expect("sandbox policy set");
assert_eq!(config, expected);
}
+4 -2
View File
@@ -513,14 +513,16 @@ impl ShellHandler {
);
emitter.begin(event_ctx).await;
let file_system_sandbox_policy = turn.file_system_sandbox_policy();
let sandbox_policy = turn.sandbox_policy();
let exec_approval_requirement = session
.services
.exec_policy
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
command: &exec_params.command,
approval_policy: turn.approval_policy.value(),
sandbox_policy: turn.sandbox_policy.get(),
file_system_sandbox_policy: &turn.file_system_sandbox_policy,
sandbox_policy: &sandbox_policy,
file_system_sandbox_policy: &file_system_sandbox_policy,
sandbox_permissions: if effective_additional_permissions.permissions_preapproved {
codex_protocol::models::SandboxPermissions::UseDefault
} else {
+2 -1
View File
@@ -359,7 +359,8 @@ impl NetworkApprovalService {
.await;
return NetworkDecision::deny(REASON_NOT_ALLOWED);
};
if !sandbox_policy_allows_network_approval_flow(turn_context.sandbox_policy.get()) {
let sandbox_policy = turn_context.sandbox_policy();
if !sandbox_policy_allows_network_approval_flow(&sandbox_policy) {
pending.set_decision(PendingApprovalDecision::Deny).await;
self.pending_host_approvals.lock().await.remove(&key);
self.record_outcome_for_single_active_call(NetworkApprovalOutcome::DeniedByPolicy(
+6 -4
View File
@@ -122,8 +122,10 @@ impl ToolOrchestrator {
// 1) Approval
let mut already_approved = false;
let file_system_sandbox_policy = turn_ctx.file_system_sandbox_policy();
let network_sandbox_policy = turn_ctx.network_sandbox_policy();
let requirement = tool.exec_approval_requirement(req).unwrap_or_else(|| {
default_exec_approval_requirement(approval_policy, &turn_ctx.file_system_sandbox_policy)
default_exec_approval_requirement(approval_policy, &file_system_sandbox_policy)
});
match requirement {
ExecApprovalRequirement::Skip { .. } => {
@@ -194,8 +196,8 @@ impl ToolOrchestrator {
let initial_sandbox = match tool.sandbox_mode_for_first_attempt(req) {
SandboxOverride::BypassSandboxFirstAttempt => SandboxType::None,
SandboxOverride::NoOverride => self.sandbox.select_initial(
&turn_ctx.file_system_sandbox_policy,
turn_ctx.network_sandbox_policy,
&file_system_sandbox_policy,
network_sandbox_policy,
tool.sandbox_preference(),
turn_ctx.windows_sandbox_level,
managed_network_active,
@@ -268,7 +270,7 @@ impl ToolOrchestrator {
&& matches!(
default_exec_approval_requirement(
approval_policy,
&turn_ctx.file_system_sandbox_policy
&file_system_sandbox_policy
),
ExecApprovalRequirement::NeedsApproval { .. }
);
@@ -390,11 +390,12 @@ async fn execve_permission_request_hook_short_circuits_prompt() -> anyhow::Resul
..HooksConfig::default()
});
let sandbox_policy = SandboxPolicy::new_read_only_policy();
turn_context.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
turn_context.sandbox_policy = Constrained::allow_any(sandbox_policy.clone());
turn_context.file_system_sandbox_policy = read_only_file_system_sandbox_policy();
turn_context.network_sandbox_policy = NetworkSandboxPolicy::Restricted;
turn_context.permission_profile = PermissionProfile::from_runtime_permissions(
&read_only_file_system_sandbox_policy(),
NetworkSandboxPolicy::Restricted,
);
let sandbox_policy = SandboxPolicy::new_read_only_policy();
let workdir = AbsolutePathBuf::try_from(std::env::current_dir()?)?;
let target = std::env::temp_dir().join("execve-hook-short-circuit.txt");
@@ -788,6 +788,8 @@ impl UnifiedExecProcessManager {
self,
context.turn.tools_config.unified_exec_shell_mode.clone(),
);
let file_system_sandbox_policy = context.turn.file_system_sandbox_policy();
let sandbox_policy = context.turn.sandbox_policy();
let exec_approval_requirement = context
.session
.services
@@ -795,8 +797,8 @@ impl UnifiedExecProcessManager {
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
command: &request.command,
approval_policy: context.turn.approval_policy.value(),
sandbox_policy: context.turn.sandbox_policy.get(),
file_system_sandbox_policy: &context.turn.file_system_sandbox_policy,
sandbox_policy: &sandbox_policy,
file_system_sandbox_policy: &file_system_sandbox_policy,
sandbox_permissions: if request.additional_permissions_preapproved {
crate::sandboxing::SandboxPermissions::UseDefault
} else {