mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
core: remove SandboxPolicy from production core
This commit is contained in:
@@ -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(),
|
||||
¤t_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,
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(¤t_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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
¤t_file_system_sandbox_policy,
|
||||
current_network_sandbox_policy,
|
||||
&self.cwd,
|
||||
)
|
||||
});
|
||||
let legacy_file_system_projection =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
¤t_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,
|
||||
¤t_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(),
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user