core: remove SandboxPolicy from production core

This commit is contained in:
Michael Bolin
2026-06-01 13:55:29 -07:00
parent 7c285f9e9c
commit c8413a24d0
40 changed files with 674 additions and 439 deletions
@@ -234,10 +234,6 @@ impl CommandExecRequestProcessor {
config.effective_workspace_roots(),
)
} else if let Some(policy) = sandbox_policy.map(|policy| policy.to_core()) {
self.config
.permissions
.can_set_legacy_sandbox_policy(&policy, &sandbox_cwd)
.map_err(|err| invalid_request(format!("invalid sandbox policy: {err}")))?;
let file_system_sandbox_policy =
codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&policy, &sandbox_cwd);
let network_sandbox_policy =
@@ -96,21 +96,24 @@ fn collect_resume_override_mismatches(
}
}
if let Some(requested_sandbox) = request.sandbox.as_ref() {
let active_sandbox = config_snapshot.sandbox_policy();
let active_sandbox = thread_response_sandbox_policy(
&config_snapshot.permission_profile,
config_snapshot.cwd.as_path(),
);
let sandbox_matches = matches!(
(requested_sandbox, &active_sandbox),
(
SandboxMode::ReadOnly,
codex_protocol::protocol::SandboxPolicy::ReadOnly { .. }
codex_app_server_protocol::SandboxPolicy::ReadOnly { .. }
) | (
SandboxMode::WorkspaceWrite,
codex_protocol::protocol::SandboxPolicy::WorkspaceWrite { .. }
codex_app_server_protocol::SandboxPolicy::WorkspaceWrite { .. }
) | (
SandboxMode::DangerFullAccess,
codex_protocol::protocol::SandboxPolicy::DangerFullAccess
codex_app_server_protocol::SandboxPolicy::DangerFullAccess
) | (
SandboxMode::DangerFullAccess,
codex_protocol::protocol::SandboxPolicy::ExternalSandbox { .. }
codex_app_server_protocol::SandboxPolicy::ExternalSandbox { .. }
)
);
if !sandbox_matches {
@@ -32,6 +32,26 @@ fn resolve_runtime_workspace_roots(
resolved_roots
}
fn permission_profile_from_legacy_sandbox_policy(
sandbox_policy: &codex_protocol::protocol::SandboxPolicy,
cwd: &AbsolutePathBuf,
current_permission_profile: &codex_protocol::models::PermissionProfile,
) -> codex_protocol::models::PermissionProfile {
let current_file_system_sandbox_policy =
current_permission_profile.file_system_sandbox_policy();
let file_system_sandbox_policy =
codex_protocol::permissions::FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
sandbox_policy,
cwd.as_path(),
&current_file_system_sandbox_policy,
);
codex_protocol::models::PermissionProfile::from_runtime_permissions_with_enforcement(
codex_protocol::models::SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy),
&file_system_sandbox_policy,
codex_protocol::permissions::NetworkSandboxPolicy::from(sandbox_policy),
)
}
fn map_additional_context(
additional_context: Option<HashMap<String, AdditionalContextEntry>>,
) -> BTreeMap<String, CoreAdditionalContextEntry> {
@@ -524,7 +544,10 @@ impl TurnRequestProcessor {
// `thread/settings/update` only acknowledges that the update was queued.
// Clients that send dependent partial updates should wait for
// `thread/settings/updated` or combine the fields in one request.
let snapshot = if permissions.is_some() || runtime_workspace_roots_request.is_some() {
let snapshot = if permissions.is_some()
|| runtime_workspace_roots_request.is_some()
|| sandbox_policy.is_some()
{
Some(thread.config_snapshot().await)
} else {
None
@@ -564,6 +587,26 @@ impl TurnRequestProcessor {
let approvals_reviewer =
approvals_reviewer.map(codex_app_server_protocol::ApprovalsReviewer::to_core);
let sandbox_policy = sandbox_policy.map(|policy| policy.to_core());
let sandbox_policy_permission_profile = if let Some(sandbox_policy) =
sandbox_policy.as_ref()
{
let Some(snapshot) = snapshot.as_ref() else {
return Err(internal_error(format!(
"{method} sandbox policy selection missing thread snapshot"
)));
};
let sandbox_policy_cwd = cwd
.as_ref()
.map(|cwd| AbsolutePathBuf::resolve_path_against_base(cwd, snapshot.cwd.as_path()))
.unwrap_or_else(|| snapshot.cwd.clone());
Some(permission_profile_from_legacy_sandbox_policy(
sandbox_policy,
&sandbox_policy_cwd,
&snapshot.permission_profile,
))
} else {
None
};
let (permission_profile, active_permission_profile, profile_workspace_roots) =
if let Some(permissions) = permissions {
let Some(snapshot) = snapshot.as_ref() else {
@@ -623,8 +666,9 @@ impl TurnRequestProcessor {
workspace_roots: runtime_workspace_roots.clone(),
approval_policy,
approvals_reviewer,
sandbox_policy: sandbox_policy.clone(),
permission_profile: permission_profile.clone(),
permission_profile: permission_profile
.clone()
.or_else(|| sandbox_policy_permission_profile.clone()),
active_permission_profile: active_permission_profile.clone(),
profile_workspace_roots: profile_workspace_roots.clone(),
windows_sandbox_level: None,
+26
View File
@@ -12,6 +12,8 @@ use std::time::Duration;
use codex_exec_server::Environment;
use codex_exec_server::EnvironmentManager;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use serde::Deserialize;
@@ -29,6 +31,30 @@ pub struct SandboxState {
pub use_legacy_landlock: bool,
}
impl SandboxState {
pub fn from_permission_profile(
permission_profile: PermissionProfile,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
codex_linux_sandbox_exe: Option<PathBuf>,
sandbox_cwd: PathBuf,
use_legacy_landlock: bool,
) -> Self {
let sandbox_policy = permission_profile.compatibility_sandbox_policy(
file_system_sandbox_policy,
network_sandbox_policy,
sandbox_cwd.as_path(),
);
Self {
permission_profile: Some(permission_profile),
sandbox_policy,
codex_linux_sandbox_exe,
sandbox_cwd,
use_legacy_landlock,
}
}
}
/// Runtime context used when resolving per-server MCP environments.
///
/// `McpConfig` describes what servers exist. This value carries the canonical
+14 -12
View File
@@ -14,8 +14,9 @@ When using the workspace-write sandbox policy, the Seatbelt profile allows
writes under the configured writable roots while keeping `.git` (directory or
pointer file), the resolved `gitdir:` target, and `.codex` read-only.
Network access and filesystem read/write roots are controlled by
`SandboxPolicy`. Seatbelt consumes the resolved policy and enforces it.
Network access and filesystem read/write roots are controlled by the resolved
permission profile. Seatbelt consumes the derived runtime policies and enforces
them.
Seatbelt also keeps the legacy default preferences read access
(`user-preference-read`) needed for cfprefs-backed macOS behavior.
@@ -24,14 +25,15 @@ Seatbelt also keeps the legacy default preferences read access
Expects the binary containing `codex-core` to run the equivalent of `codex sandbox` when `arg0` is `codex-linux-sandbox`. See the `codex-arg0` crate for details.
Legacy `SandboxPolicy` / `sandbox_mode` configs are still supported on Linux.
They can continue to use the legacy Landlock path when the split filesystem
policy is sandbox-equivalent to the legacy model after `cwd` resolution.
Legacy `sandbox_mode` configs are still supported on Linux at configuration and
protocol boundaries. They can continue to use the legacy Landlock path when the
split filesystem policy is sandbox-equivalent to the older mode model after
`cwd` resolution.
Split filesystem policies that need direct `FileSystemSandboxPolicy`
enforcement, such as read-only or denied carveouts under a broader writable
root, automatically route through bubblewrap. The legacy Landlock path is used
only when the split filesystem policy round-trips through the legacy
`SandboxPolicy` model without changing semantics. That includes overlapping
mode model without changing semantics. That includes overlapping
cases like `/repo = write`, `/repo/a = none`, `/repo/a/b = write`, where the
more specific writable child must reopen under a denied parent.
@@ -50,14 +52,14 @@ bubblewrap path before invoking `bwrap`.
### Windows
Legacy `SandboxPolicy` / `sandbox_mode` configs are still supported on
Windows. Legacy `read-only` and `workspace-write` policies imply full
Legacy `sandbox_mode` configs are still supported on Windows at configuration
and protocol boundaries. Legacy `read-only` and `workspace-write` modes imply full
filesystem read access; exact readable roots are represented by split
filesystem policies instead.
The elevated Windows sandbox also supports:
- legacy `ReadOnly` and `WorkspaceWrite` behavior
- legacy `read-only` and `workspace-write` behavior
- split filesystem policies that need exact readable roots, exact writable
roots, or extra read-only carveouts under writable roots
- backend-managed system read roots required for basic execution, such as
@@ -65,14 +67,14 @@ The elevated Windows sandbox also supports:
`C:\ProgramData`, when a split filesystem policy requests platform defaults
The unelevated restricted-token backend still supports the legacy full-read
Windows model for legacy `ReadOnly` and `WorkspaceWrite` behavior. It also
Windows model for legacy `read-only` and `workspace-write` behavior. It also
supports a narrow split-filesystem subset: full-read split policies whose
writable roots still match the legacy `WorkspaceWrite` root set, but add extra
writable roots still match the legacy workspace-write root set, but add extra
read-only carveouts under those writable roots.
New `[permissions]` / split filesystem policies remain supported on Windows
only when they can be enforced directly by the selected Windows backend or
round-trip through the legacy `SandboxPolicy` model without changing semantics.
round-trip through the legacy mode model without changing semantics.
Policies that would require direct explicit unreadable carveouts (`none`) or
reopened writable descendants under read-only carveouts still fail closed
instead of running with weaker enforcement.
+8 -1
View File
@@ -337,7 +337,14 @@ writable_roots = ["./sandbox-root"]
false
);
match &config.legacy_sandbox_policy() {
let permission_profile = config.permissions.effective_permission_profile();
let file_system_policy = permission_profile.file_system_sandbox_policy();
let sandbox_policy = permission_profile.compatibility_sandbox_policy(
&file_system_policy,
permission_profile.network_sandbox_policy(),
config.cwd.as_path(),
);
match &sandbox_policy {
SandboxPolicy::WorkspaceWrite { network_access, .. } => {
assert_eq!(*network_access, true);
}
-16
View File
@@ -25,7 +25,6 @@ use codex_protocol::protocol::AdditionalContextEntry;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionConfiguredEvent;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::Submission;
@@ -73,18 +72,6 @@ pub struct ThreadConfigSnapshot {
pub thread_source: Option<ThreadSource>,
}
impl ThreadConfigSnapshot {
pub fn sandbox_policy(&self) -> SandboxPolicy {
let file_system_sandbox_policy = self.permission_profile.file_system_sandbox_policy();
codex_sandboxing::compatibility_sandbox_policy_for_permission_profile(
&self.permission_profile,
&file_system_sandbox_policy,
self.permission_profile.network_sandbox_policy(),
self.cwd.as_path(),
)
}
}
/// Thread settings overrides that app-server validates before starting a turn.
#[derive(Clone, Default)]
pub struct CodexThreadSettingsOverrides {
@@ -93,7 +80,6 @@ pub struct CodexThreadSettingsOverrides {
pub profile_workspace_roots: Option<Vec<AbsolutePathBuf>>,
pub approval_policy: Option<AskForApproval>,
pub approvals_reviewer: Option<ApprovalsReviewer>,
pub sandbox_policy: Option<SandboxPolicy>,
pub permission_profile: Option<PermissionProfile>,
pub active_permission_profile: Option<ActivePermissionProfile>,
pub windows_sandbox_level: Option<WindowsSandboxLevel>,
@@ -320,7 +306,6 @@ impl CodexThread {
profile_workspace_roots,
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
active_permission_profile,
windows_sandbox_level,
@@ -347,7 +332,6 @@ impl CodexThread {
profile_workspace_roots,
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
active_permission_profile,
windows_sandbox_level,
@@ -850,7 +850,14 @@ writable_roots = ["~/code"]
.await?;
let expected_root = AbsolutePathBuf::from_absolute_path(home.join("code"))?;
match &config.legacy_sandbox_policy() {
let permission_profile = config.permissions.effective_permission_profile();
let file_system_policy = permission_profile.file_system_sandbox_policy();
let sandbox_policy = permission_profile.compatibility_sandbox_policy(
&file_system_policy,
permission_profile.network_sandbox_policy(),
config.cwd.as_path(),
);
match &sandbox_policy {
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
assert_eq!(
writable_roots
+34 -24
View File
@@ -109,6 +109,16 @@ use std::path::Path;
use std::time::Duration;
use tempfile::TempDir;
fn legacy_sandbox_policy(config: &Config) -> SandboxPolicy {
let permission_profile = config.permissions.effective_permission_profile();
let file_system_policy = permission_profile.file_system_sandbox_policy();
permission_profile.compatibility_sandbox_policy(
&file_system_policy,
permission_profile.network_sandbox_policy(),
config.cwd.as_path(),
)
}
fn stdio_mcp(command: &str) -> McpServerConfig {
McpServerConfig {
transport: McpServerTransportConfig::Stdio {
@@ -1650,7 +1660,7 @@ async fn default_permissions_profile_populates_runtime_sandbox_policy() -> std::
]),
);
assert_eq!(
&config.legacy_sandbox_policy(),
&legacy_sandbox_policy(&config),
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
network_access: false,
@@ -1761,7 +1771,7 @@ async fn permission_profile_override_populates_runtime_permissions() -> std::io:
);
assert_eq!(config.permissions.active_permission_profile(), None);
assert_eq!(
&config.legacy_sandbox_policy(),
&legacy_sandbox_policy(&config),
&SandboxPolicy::DangerFullAccess
);
Ok(())
@@ -1817,7 +1827,7 @@ async fn permission_profile_override_preserves_managed_unrestricted_filesystem()
permission_profile
);
assert_eq!(
&config.legacy_sandbox_policy(),
&legacy_sandbox_policy(&config),
&SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Restricted,
}
@@ -1846,7 +1856,7 @@ async fn managed_unrestricted_permission_profile_still_enables_network_requireme
)
.await?;
assert_eq!(
&config.legacy_sandbox_policy(),
&legacy_sandbox_policy(&config),
&SandboxPolicy::DangerFullAccess,
"the legacy projection is intentionally lossy for managed unrestricted profiles"
);
@@ -1922,7 +1932,7 @@ async fn permission_profile_override_keeps_memories_root_out_of_legacy_projectio
.can_write_path_with_cwd(memories_root.as_path(), cwd.path())
);
assert_eq!(
&config.legacy_sandbox_policy(),
&legacy_sandbox_policy(&config),
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![],
network_access: false,
@@ -2560,7 +2570,7 @@ async fn implicit_builtin_workspace_profile_preserves_sandbox_workspace_write_se
"implicit :workspace cannot be faithfully re-selected when it includes \
legacy sandbox_workspace_write settings"
);
match config.legacy_sandbox_policy() {
match legacy_sandbox_policy(&config) {
SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access,
@@ -2829,7 +2839,7 @@ async fn permissions_profiles_allow_direct_write_roots_outside_workspace_root()
.can_write_path_with_cwd(external_write_path.as_path(), cwd.path())
);
assert_eq!(
&config.legacy_sandbox_policy(),
&legacy_sandbox_policy(&config),
&SandboxPolicy::WorkspaceWrite {
writable_roots: vec![external_write_path],
network_access: false,
@@ -2944,7 +2954,7 @@ async fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<(
}]),
);
assert_eq!(
&config.legacy_sandbox_policy(),
&legacy_sandbox_policy(&config),
&SandboxPolicy::ReadOnly {
network_access: false,
}
@@ -3015,7 +3025,7 @@ async fn permissions_profiles_allow_missing_filesystem_with_warning() -> std::io
FileSystemSandboxPolicy::restricted(Vec::new())
);
assert_eq!(
&config.legacy_sandbox_policy(),
&legacy_sandbox_policy(&config),
&SandboxPolicy::ReadOnly {
network_access: false,
}
@@ -3151,7 +3161,7 @@ async fn permissions_profiles_allow_network_enablement() -> std::io::Result<()>
config.permissions.network_sandbox_policy().is_enabled(),
"expected network sandbox policy to be enabled",
);
assert!(config.legacy_sandbox_policy().has_full_network_access());
assert!(legacy_sandbox_policy(&config).has_full_network_access());
Ok(())
}
@@ -3647,7 +3657,7 @@ exclude_slash_tmp = true
)
.await?;
let sandbox_policy = config.legacy_sandbox_policy();
let sandbox_policy = legacy_sandbox_policy(&config);
let file_system_policy = config.permissions.file_system_sandbox_policy();
let network_policy = config.permissions.network_sandbox_policy();
@@ -4514,12 +4524,12 @@ async fn add_dir_override_extends_workspace_writable_roots() -> std::io::Result<
let expected_backend = backend.abs();
if cfg!(target_os = "windows") {
match &config.legacy_sandbox_policy() {
match &legacy_sandbox_policy(&config) {
SandboxPolicy::ReadOnly { .. } => {}
other => panic!("expected read-only policy on Windows, got {other:?}"),
}
} else {
match &config.legacy_sandbox_policy() {
match &legacy_sandbox_policy(&config) {
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
assert_eq!(
writable_roots
@@ -4598,7 +4608,7 @@ async fn workspace_write_includes_configured_writable_root_once_without_memories
.await?;
if cfg!(target_os = "windows") {
match &config.legacy_sandbox_policy() {
match &legacy_sandbox_policy(&config) {
SandboxPolicy::ReadOnly { .. } => {}
other => panic!("expected read-only policy on Windows, got {other:?}"),
}
@@ -4609,7 +4619,7 @@ async fn workspace_write_includes_configured_writable_root_once_without_memories
memories_root.display()
);
let expected_memories_root = memories_root.abs();
match &config.legacy_sandbox_policy() {
match &legacy_sandbox_policy(&config) {
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
assert!(!writable_roots.contains(&expected_memories_root));
assert_eq!(
@@ -4669,12 +4679,12 @@ async fn memory_tool_makes_memories_root_readable_without_creating_or_widening_w
assert!(!file_system_policy.can_write_path_with_cwd(memories_root_abs.as_path(), cwd.path()));
if cfg!(target_os = "windows") {
match &config.legacy_sandbox_policy() {
match &legacy_sandbox_policy(&config) {
SandboxPolicy::ReadOnly { .. } => {}
other => panic!("expected read-only policy on Windows, got {other:?}"),
}
} else {
match &config.legacy_sandbox_policy() {
match &legacy_sandbox_policy(&config) {
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
assert!(!writable_roots.contains(&memories_root_abs));
}
@@ -4985,7 +4995,7 @@ async fn unselected_profile_sandbox_mode_is_ignored() -> std::io::Result<()> {
.await?;
assert_eq!(
config.legacy_sandbox_policy(),
legacy_sandbox_policy(&config),
SandboxPolicy::new_read_only_policy()
);
@@ -8932,7 +8942,7 @@ async fn test_untrusted_project_gets_unless_trusted_approval_policy() -> anyhow:
if cfg!(target_os = "windows") {
assert!(
matches!(
&config.legacy_sandbox_policy(),
&legacy_sandbox_policy(&config),
SandboxPolicy::ReadOnly { .. }
),
"Expected ReadOnly on Windows"
@@ -8940,7 +8950,7 @@ async fn test_untrusted_project_gets_unless_trusted_approval_policy() -> anyhow:
} else {
assert!(
matches!(
&config.legacy_sandbox_policy(),
&legacy_sandbox_policy(&config),
SandboxPolicy::WorkspaceWrite { .. }
),
"Expected WorkspaceWrite sandbox for untrusted project"
@@ -8966,7 +8976,7 @@ async fn requirements_disallowing_default_sandbox_falls_back_to_required_default
.build()
.await?;
assert_eq!(
config.legacy_sandbox_policy(),
legacy_sandbox_policy(&config),
SandboxPolicy::new_read_only_policy()
);
Ok(())
@@ -9013,7 +9023,7 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s
.build()
.await?;
assert_eq!(
config.legacy_sandbox_policy(),
legacy_sandbox_policy(&config),
SandboxPolicy::new_read_only_policy()
);
Ok(())
@@ -9150,7 +9160,7 @@ async fn permission_profile_override_falls_back_when_disallowed_by_requirements(
.await?;
let expected_sandbox_policy = SandboxPolicy::new_read_only_policy();
assert_eq!(config.legacy_sandbox_policy(), expected_sandbox_policy);
assert_eq!(legacy_sandbox_policy(&config), expected_sandbox_policy);
assert_eq!(
config.permissions.effective_permission_profile(),
PermissionProfile::read_only()
@@ -9262,7 +9272,7 @@ async fn permission_profile_override_preserves_split_write_roots() -> std::io::R
.can_write_path_with_cwd(outside_root.as_path(), config.cwd.as_path())
);
assert!(matches!(
&config.legacy_sandbox_policy(),
&legacy_sandbox_policy(&config),
SandboxPolicy::WorkspaceWrite { .. }
));
assert_eq!(
+2 -96
View File
@@ -93,13 +93,11 @@ use codex_protocol::config_types::WebSearchMode;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::ActivePermissionProfile;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::SandboxEnforcement;
use codex_protocol::openai_models::ModelsResponse;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::AbsolutePathBufGuard;
use rmcp::model::ElicitationCapability;
@@ -147,7 +145,6 @@ pub use codex_config::ConstraintError;
pub use codex_config::ConstraintResult;
pub use codex_config::LoaderOverrides;
pub use codex_network_proxy::NetworkProxyAuditMetadata;
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
pub use codex_sandboxing::system_bwrap_warning;
pub use managed_features::ManagedFeatures;
pub use network_proxy_spec::NetworkProxySpec;
@@ -440,79 +437,6 @@ impl Permissions {
self.permission_profile().network_sandbox_policy()
}
/// Legacy compatibility projection derived from the canonical profile.
pub fn legacy_sandbox_policy(&self, cwd: &Path) -> SandboxPolicy {
let permission_profile = self.materialized_permission_profile();
let file_system_sandbox_policy = permission_profile.file_system_sandbox_policy();
compatibility_sandbox_policy_for_permission_profile(
&permission_profile,
&file_system_sandbox_policy,
permission_profile.network_sandbox_policy(),
cwd,
)
}
/// Check whether a legacy sandbox policy can be applied to this permission
/// set after projecting it into the canonical permission profile.
pub fn can_set_legacy_sandbox_policy(
&self,
sandbox_policy: &SandboxPolicy,
cwd: &Path,
) -> ConstraintResult<()> {
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd);
let network_sandbox_policy = NetworkSandboxPolicy::from(sandbox_policy);
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy),
&file_system_sandbox_policy,
network_sandbox_policy,
);
self.permission_profile_state
.can_set_legacy_permission_profile(&permission_profile)
}
/// Set permissions from a legacy sandbox policy and keep every permission
/// projection in sync.
pub fn set_legacy_sandbox_policy(
&mut self,
sandbox_policy: SandboxPolicy,
cwd: &Path,
) -> ConstraintResult<()> {
self.can_set_legacy_sandbox_policy(&sandbox_policy, cwd)?;
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&sandbox_policy, cwd);
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&file_system_sandbox_policy,
network_sandbox_policy,
);
self.workspace_roots = match &sandbox_policy {
SandboxPolicy::WorkspaceWrite { writable_roots, .. } => {
let mut workspace_roots = vec![
AbsolutePathBuf::from_absolute_path(cwd)
.unwrap_or_else(|_| AbsolutePathBuf::resolve_path_against_base(cwd, "/")),
];
for root in writable_roots {
if !workspace_roots.iter().any(|existing| existing == root) {
workspace_roots.push(root.clone());
}
}
workspace_roots
}
SandboxPolicy::DangerFullAccess
| SandboxPolicy::ExternalSandbox { .. }
| SandboxPolicy::ReadOnly { .. } => vec![
AbsolutePathBuf::from_absolute_path(cwd)
.unwrap_or_else(|_| AbsolutePathBuf::resolve_path_against_base(cwd, "/")),
],
};
self.permission_profile_state
.set_legacy_permission_profile(permission_profile)?;
Ok(())
}
/// Set permissions from the canonical profile.
pub fn set_permission_profile(
&mut self,
@@ -1281,24 +1205,6 @@ impl ConfigBuilder {
}
impl Config {
pub fn legacy_sandbox_policy(&self) -> SandboxPolicy {
self.permissions.legacy_sandbox_policy(self.cwd.as_path())
}
pub fn set_legacy_sandbox_policy(
&mut self,
sandbox_policy: SandboxPolicy,
) -> ConstraintResult<()> {
self.workspace_roots_explicit = matches!(
&sandbox_policy,
SandboxPolicy::WorkspaceWrite { writable_roots, .. } if !writable_roots.is_empty()
);
self.permissions
.set_legacy_sandbox_policy(sandbox_policy, self.cwd.as_path())?;
self.workspace_roots = self.permissions.workspace_roots().to_vec();
Ok(())
}
pub fn effective_workspace_roots(&self) -> Vec<AbsolutePathBuf> {
let mut workspace_roots = self.workspace_roots.clone();
workspace_roots.extend(self.permissions.profile_workspace_roots().iter().cloned());
@@ -2899,7 +2805,7 @@ impl Config {
// should still flow through the canonical profile representation.
// Derive the old `sandbox_mode` defaults as a profile first, then
// keep a legacy-compatible projection only for the remaining code
// paths that still speak `SandboxPolicy`.
// paths that still speak `sandbox_mode`.
let mut permission_profile = cfg
.derive_permission_profile(
sandbox_mode,
@@ -2910,7 +2816,7 @@ impl Config {
)
.await;
// The legacy-derived profiles above are expected to be
// representable as `SandboxPolicy`. This guard keeps the old safe
// representable as a legacy sandbox mode. This guard keeps the old safe
// fallback behavior if future changes make this branch derive a
// profile with split-only filesystem semantics, such as root write
// with carveouts or writes that are not expressible as
+23 -31
View File
@@ -38,7 +38,6 @@ use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ExecCommandOutputDeltaEvent;
use codex_protocol::protocol::ExecOutputStream;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxTransformRequest;
@@ -419,11 +418,10 @@ pub fn build_exec_request(
exec_req.windows_sandbox_level,
exec_req.network.is_some(),
);
let sandbox_policy = exec_req.compatibility_sandbox_policy();
exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend {
resolve_windows_elevated_filesystem_overrides(
exec_req.sandbox,
&sandbox_policy,
&exec_req.permission_profile,
&exec_req.file_system_sandbox_policy,
exec_req.network_sandbox_policy,
sandbox_cwd,
@@ -432,7 +430,7 @@ pub fn build_exec_request(
} else {
resolve_windows_restricted_token_filesystem_overrides(
exec_req.sandbox,
&sandbox_policy,
&exec_req.permission_profile,
&exec_req.file_system_sandbox_policy,
exec_req.network_sandbox_policy,
sandbox_cwd,
@@ -1006,21 +1004,16 @@ async fn exec(
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
fn should_use_windows_restricted_token_sandbox(
sandbox: SandboxType,
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
) -> bool {
sandbox == SandboxType::WindowsRestrictedToken
&& file_system_sandbox_policy.kind == FileSystemSandboxKind::Restricted
&& !matches!(
sandbox_policy,
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. }
)
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn unsupported_windows_restricted_token_sandbox_reason(
sandbox: SandboxType,
sandbox_policy: &SandboxPolicy,
permission_profile: &PermissionProfile,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_policy_cwd: &AbsolutePathBuf,
@@ -1029,7 +1022,7 @@ pub(crate) fn unsupported_windows_restricted_token_sandbox_reason(
if windows_sandbox_level == WindowsSandboxLevel::Elevated {
resolve_windows_elevated_filesystem_overrides(
sandbox,
sandbox_policy,
permission_profile,
file_system_sandbox_policy,
network_sandbox_policy,
sandbox_policy_cwd,
@@ -1039,7 +1032,7 @@ pub(crate) fn unsupported_windows_restricted_token_sandbox_reason(
} else {
resolve_windows_restricted_token_filesystem_overrides(
sandbox,
sandbox_policy,
permission_profile,
file_system_sandbox_policy,
network_sandbox_policy,
sandbox_policy_cwd,
@@ -1051,7 +1044,7 @@ pub(crate) fn unsupported_windows_restricted_token_sandbox_reason(
pub(crate) fn resolve_windows_restricted_token_filesystem_overrides(
sandbox: SandboxType,
sandbox_policy: &SandboxPolicy,
permission_profile: &PermissionProfile,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_policy_cwd: &AbsolutePathBuf,
@@ -1066,22 +1059,15 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides(
let needs_direct_runtime_enforcement = file_system_sandbox_policy
.needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd);
if should_use_windows_restricted_token_sandbox(
sandbox,
sandbox_policy,
file_system_sandbox_policy,
) && !needs_direct_runtime_enforcement
if should_use_windows_restricted_token_sandbox(sandbox, file_system_sandbox_policy)
&& !needs_direct_runtime_enforcement
{
return Ok(None);
}
if !should_use_windows_restricted_token_sandbox(
sandbox,
sandbox_policy,
file_system_sandbox_policy,
) {
if !should_use_windows_restricted_token_sandbox(sandbox, file_system_sandbox_policy) {
return Err(format!(
"windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, legacy_policy={sandbox_policy:?}; refusing to run unsandboxed",
"windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, permission_profile={permission_profile:?}; refusing to run unsandboxed",
file_system_sandbox_policy.kind,
));
}
@@ -1108,6 +1094,11 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides(
);
}
let sandbox_policy = permission_profile.compatibility_sandbox_policy(
file_system_sandbox_policy,
network_sandbox_policy,
sandbox_policy_cwd.as_path(),
);
let legacy_writable_roots = sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd);
let split_writable_roots =
file_system_sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd);
@@ -1204,7 +1195,7 @@ fn windows_policy_has_root_read_access(
pub(crate) fn resolve_windows_elevated_filesystem_overrides(
sandbox: SandboxType,
sandbox_policy: &SandboxPolicy,
permission_profile: &PermissionProfile,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_policy_cwd: &AbsolutePathBuf,
@@ -1214,13 +1205,9 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides(
return Ok(None);
}
if !should_use_windows_restricted_token_sandbox(
sandbox,
sandbox_policy,
file_system_sandbox_policy,
) {
if !should_use_windows_restricted_token_sandbox(sandbox, file_system_sandbox_policy) {
return Err(format!(
"windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, legacy_policy={sandbox_policy:?}; refusing to run unsandboxed",
"windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, permission_profile={permission_profile:?}; refusing to run unsandboxed",
file_system_sandbox_policy.kind,
));
}
@@ -1242,6 +1229,11 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides(
let needs_direct_runtime_enforcement = file_system_sandbox_policy
.needs_direct_runtime_enforcement(network_sandbox_policy, sandbox_policy_cwd);
let normalize_path = |path: PathBuf| dunce::canonicalize(&path).unwrap_or(path);
let sandbox_policy = permission_profile.compatibility_sandbox_policy(
file_system_sandbox_policy,
network_sandbox_policy,
sandbox_policy_cwd.as_path(),
);
let legacy_writable_roots = sandbox_policy.get_writable_roots_with_cwd(sandbox_policy_cwd);
let legacy_root_paths: BTreeSet<PathBuf> = legacy_writable_roots
.iter()
+53 -16
View File
@@ -1,6 +1,8 @@
use super::*;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::SandboxEnforcement;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::SandboxType;
use core_test_support::PathBufExt;
use core_test_support::PathExt;
@@ -26,6 +28,17 @@ fn make_exec_output(
}
}
fn permission_profile_for_runtime_permissions(
policy: &SandboxPolicy,
file_system_policy: &FileSystemSandboxPolicy,
) -> PermissionProfile {
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(policy),
file_system_policy,
NetworkSandboxPolicy::from(policy),
)
}
#[test]
fn sandbox_detection_requires_keywords() {
let output = make_exec_output(/*exit_code*/ 1, "", "", "");
@@ -387,7 +400,6 @@ fn windows_restricted_token_skips_external_sandbox_policies() {
assert_eq!(
should_use_windows_restricted_token_sandbox(
SandboxType::WindowsRestrictedToken,
&policy,
&file_system_policy,
),
false
@@ -402,7 +414,6 @@ fn windows_restricted_token_runs_for_legacy_restricted_policies() {
assert_eq!(
should_use_windows_restricted_token_sandbox(
SandboxType::WindowsRestrictedToken,
&policy,
&file_system_policy,
),
true
@@ -431,19 +442,21 @@ fn windows_restricted_token_rejects_network_only_restrictions() {
network_access: codex_protocol::protocol::NetworkAccess::Restricted,
};
let file_system_policy = FileSystemSandboxPolicy::unrestricted();
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd");
assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&sandbox_policy_cwd,
WindowsSandboxLevel::RestrictedToken,
),
Some(
"windows sandbox backend cannot enforce file_system=Unrestricted, network=Restricted, legacy_policy=ExternalSandbox { network_access: Restricted }; refusing to run unsandboxed".to_string()
"windows sandbox backend cannot enforce file_system=Unrestricted, network=Restricted, permission_profile=Managed { file_system: Unrestricted, network: Restricted }; refusing to run unsandboxed".to_string()
)
);
}
@@ -452,12 +465,14 @@ fn windows_restricted_token_rejects_network_only_restrictions() {
fn windows_restricted_token_allows_legacy_restricted_policies() {
let policy = SandboxPolicy::new_read_only_policy();
let file_system_policy = FileSystemSandboxPolicy::from(&policy);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd");
assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&sandbox_policy_cwd,
@@ -476,12 +491,14 @@ fn windows_restricted_token_allows_legacy_workspace_write_policies() {
exclude_slash_tmp: true,
};
let file_system_policy = FileSystemSandboxPolicy::from(&policy);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
let sandbox_policy_cwd = AbsolutePathBuf::current_dir().expect("cwd");
assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&sandbox_policy_cwd,
@@ -508,11 +525,13 @@ fn windows_elevated_allows_split_restricted_read_policies() {
access: codex_protocol::permissions::FileSystemAccessMode::Read,
},
]);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&temp_dir.path().abs(),
@@ -550,11 +569,13 @@ fn windows_restricted_token_rejects_split_only_filesystem_policies() {
access: codex_protocol::permissions::FileSystemAccessMode::Read,
},
]);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&temp_dir.path().abs(),
@@ -593,11 +614,13 @@ fn windows_restricted_token_rejects_root_write_read_only_carveouts() {
access: codex_protocol::permissions::FileSystemAccessMode::Read,
},
]);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&temp_dir.path().abs(),
@@ -644,6 +667,8 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {
access: codex_protocol::permissions::FileSystemAccessMode::Read,
},
]);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
// The legacy workspace-write root already protects top-level `.codex`, so
// the restricted-token overlay only needs the extra read-only docs carveout.
@@ -652,7 +677,7 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {
assert_eq!(
resolve_windows_restricted_token_filesystem_overrides(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&cwd,
@@ -702,11 +727,13 @@ fn windows_restricted_token_rejects_unreadable_split_carveouts() {
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
},
]);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
assert_eq!(
resolve_windows_restricted_token_filesystem_overrides(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&cwd,
@@ -737,11 +764,13 @@ fn windows_elevated_supports_split_restricted_read_roots() {
access: codex_protocol::permissions::FileSystemAccessMode::Read,
},
]);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
assert_eq!(
resolve_windows_elevated_filesystem_overrides(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&temp_dir.path().abs(),
@@ -792,11 +821,13 @@ fn windows_elevated_supports_split_write_read_carveouts() {
access: codex_protocol::permissions::FileSystemAccessMode::Read,
},
]);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
assert_eq!(
resolve_windows_elevated_filesystem_overrides(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&temp_dir.path().abs(),
@@ -850,11 +881,13 @@ fn windows_elevated_supports_unreadable_split_carveouts() {
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
},
]);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
assert_eq!(
resolve_windows_elevated_filesystem_overrides(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&temp_dir.path().abs(),
@@ -912,11 +945,13 @@ fn windows_elevated_supports_unreadable_globs() {
access: codex_protocol::permissions::FileSystemAccessMode::Deny,
},
]);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
assert_eq!(
resolve_windows_elevated_filesystem_overrides(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&temp_dir.path().abs(),
@@ -977,11 +1012,13 @@ fn windows_elevated_rejects_reopened_writable_descendants() {
access: codex_protocol::permissions::FileSystemAccessMode::Write,
},
]);
let permission_profile =
permission_profile_for_runtime_permissions(&policy, &file_system_policy);
assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
SandboxType::WindowsRestrictedToken,
&policy,
&permission_profile,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
&temp_dir.path().abs(),
@@ -20,7 +20,6 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_protocol::protocol::TokenUsage;
@@ -719,9 +718,7 @@ async fn run_review_on_session(
.total_token_usage()
.await
.unwrap_or_default();
// The legacy SandboxPolicy should match the PermissionProfile.
let guardian_permission_profile = PermissionProfile::read_only();
let legacy_sandbox_policy = SandboxPolicy::new_read_only_policy();
let submit_result = run_before_review_deadline(
deadline,
@@ -736,7 +733,6 @@ async fn run_review_on_session(
#[allow(deprecated)]
cwd: Some(params.parent_turn.cwd.to_path_buf()),
approval_policy: Some(AskForApproval::Never),
sandbox_policy: Some(legacy_sandbox_policy),
permission_profile: Some(guardian_permission_profile),
summary: Some(params.reasoning_summary),
personality: params.personality,
+10 -8
View File
@@ -716,14 +716,16 @@ async fn augment_mcp_tool_request_meta_with_sandbox_state(
return Ok(meta);
}
let sandbox_state = serde_json::to_value(SandboxState {
permission_profile: Some(turn_context.permission_profile()),
sandbox_policy: turn_context.sandbox_policy(),
codex_linux_sandbox_exe: turn_context.codex_linux_sandbox_exe.clone(),
#[allow(deprecated)]
sandbox_cwd: turn_context.cwd.to_path_buf(),
use_legacy_landlock: turn_context.features.use_legacy_landlock(),
})?;
#[allow(deprecated)]
let sandbox_cwd = turn_context.cwd.to_path_buf();
let sandbox_state = serde_json::to_value(SandboxState::from_permission_profile(
turn_context.permission_profile(),
&turn_context.file_system_sandbox_policy(),
turn_context.network_sandbox_policy(),
turn_context.codex_linux_sandbox_exe.clone(),
sandbox_cwd,
turn_context.features.use_legacy_landlock(),
))?;
match meta.as_mut() {
Some(serde_json::Value::Object(map)) => {
-11
View File
@@ -22,10 +22,8 @@ use codex_protocol::models::PermissionProfile;
pub use codex_protocol::models::SandboxPermissions;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use codex_sandboxing::SandboxExecRequest;
use codex_sandboxing::SandboxType;
use codex_sandboxing::compatibility_sandbox_policy_for_permission_profile;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
@@ -102,15 +100,6 @@ impl ExecRequest {
}
}
pub(crate) fn compatibility_sandbox_policy(&self) -> SandboxPolicy {
compatibility_sandbox_policy_for_permission_profile(
&self.permission_profile,
&self.file_system_sandbox_policy,
self.network_sandbox_policy,
self.windows_sandbox_policy_cwd.as_path(),
)
}
pub(crate) fn from_sandbox_exec_request(
request: SandboxExecRequest,
options: ExecOptions,
+43 -18
View File
@@ -15,6 +15,7 @@ use crate::session::session::Session;
use crate::session::session::SessionSettingsUpdate;
use crate::config::Config;
use crate::path_utils::normalize_for_native_workdir;
use crate::realtime_context::REALTIME_TURN_TOKEN_BUDGET;
use crate::realtime_context::truncate_realtime_text_to_token_budget;
use crate::realtime_conversation::REALTIME_USER_TEXT_PREFIX;
@@ -51,6 +52,7 @@ use codex_protocol::protocol::TurnAbortReason;
use codex_protocol::protocol::WarningEvent;
use codex_protocol::request_permissions::RequestPermissionsResponse;
use codex_protocol::request_user_input::RequestUserInputResponse;
use codex_utils_absolute_path::AbsolutePathBuf;
use crate::context_manager::is_user_turn_boundary;
use codex_protocol::dynamic_tools::DynamicToolResponse;
@@ -121,42 +123,65 @@ async fn thread_settings_update(
sess: &Session,
thread_settings: ThreadSettingsOverrides,
) -> SessionSettingsUpdate {
let (collaboration_mode, permission_profile) = {
let state = sess.state.lock().await;
let session_configuration = &state.session_configuration;
let collaboration_mode = match thread_settings.collaboration_mode.clone() {
Some(collaboration_mode) => collaboration_mode,
None => {
// Model and reasoning effort live in CollaborationMode settings today, so
// partial thread-settings updates refresh those fields on the active mode.
session_configuration.collaboration_mode.with_updates(
thread_settings.model.clone(),
thread_settings.effort,
/*developer_instructions*/ None,
)
}
};
let update_cwd = thread_settings
.cwd
.as_ref()
.map(|cwd| {
AbsolutePathBuf::relative_to_current_dir(normalize_for_native_workdir(
cwd.as_path(),
))
.unwrap_or_else(|err| {
tracing::warn!("failed to normalize update cwd: {cwd:?}: {err}");
session_configuration.cwd.clone()
})
})
.unwrap_or_else(|| session_configuration.cwd.clone());
let current_file_system_sandbox_policy = session_configuration.file_system_sandbox_policy();
let permission_profile = thread_settings.permission_profile_update_for_cwd(
update_cwd.as_path(),
Some(&current_file_system_sandbox_policy),
);
(collaboration_mode, permission_profile)
};
let ThreadSettingsOverrides {
cwd,
workspace_roots,
profile_workspace_roots,
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
sandbox_policy: _,
permission_profile: _,
active_permission_profile,
windows_sandbox_level,
model,
effort,
model: _,
effort: _,
summary,
service_tier,
collaboration_mode,
collaboration_mode: _,
personality,
} = thread_settings;
let collaboration_mode = match collaboration_mode {
Some(collaboration_mode) => collaboration_mode,
None => {
let state = sess.state.lock().await;
// Model and reasoning effort live in CollaborationMode settings today, so
// partial thread-settings updates refresh those fields on the active mode.
state
.session_configuration
.collaboration_mode
.with_updates(model, effort, /*developer_instructions*/ None)
}
};
SessionSettingsUpdate {
cwd,
workspace_roots,
profile_workspace_roots,
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile,
active_permission_profile,
windows_sandbox_level,
-1
View File
@@ -349,7 +349,6 @@ use codex_protocol::protocol::Op;
use codex_protocol::protocol::RateLimitSnapshot;
use codex_protocol::protocol::RequestUserInputEvent;
use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionConfiguredEvent;
use codex_protocol::protocol::SessionNetworkProxyRuntime;
use codex_protocol::protocol::StreamErrorEvent;
@@ -9,9 +9,23 @@ use codex_protocol::protocol::CompactedItem;
use codex_protocol::protocol::InitialHistory;
use codex_protocol::protocol::InterAgentCommunication;
use codex_protocol::protocol::ResumedHistory;
use codex_protocol::protocol::SandboxPolicy;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
fn legacy_sandbox_policy_for_turn_context(turn_context: &TurnContext) -> SandboxPolicy {
let file_system_sandbox_policy = turn_context.file_system_sandbox_policy();
#[allow(deprecated)]
let cwd = turn_context.cwd.as_path();
turn_context
.permission_profile()
.compatibility_sandbox_policy(
&file_system_sandbox_policy,
turn_context.network_sandbox_policy(),
cwd,
)
}
fn user_message(text: &str) -> ResponseItem {
ResponseItem::Message {
id: None,
@@ -65,7 +79,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(),
sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -103,7 +117,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(),
sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -952,7 +966,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(),
sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -1030,7 +1044,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(),
sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -1058,7 +1072,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(),
sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -1176,7 +1190,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(),
sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -1293,7 +1307,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(),
sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -1451,7 +1465,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(),
sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
+24 -37
View File
@@ -13,6 +13,15 @@ use codex_protocol::protocol::ThreadSource;
use codex_protocol::protocol::TurnEnvironmentSelection;
use tokio::sync::Semaphore;
fn sandbox_mode_label(permission_profile: &PermissionProfile) -> &'static str {
match codex_config::sandbox_mode_requirement_for_permission_profile(permission_profile) {
codex_config::SandboxModeRequirement::ReadOnly => "read-only",
codex_config::SandboxModeRequirement::WorkspaceWrite => "workspace-write",
codex_config::SandboxModeRequirement::DangerFullAccess => "danger-full-access",
codex_config::SandboxModeRequirement::ExternalSandbox => "external-sandbox",
}
}
/// Context for an initialized model agent
///
/// A session has at most 1 running task at a time, and can be interrupted by user input.
@@ -147,20 +156,6 @@ impl SessionConfiguration {
.set_legacy_permission_profile(permission_profile)
}
pub(super) fn sandbox_policy(&self) -> SandboxPolicy {
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_state.permission_profile(),
&file_system_sandbox_policy,
self.network_sandbox_policy(),
&self.cwd,
)
})
}
pub(super) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
self.permission_profile().file_system_sandbox_policy()
}
@@ -196,9 +191,20 @@ impl SessionConfiguration {
pub(crate) fn apply(&self, updates: &SessionSettingsUpdate) -> ConstraintResult<Self> {
let mut next_configuration = self.clone();
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 current_sandbox_policy = self
.permission_profile()
.to_legacy_sandbox_policy(&self.cwd)
.unwrap_or_else(|_| {
self.permission_profile_state
.permission_profile()
.compatibility_sandbox_policy(
&current_file_system_sandbox_policy,
current_network_sandbox_policy,
&self.cwd,
)
});
let legacy_file_system_projection =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
&current_sandbox_policy,
@@ -328,23 +334,6 @@ impl SessionConfiguration {
)?;
next_configuration.original_config_do_not_use = Arc::new(config);
}
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
&sandbox_policy,
&next_configuration.cwd,
&current_file_system_sandbox_policy,
);
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
next_configuration
.permission_profile_state
.set_legacy_permission_profile(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&file_system_sandbox_policy,
network_sandbox_policy,
),
)?;
} else if cwd_changed
&& file_system_policy_matches_legacy
&& file_system_policy_has_rebindable_project_root_write
@@ -421,7 +410,6 @@ pub(crate) struct SessionSettingsUpdate {
pub(crate) profile_workspace_roots: Option<Vec<AbsolutePathBuf>>,
pub(crate) approval_policy: Option<AskForApproval>,
pub(crate) approvals_reviewer: Option<ApprovalsReviewer>,
pub(crate) sandbox_policy: Option<SandboxPolicy>,
pub(crate) permission_profile: Option<PermissionProfile>,
pub(crate) active_permission_profile: Option<ActivePermissionProfile>,
pub(crate) windows_sandbox_level: Option<WindowsSandboxLevel>,
@@ -709,7 +697,8 @@ 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()),
sandbox_policy: sandbox_mode_label(&session_configuration.permission_profile())
.to_string(),
};
let rollout_thread_trace = if matches!(
session_configuration.session_source,
@@ -825,9 +814,7 @@ impl Session {
config.model_context_window,
config.model_auto_compact_token_limit,
config.permissions.approval_policy.value(),
config
.permissions
.legacy_sandbox_policy(session_configuration.cwd.as_path()),
sandbox_mode_label(&session_configuration.permission_profile()),
mcp_servers.keys().map(String::as_str).collect(),
);
+34 -15
View File
@@ -68,6 +68,32 @@ use crate::tasks::SessionTask;
use crate::tasks::SessionTaskContext;
use crate::tasks::UserShellCommandMode;
use crate::tasks::execute_user_shell_command;
fn legacy_sandbox_policy_for_turn_context(turn_context: &TurnContext) -> SandboxPolicy {
let file_system_sandbox_policy = turn_context.file_system_sandbox_policy();
#[allow(deprecated)]
let cwd = turn_context.cwd.as_path();
turn_context
.permission_profile()
.compatibility_sandbox_policy(
&file_system_sandbox_policy,
turn_context.network_sandbox_policy(),
cwd,
)
}
fn legacy_sandbox_policy_for_session_configuration(
session_configuration: &SessionConfiguration,
) -> SandboxPolicy {
let file_system_sandbox_policy = session_configuration.file_system_sandbox_policy();
session_configuration
.permission_profile()
.compatibility_sandbox_policy(
&file_system_sandbox_policy,
session_configuration.network_sandbox_policy(),
session_configuration.cwd.as_path(),
)
}
use crate::tools::ToolRouter;
use crate::tools::context::ToolInvocation;
use crate::tools::context::ToolPayload;
@@ -913,7 +939,7 @@ async fn new_turn_refreshes_managed_network_proxy_for_sandbox_change() -> anyhow
.new_turn_with_sub_id(
"sandbox-policy-change".to_string(),
SessionSettingsUpdate {
sandbox_policy: Some(SandboxPolicy::DangerFullAccess),
permission_profile: Some(PermissionProfile::Disabled),
..Default::default()
},
)
@@ -2267,9 +2293,6 @@ async fn record_initial_history_reconstructs_forked_transcript() {
async fn session_configured_reports_permission_profile_for_external_sandbox() -> anyhow::Result<()>
{
let server = start_mock_server().await;
let sandbox_policy = SandboxPolicy::ExternalSandbox {
network_access: codex_protocol::protocol::NetworkAccess::Restricted,
};
let permission_profile = PermissionProfile::External {
network: NetworkSandboxPolicy::Restricted,
};
@@ -2277,11 +2300,8 @@ async fn session_configured_reports_permission_profile_for_external_sandbox() ->
let mut builder = test_codex().with_config(move |config| {
config
.permissions
.set_permission_profile(permission_profile.clone())
.set_permission_profile(permission_profile)
.expect("set permission profile");
config
.set_legacy_sandbox_policy(sandbox_policy)
.expect("set sandbox policy");
});
let test = builder.build(&server).await?;
@@ -2465,7 +2485,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(),
sandbox_policy: legacy_sandbox_policy_for_turn_context(&turn_context),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
@@ -3902,7 +3922,7 @@ async fn session_configuration_apply_permission_profile_accepts_direct_write_roo
file_system_sandbox_policy
);
assert_eq!(
updated.sandbox_policy(),
legacy_sandbox_policy_for_session_configuration(&updated),
SandboxPolicy::WorkspaceWrite {
writable_roots: vec![external_write_path],
network_access: false,
@@ -5582,7 +5602,7 @@ async fn user_turn_updates_approvals_reviewer() {
cwd: Some(config.cwd.to_path_buf()),
approval_policy: Some(config.permissions.approval_policy.value()),
approvals_reviewer: Some(codex_config::types::ApprovalsReviewer::AutoReview),
sandbox_policy: Some(config.legacy_sandbox_policy()),
permission_profile: Some(config.permissions.effective_permission_profile()),
summary: config.model_reasoning_summary,
personality: config.personality,
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
@@ -7596,7 +7616,7 @@ async fn build_initial_context_restates_realtime_start_when_reference_context_is
fn file_system_policy_with_unreadable_glob(turn_context: &TurnContext) -> FileSystemSandboxPolicy {
#[allow(deprecated)]
let mut policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
&turn_context.sandbox_policy(),
&legacy_sandbox_policy_for_turn_context(turn_context),
&turn_context.cwd,
);
#[allow(deprecated)]
@@ -10398,11 +10418,10 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
#[tokio::test]
async fn shell_tool_cancellation_waits_for_runtime_cleanup() -> anyhow::Result<()> {
let session = make_session_with_config(|config| {
let cwd = config.cwd.clone();
config
.permissions
.set_legacy_sandbox_policy(SandboxPolicy::DangerFullAccess, cwd.as_path())
.expect("test setup should allow sandbox policy");
.set_permission_profile(PermissionProfile::Disabled)
.expect("test setup should allow permission profile");
})
.await?;
let turn_context = session.new_default_turn().await;
+1 -1
View File
@@ -1710,7 +1710,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(),
permission_profile = turn_context.permission_profile(),
effort = turn_context.reasoning_effort,
auth_mode = sess.services.auth_manager.auth_mode(),
features = sess.features.enabled_features(),
+18 -16
View File
@@ -10,7 +10,6 @@ use codex_protocol::models::AdditionalPermissionProfile;
use codex_protocol::openai_models::ToolMode;
use codex_protocol::protocol::ThreadSource;
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;
@@ -114,18 +113,6 @@ impl TurnContext {
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,
#[allow(deprecated)]
&self.cwd,
)
}
pub(crate) fn effective_reasoning_effort(&self) -> Option<ReasoningEffortConfig> {
if self.model_info.supports_reasoning_summaries {
self.reasoning_effort
@@ -319,13 +306,20 @@ impl TurnContext {
// the legacy sandbox policy. This keeps turn-context payloads stable
// while both fields exist; once callers consume only the split policy,
// this comparison and the legacy projection should go away.
let file_system_sandbox_policy = self.file_system_sandbox_policy();
let network_sandbox_policy = self.network_sandbox_policy();
let sandbox_policy = self.permission_profile.compatibility_sandbox_policy(
&file_system_sandbox_policy,
network_sandbox_policy,
#[allow(deprecated)]
&self.cwd,
);
let legacy_file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
&self.sandbox_policy(),
&sandbox_policy,
#[allow(deprecated)]
&self.cwd,
);
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)
}
@@ -338,6 +332,14 @@ impl TurnContext {
pub(crate) fn to_turn_context_item(&self) -> TurnContextItem {
let workspace_roots = self.config.effective_workspace_roots();
let file_system_sandbox_policy = self.file_system_sandbox_policy();
let network_sandbox_policy = self.network_sandbox_policy();
let sandbox_policy = self.permission_profile.compatibility_sandbox_policy(
&file_system_sandbox_policy,
network_sandbox_policy,
#[allow(deprecated)]
&self.cwd,
);
TurnContextItem {
turn_id: Some(self.sub_id.clone()),
#[allow(deprecated)]
@@ -346,7 +348,7 @@ impl TurnContext {
current_date: self.current_date.clone(),
timezone: self.timezone.clone(),
approval_policy: self.approval_policy.value(),
sandbox_policy: self.sandbox_policy(),
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(),
+1 -1
View File
@@ -30,7 +30,7 @@ pub enum StdioPolicy {
Inherit,
}
/// Spawns the appropriate child process for the ExecParams and SandboxPolicy,
/// Spawns the appropriate child process for the exec params and sandbox settings,
/// ensuring the args and environment variables used to create the `Command`
/// (and `Child`) honor the configuration.
///
@@ -65,6 +65,38 @@ use tokio::sync::Mutex;
use tokio::time::timeout;
use tokio_util::sync::CancellationToken;
fn legacy_sandbox_policy_for_permissions(
permissions: &crate::config::Permissions,
cwd: &std::path::Path,
) -> SandboxPolicy {
let permission_profile = permissions.effective_permission_profile();
let file_system_policy = permission_profile.file_system_sandbox_policy();
permission_profile.compatibility_sandbox_policy(
&file_system_policy,
permission_profile.network_sandbox_policy(),
cwd,
)
}
fn can_set_legacy_sandbox_policy(
permissions: &crate::config::Permissions,
sandbox_policy: &SandboxPolicy,
cwd: &std::path::Path,
) -> crate::config::ConstraintResult<()> {
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd);
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy),
&file_system_sandbox_policy,
NetworkSandboxPolicy::from(sandbox_policy),
);
permissions.can_set_permission_profile(&permission_profile)
}
fn legacy_sandbox_policy_for_config(config: &crate::config::Config) -> SandboxPolicy {
legacy_sandbox_policy_for_permissions(&config.permissions, config.cwd.as_path())
}
fn invocation(
session: Arc<crate::session::session::Session>,
turn: Arc<TurnContext>,
@@ -2221,7 +2253,7 @@ 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 = turn.config.legacy_sandbox_policy();
let expected_sandbox = legacy_sandbox_policy_for_config(&turn.config);
#[allow(deprecated)]
let mut expected_file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&expected_sandbox, &turn.cwd);
@@ -2279,7 +2311,14 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
.expect("spawned agent thread should exist")
.config_snapshot()
.await;
assert_eq!(snapshot.sandbox_policy(), expected_sandbox);
let snapshot_file_system_sandbox_policy =
snapshot.permission_profile.file_system_sandbox_policy();
let snapshot_sandbox_policy = snapshot.permission_profile.compatibility_sandbox_policy(
&snapshot_file_system_sandbox_policy,
snapshot.permission_profile.network_sandbox_policy(),
snapshot.cwd.as_path(),
);
assert_eq!(snapshot_sandbox_policy, expected_sandbox);
assert_eq!(snapshot.approval_policy, AskForApproval::OnRequest);
assert_eq!(snapshot.permission_profile, expected_permission_profile);
let child_thread = manager
@@ -4188,9 +4227,7 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
if *candidate == base {
return false;
}
permissions
.can_set_legacy_sandbox_policy(candidate, cwd)
.is_ok()
can_set_legacy_sandbox_policy(permissions, candidate, cwd).is_ok()
})
.unwrap_or(base)
}
@@ -4215,7 +4252,7 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
let turn_cwd = turn.cwd.clone();
let sandbox_policy = pick_allowed_sandbox_policy(
&turn.config.permissions,
turn.config.legacy_sandbox_policy(),
legacy_sandbox_policy_for_config(&turn.config),
turn_cwd.as_path(),
);
let file_system_sandbox_policy =
+56
View File
@@ -16,10 +16,17 @@ use codex_core::CodexThread;
use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_core::config::ConfigOverrides;
use codex_core::config::ConstraintResult;
use codex_core::config::Permissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
pub use codex_utils_absolute_path::test_support::PathBufExt;
pub use codex_utils_absolute_path::test_support::PathExt;
use regex_lite::Regex;
use std::path::Path;
use std::path::PathBuf;
pub mod apps_test_server;
@@ -111,6 +118,55 @@ pub fn test_absolute_path(unix_path: &str) -> AbsolutePathBuf {
test_absolute_path_with_windows(unix_path, /*windows_path*/ None)
}
pub fn legacy_sandbox_policy_for_config(config: &Config) -> SandboxPolicy {
legacy_sandbox_policy_for_permission_profile(
&config.permissions.effective_permission_profile(),
&config.permissions.file_system_sandbox_policy(),
config.permissions.network_sandbox_policy(),
config.cwd.as_path(),
)
}
pub fn legacy_sandbox_policy_for_permission_profile(
permission_profile: &PermissionProfile,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
cwd: &Path,
) -> SandboxPolicy {
permission_profile
.to_legacy_sandbox_policy(cwd)
.unwrap_or_else(|_| {
permission_profile.compatibility_sandbox_policy(
file_system_sandbox_policy,
network_sandbox_policy,
cwd,
)
})
}
pub fn set_legacy_sandbox_policy_for_config(
config: &mut Config,
sandbox_policy: SandboxPolicy,
) -> ConstraintResult<()> {
let cwd = config.cwd.clone();
set_legacy_sandbox_policy_for_permissions(
&mut config.permissions,
sandbox_policy,
cwd.as_path(),
)
}
pub fn set_legacy_sandbox_policy_for_permissions(
permissions: &mut Permissions,
sandbox_policy: SandboxPolicy,
cwd: &Path,
) -> ConstraintResult<()> {
permissions.set_permission_profile(PermissionProfile::from_legacy_sandbox_policy_for_cwd(
&sandbox_policy,
cwd,
))
}
pub trait TempDirExt {
fn abs(&self) -> AbsolutePathBuf;
}
+13 -6
View File
@@ -1,6 +1,7 @@
use anyhow::Result;
use codex_features::Feature;
use codex_protocol::config_types::ServiceTier;
use core_test_support::legacy_sandbox_policy_for_config;
use core_test_support::responses::WebSocketConnectionConfig;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
@@ -38,8 +39,11 @@ async fn websocket_test_codex_shell_chain() -> Result<()> {
let mut builder = test_codex().with_windows_cmd_shell();
let test = builder.build_with_websocket_server(&server).await?;
test.submit_turn_with_policy("run the echo command", test.config.legacy_sandbox_policy())
.await?;
test.submit_turn_with_policy(
"run the echo command",
legacy_sandbox_policy_for_config(&test.config),
)
.await?;
let connection = server.single_connection();
assert_eq!(connection.len(), 2);
@@ -82,7 +86,7 @@ async fn websocket_first_turn_uses_startup_prewarm_and_create() -> Result<()> {
let mut builder = test_codex();
let test = builder.build_with_websocket_server(&server).await?;
test.submit_turn_with_policy("hello", test.config.legacy_sandbox_policy())
test.submit_turn_with_policy("hello", legacy_sandbox_policy_for_config(&test.config))
.await?;
assert_eq!(server.handshakes().len(), 1);
@@ -145,7 +149,7 @@ async fn websocket_first_turn_handles_handshake_delay_with_startup_prewarm() ->
let mut builder = test_codex();
let test = builder.build_with_websocket_server(&server).await?;
test.submit_turn_with_policy("hello", test.config.legacy_sandbox_policy())
test.submit_turn_with_policy("hello", legacy_sandbox_policy_for_config(&test.config))
.await?;
assert_eq!(server.handshakes().len(), 1);
@@ -198,8 +202,11 @@ async fn websocket_v2_test_codex_shell_chain() -> Result<()> {
});
let test = builder.build_with_websocket_server(&server).await?;
test.submit_turn_with_policy("run the echo command", test.config.legacy_sandbox_policy())
.await?;
test.submit_turn_with_policy(
"run the echo command",
legacy_sandbox_policy_for_config(&test.config),
)
.await?;
let connection = server.single_connection();
assert_eq!(connection.len(), 3);
+16 -20
View File
@@ -37,6 +37,8 @@ use core_test_support::responses::mount_sse_once;
use core_test_support::responses::mount_sse_once_match;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::set_legacy_sandbox_policy_for_config;
use core_test_support::set_legacy_sandbox_policy_for_permissions;
use core_test_support::skip_if_no_network;
use core_test_support::test_codex::TestCodex;
use core_test_support::test_codex::test_codex;
@@ -1898,8 +1900,7 @@ async fn run_scenario(scenario: &ScenarioSpec) -> Result<()> {
let mut builder = test_codex().with_model(model).with_config(move |config| {
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
config
.set_legacy_sandbox_policy(sandbox_policy.clone())
set_legacy_sandbox_policy_for_config(config, sandbox_policy.clone())
.expect("set sandbox policy");
for feature in features {
config
@@ -2073,8 +2074,7 @@ async fn approving_apply_patch_for_session_skips_future_prompts_for_same_file()
.with_model("gpt-5.4")
.with_config(move |config| {
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
config
.set_legacy_sandbox_policy(sandbox_policy_for_config)
set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config)
.expect("set sandbox policy");
config.approvals_reviewer = ApprovalsReviewer::User;
});
@@ -2184,8 +2184,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.permissions.approval_policy = Constrained::allow_any(approval_policy);
config
.set_legacy_sandbox_policy(sandbox_policy_for_config)
set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config)
.expect("set sandbox policy");
});
let test = builder.build(&server).await?;
@@ -2357,8 +2356,7 @@ async fn spawned_subagent_execpolicy_amendment_propagates_to_parent_session() ->
let sandbox_policy_for_config = sandbox_policy.clone();
let mut builder = test_codex().with_config(move |config| {
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
config
.set_legacy_sandbox_policy(sandbox_policy_for_config)
set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config)
.expect("set sandbox policy");
config
.features
@@ -2809,8 +2807,7 @@ async fn invalid_requested_prefix_rule_falls_back_for_compound_command() -> Resu
let sandbox_policy_for_config = sandbox_policy.clone();
let mut builder = test_codex().with_config(move |config| {
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
config
.set_legacy_sandbox_policy(sandbox_policy_for_config)
set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config)
.expect("set sandbox policy");
});
let test = builder.build(&server).await?;
@@ -2862,8 +2859,7 @@ async fn approving_fallback_rule_for_compound_command_works() -> Result<()> {
let sandbox_policy_for_config = sandbox_policy.clone();
let mut builder = test_codex().with_config(move |config| {
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
config
.set_legacy_sandbox_policy(sandbox_policy_for_config)
set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config)
.expect("set sandbox policy");
});
let test = builder.build(&server).await?;
@@ -3002,8 +2998,7 @@ allow_local_binding = true
.with_cloud_requirements(managed_network_requirements_loader())
.with_config(move |config| {
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
config
.set_legacy_sandbox_policy(sandbox_policy_for_config)
set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config)
.expect("set sandbox policy");
});
let test = builder.build(&server).await?;
@@ -3488,10 +3483,12 @@ allow_local_binding = true
.with_config(move |config| {
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
let cwd = config.cwd.clone();
config
.permissions
.set_legacy_sandbox_policy(SandboxPolicy::DangerFullAccess, cwd.as_path())
.expect("test setup should allow sandbox policy");
set_legacy_sandbox_policy_for_permissions(
&mut config.permissions,
SandboxPolicy::DangerFullAccess,
cwd.as_path(),
)
.expect("test setup should allow sandbox policy");
});
let test = builder.build(&server).await?;
assert!(
@@ -3611,8 +3608,7 @@ async fn compound_command_with_one_safe_command_still_requires_approval() -> Res
let sandbox_policy_for_config = sandbox_policy.clone();
let mut builder = test_codex().with_config(move |config| {
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
config
.set_legacy_sandbox_policy(sandbox_policy_for_config)
set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config)
.expect("set sandbox policy");
});
let test = builder.build(&server).await?;
+3 -2
View File
@@ -50,6 +50,7 @@ use codex_protocol::protocol::SessionSource;
use codex_protocol::user_input::UserInput;
use core_test_support::PathBufExt;
use core_test_support::apps_test_server::AppsTestServer;
use core_test_support::legacy_sandbox_policy_for_config;
use core_test_support::load_default_config_for_test;
use core_test_support::responses::ResponsesRequest;
use core_test_support::responses::ev_assistant_message;
@@ -1800,7 +1801,7 @@ async fn user_turn_collaboration_mode_overrides_model_and_effort() -> anyhow::Re
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
cwd: Some(config.cwd.to_path_buf()),
approval_policy: Some(config.permissions.approval_policy.value()),
sandbox_policy: Some(config.legacy_sandbox_policy()),
sandbox_policy: Some(legacy_sandbox_policy_for_config(&config)),
summary: Some(
config
.model_reasoning_summary
@@ -1923,7 +1924,7 @@ async fn user_turn_explicit_reasoning_summary_overrides_model_catalog_default()
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
cwd: Some(config.cwd.to_path_buf()),
approval_policy: Some(config.permissions.approval_policy.value()),
sandbox_policy: Some(config.legacy_sandbox_policy()),
sandbox_policy: Some(legacy_sandbox_policy_for_config(&config)),
summary: Some(ReasoningSummary::Concise),
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
mode: codex_protocol::config_types::ModeKind::Default,
@@ -7,6 +7,7 @@ use codex_protocol::protocol::COLLABORATION_MODE_OPEN_TAG;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::Op;
use codex_protocol::user_input::UserInput;
use core_test_support::legacy_sandbox_policy_for_config;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_once;
@@ -176,7 +177,7 @@ async fn collaboration_instructions_added_on_user_turn() -> Result<()> {
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
cwd: Some(test.config.cwd.to_path_buf()),
approval_policy: Some(test.config.permissions.approval_policy.value()),
sandbox_policy: Some(test.config.legacy_sandbox_policy()),
sandbox_policy: Some(legacy_sandbox_policy_for_config(&test.config)),
summary: Some(
test.config
.model_reasoning_summary
@@ -227,7 +228,7 @@ async fn collaboration_instructions_omitted_when_disabled() -> Result<()> {
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
cwd: Some(test.config.cwd.to_path_buf()),
approval_policy: Some(test.config.permissions.approval_policy.value()),
sandbox_policy: Some(test.config.legacy_sandbox_policy()),
sandbox_policy: Some(legacy_sandbox_policy_for_config(&test.config)),
summary: Some(
test.config
.model_reasoning_summary
@@ -336,7 +337,7 @@ async fn user_turn_overrides_collaboration_instructions_after_override() -> Resu
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
cwd: Some(test.config.cwd.to_path_buf()),
approval_policy: Some(test.config.permissions.approval_policy.value()),
sandbox_policy: Some(test.config.legacy_sandbox_policy()),
sandbox_policy: Some(legacy_sandbox_policy_for_config(&test.config)),
summary: Some(
test.config
.model_reasoning_summary
+2 -2
View File
@@ -17,6 +17,7 @@ use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::set_legacy_sandbox_policy_for_config;
use core_test_support::skip_if_no_network;
use core_test_support::skip_if_sandbox;
use core_test_support::test_codex::test_codex;
@@ -60,8 +61,7 @@ printf '%s\n' "${@: -1}" >> "${payload_path}""#,
let mut builder = test_codex().with_config(move |config| {
config.notify = Some(vec![notify_script_str]);
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
config
.set_legacy_sandbox_policy(sandbox_policy_for_config)
set_legacy_sandbox_policy_for_config(config, sandbox_policy_for_config)
.expect("set sandbox policy");
});
let test = builder.build(&server).await?;
+3 -2
View File
@@ -17,6 +17,7 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::Op;
use codex_protocol::user_input::UserInput;
use core_test_support::TempDirExt;
use core_test_support::legacy_sandbox_policy_for_config;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_once;
@@ -855,7 +856,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.permissions.approval_policy.value();
let default_sandbox_policy = &config.legacy_sandbox_policy();
let default_sandbox_policy = &legacy_sandbox_policy_for_config(&config);
let default_model = session_configured.model;
let default_effort = config.model_reasoning_effort;
let default_summary = config.model_reasoning_summary;
@@ -996,7 +997,7 @@ async fn send_user_turn_with_changes_sends_environment_context() -> anyhow::Resu
let default_cwd = config.cwd.clone();
let default_approval_policy = config.permissions.approval_policy.value();
let default_sandbox_policy = &config.legacy_sandbox_policy();
let default_sandbox_policy = &legacy_sandbox_policy_for_config(&config);
let default_model = session_configured.model;
let default_effort = config.model_reasoning_effort;
let default_summary = config.model_reasoning_summary;
+2 -1
View File
@@ -15,6 +15,7 @@ use codex_protocol::protocol::TurnStartedEvent;
use codex_protocol::protocol::UserMessageEvent;
use codex_protocol::protocol::WarningEvent;
use core::time::Duration;
use core_test_support::legacy_sandbox_policy_for_config;
use core_test_support::load_default_config_for_test;
use core_test_support::wait_for_event;
use tempfile::TempDir;
@@ -32,7 +33,7 @@ fn resume_history(
current_date: None,
timezone: None,
approval_policy: config.permissions.approval_policy.value(),
sandbox_policy: config.legacy_sandbox_policy(),
sandbox_policy: legacy_sandbox_policy_for_config(config),
permission_profile: None,
network: None,
file_system_sandbox_policy: None,
+33 -10
View File
@@ -17,9 +17,10 @@ use codex_config::Constrained;
use codex_core::config::Config;
use codex_features::Feature;
use codex_protocol::ThreadId;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AgentStatus;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::TokenUsage;
use codex_protocol::user_input::UserInput;
use codex_state::Stage1Output;
@@ -320,18 +321,18 @@ mod agent {
.features
.disable(Feature::SkillMcpDependencyInstall);
// Sandbox policy
let writable_roots = vec![root];
// The consolidation agent only needs local memory-root write access and no network.
let consolidation_sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
};
agent_config.workspace_roots = vec![root.clone()];
agent_config.workspace_roots_explicit = true;
agent_config.permissions.set_workspace_roots(vec![root]);
agent_config
.permissions
.set_legacy_sandbox_policy(consolidation_sandbox_policy, agent_config.cwd.as_path())
.set_permission_profile(PermissionProfile::workspace_write_with(
&[],
NetworkSandboxPolicy::Restricted,
/*exclude_tmpdir_env_var*/ true,
/*exclude_slash_tmp*/ true,
))
.ok()?;
agent_config.model = Some(
@@ -574,3 +575,25 @@ fn emit_token_usage_metrics(context: &MemoryStartupContext, token_usage: &TokenU
&[("token_type", "reasoning_output")],
);
}
#[cfg(test)]
mod tests {
use super::*;
use core_test_support::load_default_config_for_test;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
#[tokio::test]
async fn consolidation_agent_config_uses_memory_root_as_workspace_root() {
let home = TempDir::new().expect("temp dir");
let config = load_default_config_for_test(&home).await;
let root = memory_root(&config.codex_home);
let agent_config = agent::get_config(&config).expect("agent config");
assert_eq!(agent_config.cwd, root);
assert_eq!(agent_config.workspace_roots, vec![root.clone()]);
assert_eq!(agent_config.permissions.workspace_roots(), &[root]);
assert!(agent_config.workspace_roots_explicit);
}
}
@@ -40,7 +40,6 @@ use codex_protocol::models::ResponseItem;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource;
use codex_protocol::user_input::UserInput;
use eventsource_stream::Event as StreamEvent;
@@ -444,7 +443,7 @@ impl SessionTelemetry {
context_window: Option<i64>,
auto_compact_token_limit: Option<i64>,
approval_policy: AskForApproval,
sandbox_policy: SandboxPolicy,
sandbox_policy: impl std::fmt::Display,
mcp_servers: Vec<&str>,
) {
log_and_trace_event!(
+36
View File
@@ -544,6 +544,42 @@ impl PermissionProfile {
}
}
pub fn compatibility_sandbox_policy(
&self,
file_system_policy: &FileSystemSandboxPolicy,
network_policy: NetworkSandboxPolicy,
cwd: &Path,
) -> SandboxPolicy {
self.to_legacy_sandbox_policy(cwd).unwrap_or_else(|_| {
let cwd_abs = AbsolutePathBuf::from_absolute_path(cwd).ok();
let writable_roots = file_system_policy
.get_writable_roots_with_cwd(cwd)
.into_iter()
.map(|root| root.root)
.filter(|root| cwd_abs.as_ref() != Some(root))
.collect();
let tmpdir_writable = std::env::var_os("TMPDIR")
.filter(|tmpdir| !tmpdir.is_empty())
.and_then(|tmpdir| {
AbsolutePathBuf::from_absolute_path(std::path::PathBuf::from(tmpdir)).ok()
})
.is_some_and(|tmpdir| {
file_system_policy.can_write_path_with_cwd(tmpdir.as_path(), cwd)
});
let slash_tmp = Path::new("/tmp");
let slash_tmp_writable = slash_tmp.is_absolute()
&& slash_tmp.is_dir()
&& file_system_policy.can_write_path_with_cwd(slash_tmp, cwd);
SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access: network_policy.is_enabled(),
exclude_tmpdir_env_var: !tmpdir_writable,
exclude_slash_tmp: !slash_tmp_writable,
}
})
}
pub fn to_runtime_permissions(&self) -> (FileSystemSandboxPolicy, NetworkSandboxPolicy) {
(
self.file_system_sandbox_policy(),
+75
View File
@@ -475,6 +475,39 @@ pub struct ThreadSettingsOverrides {
pub personality: Option<Personality>,
}
impl ThreadSettingsOverrides {
pub fn permission_profile_update_for_cwd(
&self,
cwd: &Path,
current_file_system_sandbox_policy: Option<&FileSystemSandboxPolicy>,
) -> Option<PermissionProfile> {
if let Some(permission_profile) = self.permission_profile.clone() {
return Some(permission_profile);
}
self.sandbox_policy.as_ref().map(|sandbox_policy| {
let file_system_sandbox_policy = if let Some(current_file_system_sandbox_policy) =
current_file_system_sandbox_policy
{
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
sandbox_policy,
cwd,
current_file_system_sandbox_policy,
)
} else {
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(sandbox_policy, cwd)
};
let file_system_sandbox_policy =
file_system_sandbox_policy.materialize_project_roots_with_cwd(cwd);
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(sandbox_policy),
&file_system_sandbox_policy,
NetworkSandboxPolicy::from(sandbox_policy),
)
})
}
}
/// Source classification for client-supplied context.
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
@@ -4105,6 +4138,48 @@ mod tests {
assert!(SessionSource::Custom("atlas-dev".to_string()).matches_product_restriction(&[]));
}
#[test]
fn thread_settings_legacy_sandbox_policy_binds_project_roots_to_update_cwd() {
let cwd = TempDir::new().expect("tempdir");
let other_workspace_root = TempDir::new().expect("tempdir");
let sandbox_policy = SandboxPolicy::WorkspaceWrite {
writable_roots: Vec::new(),
network_access: false,
exclude_tmpdir_env_var: true,
exclude_slash_tmp: true,
};
let thread_settings = ThreadSettingsOverrides {
sandbox_policy: Some(sandbox_policy.clone()),
..Default::default()
};
let actual = thread_settings
.permission_profile_update_for_cwd(
cwd.path(),
/*current_file_system_sandbox_policy*/ None,
)
.expect("legacy sandbox policy should produce a permission profile update");
let expected_file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
&sandbox_policy,
cwd.path(),
)
.materialize_project_roots_with_cwd(cwd.path());
let expected = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
&expected_file_system_sandbox_policy,
NetworkSandboxPolicy::from(&sandbox_policy),
);
assert_eq!(actual, expected);
let actual_file_system_sandbox_policy = actual.file_system_sandbox_policy();
assert!(actual_file_system_sandbox_policy.can_write_path_with_cwd(cwd.path(), cwd.path()));
assert!(
!actual_file_system_sandbox_policy
.can_write_path_with_cwd(other_workspace_root.path(), cwd.path())
);
}
fn sandbox_policy_probe_paths(policy: &SandboxPolicy, cwd: &Path) -> Vec<PathBuf> {
let mut paths = vec![cwd.to_path_buf()];
for root in policy.get_writable_roots_with_cwd(cwd) {
+1 -36
View File
@@ -266,42 +266,7 @@ pub fn compatibility_sandbox_policy_for_permission_profile(
network_policy: NetworkSandboxPolicy,
cwd: &Path,
) -> SandboxPolicy {
permissions
.to_legacy_sandbox_policy(cwd)
.unwrap_or_else(|_| {
compatibility_workspace_write_policy(file_system_policy, network_policy, cwd)
})
}
fn compatibility_workspace_write_policy(
file_system_policy: &FileSystemSandboxPolicy,
network_policy: NetworkSandboxPolicy,
cwd: &Path,
) -> SandboxPolicy {
let cwd_abs = AbsolutePathBuf::from_absolute_path(cwd).ok();
let writable_roots = file_system_policy
.get_writable_roots_with_cwd(cwd)
.into_iter()
.map(|root| root.root)
.filter(|root| cwd_abs.as_ref() != Some(root))
.collect();
let tmpdir_writable = std::env::var_os("TMPDIR")
.filter(|tmpdir| !tmpdir.is_empty())
.and_then(|tmpdir| {
AbsolutePathBuf::from_absolute_path(std::path::PathBuf::from(tmpdir)).ok()
})
.is_some_and(|tmpdir| file_system_policy.can_write_path_with_cwd(tmpdir.as_path(), cwd));
let slash_tmp = Path::new("/tmp");
let slash_tmp_writable = slash_tmp.is_absolute()
&& slash_tmp.is_dir()
&& file_system_policy.can_write_path_with_cwd(slash_tmp, cwd);
SandboxPolicy::WorkspaceWrite {
writable_roots,
network_access: network_policy.is_enabled(),
exclude_tmpdir_env_var: !tmpdir_writable,
exclude_slash_tmp: !slash_tmp_writable,
}
permissions.compatibility_sandbox_policy(file_system_policy, network_policy, cwd)
}
#[cfg(target_os = "linux")]
+5 -2
View File
@@ -3015,7 +3015,7 @@ fn agent_picker_item_name_snapshot() {
async fn side_fork_config_is_ephemeral_and_appends_developer_guardrails() {
let app = make_test_app().await;
let original_approval_policy = app.config.permissions.approval_policy.value();
let original_sandbox_policy = app.config.legacy_sandbox_policy();
let original_permission_profile = app.config.permissions.effective_permission_profile();
let fork_config = app.side_fork_config();
@@ -3024,7 +3024,10 @@ async fn side_fork_config_is_ephemeral_and_appends_developer_guardrails() {
fork_config.permissions.approval_policy.value(),
original_approval_policy
);
assert_eq!(fork_config.legacy_sandbox_policy(), original_sandbox_policy);
assert_eq!(
fork_config.permissions.effective_permission_profile(),
original_permission_profile
);
let developer_instructions = fork_config
.developer_instructions
.as_deref()
@@ -1,7 +1,5 @@
use super::*;
use crate::app_event::HistoryLookupResponse;
use codex_app_server_protocol::NetworkAccess;
use codex_app_server_protocol::SandboxPolicy;
use codex_protocol::models::ManagedFileSystemPermissions;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
@@ -373,10 +371,6 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
},
};
let expected_permission_profile = expected_app_server_permission_profile.clone();
let expected_core_sandbox = expected_permission_profile
.to_legacy_sandbox_policy(expected_cwd.as_path())
.expect("permission profile should project to legacy sandbox policy");
let expected_sandbox = SandboxPolicy::from(expected_core_sandbox);
let configured = crate::session_state::ThreadSessionState {
thread_id: ThreadId::new(),
forked_from_id: None,
@@ -406,8 +400,6 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
AskForApproval::from(chat.config_ref().permissions.approval_policy.value()),
AskForApproval::Never
);
let actual_sandbox = SandboxPolicy::from(chat.config_ref().legacy_sandbox_policy());
assert_eq!(&actual_sandbox, &expected_sandbox);
assert_eq!(
chat.config_ref().permissions.effective_permission_profile(),
expected_app_server_permission_profile
@@ -494,9 +486,6 @@ async fn session_configured_external_sandbox_keeps_external_runtime_policy() {
network: NetworkSandboxPolicy::Restricted,
};
let expected_permission_profile = expected_app_server_permission_profile.clone();
let expected_sandbox = SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Restricted,
};
let configured = crate::session_state::ThreadSessionState {
thread_id: ThreadId::new(),
forked_from_id: None,
@@ -522,8 +511,6 @@ async fn session_configured_external_sandbox_keeps_external_runtime_policy() {
chat.handle_thread_session(configured);
let actual_sandbox = SandboxPolicy::from(chat.config_ref().legacy_sandbox_policy());
assert_eq!(&actual_sandbox, &expected_sandbox);
assert_eq!(
chat.config_ref().permissions.effective_permission_profile(),
expected_app_server_permission_profile
@@ -1,7 +1,7 @@
use codex_core::config::Config;
use codex_model_provider_info::WireApi;
use crate::sandbox_summary::summarize_sandbox_policy;
use crate::sandbox_summary::summarize_permission_profile;
/// Build a list of key/value pairs summarizing the effective configuration.
pub fn create_config_summary_entries(config: &Config, model: &str) -> Vec<(&'static str, String)> {
@@ -15,10 +15,10 @@ pub fn create_config_summary_entries(config: &Config, model: &str) -> Vec<(&'sta
),
(
"sandbox",
summarize_sandbox_policy(
&config
.permissions
.legacy_sandbox_policy(config.cwd.as_path()),
summarize_permission_profile(
&config.permissions.effective_permission_profile(),
&config.cwd,
&config.effective_workspace_roots(),
),
),
];