Derive sandbox contexts from turn environments (#40551)

## What changed

- Add Windows sandbox mode, private-desktop behavior, and legacy Landlock selection to each `EnvironmentConfig`.
- Centralize sandbox context construction on `TurnEnvironment`, preserving environment-owned permissions, paths, and backend settings while applying additional permission grants.
- Use the environment-derived context for project instructions, capability discovery, file uploads, and filesystem-oriented tools.

## Testing

- Verify capability discovery uses the selected environment's permission profile and sandbox backends when they differ from the thread defaults.

GitOrigin-RevId: 7ae2fed90c8ada950f2e8431e571e9b2c793c291
This commit is contained in:
Adam Perry @ OpenAI
2026-08-25 05:16:45 +00:00
committed by copyberry
parent 348f49b292
commit 8444cf63b5
47 changed files with 341 additions and 151 deletions
+2
View File
@@ -147,6 +147,8 @@ pub struct McpConfig {
/// Optional path to `codex-linux-sandbox` for sandboxed MCP tool execution.
pub codex_linux_sandbox_exe: Option<PathBuf>,
/// Whether to use legacy Landlock behavior in the MCP sandbox state.
// TODO(anp): Reconcile this runtime-wide copy with TurnEnvironment::sandbox_context
// for the environment that owns each MCP server.
pub use_legacy_landlock: bool,
/// Whether the app MCP integration is enabled by config.
///
+1 -17
View File
@@ -18,7 +18,6 @@
use crate::config::Config;
use crate::context::UserInstructions as ContextUserInstructions;
use crate::environment_selection::TurnEnvironmentSnapshot;
use crate::tools::sandboxing::executor_windows_sandbox_level;
use codex_config::ConfigLayerSource;
use codex_config::default_project_root_markers;
use codex_config::merge_toml_values;
@@ -30,7 +29,6 @@ use codex_extension_api::UserInstructions;
use codex_file_system::FileSystemSandboxContext;
use codex_file_system::FindUpErrorPolicy;
use codex_file_system::find_nearest_ancestor_with_markers;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use futures::StreamExt;
@@ -58,7 +56,6 @@ pub(crate) async fn load_project_instructions(
config: &Config,
user_instructions: Option<UserInstructions>,
environments: &TurnEnvironmentSnapshot,
windows_sandbox_level: WindowsSandboxLevel,
) -> io::Result<Option<LoadedAgentsMd>> {
let mut loaded = LoadedAgentsMd::from_user_instructions(user_instructions);
if config.active_project.is_untrusted() {
@@ -76,20 +73,7 @@ pub(crate) async fn load_project_instructions(
.permission_profile()
.file_system_sandbox_policy()
.has_full_disk_read_access())
.then(|| {
// TODO(anp): Move sandbox context construction to a method on TurnEnvironment.
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
turn_environment.permission_profile().clone(),
turn_environment.cwd().clone(),
);
sandbox.workspace_roots = turn_environment.workspace_roots().to_vec();
sandbox.windows_sandbox_level =
executor_windows_sandbox_level(windows_sandbox_level, turn_environment.cwd());
sandbox.windows_sandbox_private_desktop =
config.permissions.windows_sandbox_private_desktop;
sandbox.use_legacy_landlock = config.features.use_legacy_landlock();
sandbox
});
.then(|| turn_environment.sandbox_context(/*additional_permissions*/ None));
match read_agents_md(
config,
filesystem.as_ref(),
+4 -14
View File
@@ -4,7 +4,6 @@ use crate::config::Config;
use crate::environment_selection::TurnEnvironmentSnapshot;
use codex_extension_api::UserInstructions;
use codex_protocol::config_types::TrustLevel;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::protocol::TurnEnvironmentSelection;
use std::io;
use std::sync::Arc;
@@ -20,7 +19,6 @@ pub(crate) struct AgentsMdManager {
struct AgentsMdCache {
selections: Option<Vec<TurnEnvironmentSelection>>,
active_project_trust_level: Option<TrustLevel>,
windows_sandbox_level: Option<WindowsSandboxLevel>,
loaded: Option<Arc<LoadedAgentsMd>>,
}
@@ -38,7 +36,6 @@ impl AgentsMdManager {
&self,
config: &Config,
environments: &TurnEnvironmentSnapshot,
windows_sandbox_level: WindowsSandboxLevel,
) -> io::Result<()> {
let selections = environments
.turn_environments()
@@ -49,28 +46,21 @@ impl AgentsMdManager {
let mut cache = self.cache.lock().await;
if cache.selections.as_ref() == Some(&selections)
&& cache.active_project_trust_level == active_project_trust_level
&& cache.windows_sandbox_level == Some(windows_sandbox_level)
{
return Ok(());
}
cache.selections = None;
cache.active_project_trust_level = None;
cache.windows_sandbox_level = None;
cache.loaded = None;
}
let loaded = load_project_instructions(
config,
self.user_instructions.clone(),
environments,
windows_sandbox_level,
)
.await?
.map(Arc::new);
let loaded =
load_project_instructions(config, self.user_instructions.clone(), environments)
.await?
.map(Arc::new);
let mut cache = self.cache.lock().await;
cache.selections = Some(selections);
cache.active_project_trust_level = active_project_trust_level;
cache.windows_sandbox_level = Some(windows_sandbox_level);
cache.loaded = loaded;
Ok(())
}
+19 -39
View File
@@ -339,7 +339,6 @@ async fn load_agents_md(config: &TestConfig) -> Option<LoadedAgentsMd> {
&config.config,
config.user_instructions.clone(),
&environments,
WindowsSandboxLevel::Disabled,
)
.await
.expect("project instructions should load")
@@ -369,6 +368,9 @@ fn resolved_local_environments<const N: usize>(
workspace_roots: Vec::new(),
config: EnvironmentConfigState::Ready(EnvironmentConfig {
allow_login_shell: true,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::legacy(
PermissionProfile::read_only(),
),
@@ -1133,15 +1135,10 @@ async fn multiple_environment_docs_use_labeled_layout_and_preserve_source_order(
]);
let user_instructions = config.user_instructions.clone();
let loaded = load_project_instructions(
&config.config,
user_instructions,
&environments,
WindowsSandboxLevel::Disabled,
)
.await
.expect("project instructions should load")
.expect("instructions expected");
let loaded = load_project_instructions(&config.config, user_instructions, &environments)
.await
.expect("project instructions should load")
.expect("instructions expected");
let inner = format!(
r#"global instructions
@@ -1200,15 +1197,10 @@ async fn secondary_only_project_doc_uses_single_contributor_layout() {
]);
let user_instructions = config.user_instructions.clone();
let loaded = load_project_instructions(
&config.config,
user_instructions,
&environments,
WindowsSandboxLevel::Disabled,
)
.await
.expect("project instructions should load")
.expect("instructions expected");
let loaded = load_project_instructions(&config.config, user_instructions, &environments)
.await
.expect("project instructions should load")
.expect("instructions expected");
let inner = format!("global instructions{AGENTS_MD_SEPARATOR}secondary doc");
assert_eq!(loaded.legacy_text(), inner);
@@ -1235,15 +1227,10 @@ async fn primary_only_project_doc_preserves_legacy_layout_with_multiple_bound_en
]);
let user_instructions = config.user_instructions.clone();
let loaded = load_project_instructions(
&config.config,
user_instructions,
&environments,
WindowsSandboxLevel::Disabled,
)
.await
.expect("project instructions should load")
.expect("instructions expected");
let loaded = load_project_instructions(&config.config, user_instructions, &environments)
.await
.expect("project instructions should load")
.expect("instructions expected");
let inner = format!("global instructions{AGENTS_MD_SEPARATOR}primary doc");
assert_eq!(loaded.legacy_text(), inner);
@@ -1271,15 +1258,10 @@ async fn project_doc_byte_limit_is_shared_across_environments() {
]);
let user_instructions = config.user_instructions.clone();
let loaded = load_project_instructions(
&config.config,
user_instructions,
&environments,
WindowsSandboxLevel::Disabled,
)
.await
.expect("project instructions should load")
.expect("instructions expected");
let loaded = load_project_instructions(&config.config, user_instructions, &environments)
.await
.expect("project instructions should load")
.expect("instructions expected");
assert_eq!(
loaded.text(),
@@ -1310,7 +1292,6 @@ async fn full_primary_environment_budget_excludes_later_environment_docs() {
&config.config,
/*user_instructions*/ None,
&environments,
WindowsSandboxLevel::Disabled,
)
.await
.expect("project instructions should load")
@@ -1343,7 +1324,6 @@ async fn secondary_environment_invalid_utf8_does_not_suppress_other_docs() {
&config.config,
/*user_instructions*/ None,
&environments,
WindowsSandboxLevel::Disabled,
)
.await
.expect("project instructions should load")
+2
View File
@@ -25,6 +25,8 @@ pub(crate) fn prepare_apply_patch(
file_system_sandbox_policy: &FileSystemSandboxPolicy,
action: ApplyPatchAction,
) -> Result<ApplyPatchRuntimeInvocation, FunctionCallError> {
// TODO(anp): Reconcile patch approval with TurnEnvironment::sandbox_context
// so the Windows backend matches the environment that will apply the patch.
match assess_patch_safety(
&action,
turn_context.approval_policy(),
@@ -878,6 +878,7 @@ mod tests {
use codex_exec_server_test_support::environment_manager_without_environments;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::ActivePermissionProfile;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::TurnEnvironmentSelection;
@@ -902,6 +903,9 @@ mod tests {
fn test_environment_config() -> EnvironmentConfig {
EnvironmentConfig {
allow_login_shell: true,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
shell_environment_policy: Default::default(),
exec_policy: None,
@@ -1073,6 +1077,9 @@ url = "ws://127.0.0.1:8765"
};
let expected_config = EnvironmentConfig {
allow_login_shell: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::active_with_profile_workspace_roots(
PermissionProfile::read_only(),
ActivePermissionProfile::read_only(),
@@ -1309,6 +1316,9 @@ url = "ws://127.0.0.1:8765"
let cwd = AbsolutePathBuf::current_dir().expect("cwd");
let expected_config = EnvironmentConfig {
allow_login_shell: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::active_with_profile_workspace_roots(
PermissionProfile::read_only(),
ActivePermissionProfile::read_only(),
@@ -1646,6 +1656,9 @@ url = "ws://127.0.0.1:8765"
.expect("replacement environment");
let child_config = EnvironmentConfig {
allow_login_shell: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::active_with_profile_workspace_roots(
PermissionProfile::read_only(),
ActivePermissionProfile::read_only(),
@@ -1697,6 +1710,9 @@ url = "ws://127.0.0.1:8765"
resolve_turn_environments(Arc::clone(&manager), std::slice::from_ref(&selection)).await;
let parent_owner_config = EnvironmentConfig {
allow_login_shell: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
shell_environment_policy: Default::default(),
exec_policy: None,
+2
View File
@@ -101,6 +101,8 @@ pub struct ExecParams {
pub network: Option<NetworkProxy>,
pub network_environment_id: Option<String>,
pub sandbox_permissions: SandboxPermissions,
// TODO(anp): Reconcile these launch settings with TurnEnvironment::sandbox_context
// so turn-scoped execution uses the selected environment's backend.
pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
pub windows_sandbox_private_desktop: bool,
pub justification: Option<String>,
+4
View File
@@ -167,6 +167,8 @@ pub(crate) enum ExecPolicyCommandOrigin {
pub(crate) struct UnmatchedCommandContext<'a> {
pub(crate) approval_policy: AskForApproval,
pub(crate) permission_profile: &'a PermissionProfile,
// TODO(anp): Reconcile this decision input with TurnEnvironment::sandbox_context
// so approval heuristics match the selected environment's Windows backend.
pub(crate) windows_sandbox_level: WindowsSandboxLevel,
pub(crate) sandbox_permissions: SandboxPermissions,
pub(crate) command_origin: ExecPolicyCommandOrigin,
@@ -281,6 +283,8 @@ pub(crate) struct ExecApprovalRequest<'a> {
pub(crate) approval_policy: AskForApproval,
pub(crate) permission_profile: PermissionProfile,
pub(crate) environment_policy: Option<&'a RequirementsExecPolicy>,
// TODO(anp): Reconcile this approval snapshot with TurnEnvironment::sandbox_context
// rather than taking the Windows backend from the turn-wide default.
pub(crate) windows_sandbox_level: WindowsSandboxLevel,
pub(crate) sandbox_permissions: SandboxPermissions,
pub(crate) prefix_rule: Option<Vec<String>>,
+2 -3
View File
@@ -180,9 +180,8 @@ async fn build_uploaded_argument_value(
.entries
.iter()
.any(|entry| entry.access == FileSystemAccessMode::Deny);
let sandbox = requires_sandbox.then(|| {
turn_context.file_system_sandbox_context(additional_permissions, turn_environment)
});
let sandbox =
requires_sandbox.then(|| turn_environment.sandbox_context(additional_permissions));
if sandbox.is_some() {
let environment_info = turn_environment
.environment
+2
View File
@@ -773,6 +773,8 @@ async fn augment_mcp_tool_request_meta_with_sandbox_state(
else {
return Ok(meta);
};
// TODO(anp): Build this metadata from the server's captured
// TurnEnvironment::sandbox_context instead of the runtime-wide Landlock value.
let permission_profile = prepared_call.config().permission_profile.clone();
let sandbox_state = serde_json::to_value(SandboxState {
permission_profile,
+6
View File
@@ -1113,6 +1113,12 @@ async fn mcp_sandbox_cwd_uses_matching_server_environment_uri() -> anyhow::Resul
workspace_roots: Vec::new(),
config: EnvironmentConfigState::Ready(EnvironmentConfig {
allow_login_shell: true,
windows_sandbox_level: turn_context.windows_sandbox_level,
windows_sandbox_private_desktop: turn_context
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: turn_context.config.features.use_legacy_landlock(),
permission_profile: turn_context
.config
.permissions
+2
View File
@@ -60,6 +60,8 @@ pub struct ExecRequest {
pub sandbox: SandboxType,
pub windows_sandbox_policy_cwd: PathUri,
pub windows_sandbox_workspace_roots: Vec<AbsolutePathBuf>,
// TODO(anp): Reconcile these backend copies with TurnEnvironment::sandbox_context
// and exec_server_sandbox so local and remote launches use the same settings.
pub windows_sandbox_level: WindowsSandboxLevel,
pub windows_sandbox_private_desktop: bool,
pub permission_profile: PermissionProfile,
+5 -20
View File
@@ -1,10 +1,8 @@
use super::mcp_refresh::McpRefreshInvalidationGuard;
use super::*;
use crate::environment_selection::combine_selected_capability_roots;
use crate::tools::sandboxing::executor_windows_sandbox_level;
use codex_exec_server::ExecutorCapabilityDiscoveryCache;
use codex_exec_server::ExecutorCapabilityDiscoverySnapshot;
use codex_exec_server::FileSystemSandboxContext;
use codex_exec_server::MAX_SELECTED_CAPABILITY_ROOTS;
use codex_exec_server::ResolvedSelectedCapabilityRoot;
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
@@ -98,10 +96,9 @@ impl Session {
config: &Config,
) -> (McpConfig, McpRuntimeContext) {
let originator = self.originator().await;
let (windows_sandbox_level, session_source, host_fallback_cwd) = {
let (session_source, host_fallback_cwd) = {
let state = self.state.lock().await;
(
state.session_configuration.windows_sandbox_level,
state.session_configuration.session_source.clone(),
state.session_configuration.cwd().clone(),
)
@@ -117,7 +114,6 @@ impl Session {
config,
&ready_selected_capability_roots,
&environments,
windows_sandbox_level,
)
.await;
let mcp_projection = self
@@ -210,7 +206,6 @@ impl Session {
&desired.config,
&ready_selected_capability_roots,
&desired.environments,
desired.windows_sandbox_level,
)
.await;
let mcp_projection = self
@@ -265,7 +260,6 @@ impl Session {
&desired.config,
&ready_selected_capability_roots,
&desired.environments,
desired.windows_sandbox_level,
)
.await;
let mcp_projection = self
@@ -373,7 +367,6 @@ impl Session {
config: &Config,
ready_selected_capability_roots: &[SelectedCapabilityRoot],
environments: &TurnEnvironmentSnapshot,
windows_sandbox_level: WindowsSandboxLevel,
) -> Option<Arc<ExecutorCapabilityDiscoverySnapshot>> {
// Capability roots can currently be selected independently of turn environments, so a
// root may be ready when there is no primary `TurnEnvironment`. Keep using the thread
@@ -408,17 +401,10 @@ impl Session {
environments
.turn_environments()
.map(|environment| {
let mut sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
environment.permission_profile().clone(),
environment.cwd().clone(),
);
sandbox.workspace_roots = environment.workspace_roots().to_vec();
sandbox.windows_sandbox_level =
executor_windows_sandbox_level(windows_sandbox_level, environment.cwd());
sandbox.windows_sandbox_private_desktop =
config.permissions.windows_sandbox_private_desktop;
sandbox.use_legacy_landlock = config.features.use_legacy_landlock();
(environment.selection.environment_id.clone(), sandbox)
(
environment.selection.environment_id.clone(),
environment.sandbox_context(/*additional_permissions*/ None),
)
})
.collect::<HashMap<_, _>>()
} else {
@@ -665,7 +651,6 @@ impl Session {
refresh_config,
&ready_selected_capability_roots,
&environments,
turn_context.windows_sandbox_level,
)
.await;
let mcp_projection = self
+3 -3
View File
@@ -28,7 +28,6 @@ pub(super) struct McpDesiredState {
pub(super) session_source: SessionSource,
pub(super) environments: TurnEnvironmentSnapshot,
pub(super) local_process_cwd: PathBuf,
pub(super) windows_sandbox_level: WindowsSandboxLevel,
}
impl Session {
@@ -38,6 +37,9 @@ impl Session {
next: &SessionConfiguration,
updates: &SessionSettingsUpdate,
) -> bool {
// TODO(anp): Reconcile invalidation with TurnEnvironment::sandbox_context.
// This gap predates that API: an internal Windows-level-only settings update
// can leave the published MCP configuration stale.
current.cwd() != next.cwd()
|| current.approval_policy.value() != next.approval_policy.value()
|| current.approvals_reviewer != next.approvals_reviewer
@@ -97,7 +99,6 @@ impl Session {
session_source: session_configuration.session_source.clone(),
environments,
local_process_cwd,
windows_sandbox_level: session_configuration.windows_sandbox_level,
}
}
@@ -124,7 +125,6 @@ impl Session {
session_source: session_configuration.session_source.clone(),
environments: resolved_environments.clone(),
local_process_cwd,
windows_sandbox_level: session_configuration.windows_sandbox_level,
};
self.publish_mcp_runtime(
&desired,
+4 -10
View File
@@ -716,6 +716,8 @@ impl Session {
allow_login_shell: config.permissions.allow_login_shell,
shell_environment_policy: config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: config.features.use_legacy_landlock(),
legacy_fallback_cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
@@ -3254,11 +3256,7 @@ impl Session {
let environments = turn_context.environments.refresh_readiness();
self.services
.agents_md_manager
.refresh(
&turn_context.config,
&environments,
turn_context.windows_sandbox_level,
)
.refresh(&turn_context.config, &environments)
.await?;
let loaded_agents_md = self.services.agents_md_manager.get_loaded().await;
let selected_capability_roots = self
@@ -3271,7 +3269,6 @@ impl Session {
&turn_context.config,
&ready_selected_capability_roots,
&environments,
turn_context.windows_sandbox_level,
)
.or_cancel(cancellation_token)
.await?;
@@ -3292,10 +3289,7 @@ impl Session {
.map(|environment| {
(
environment.selection.environment_id.clone(),
turn_context.file_system_sandbox_context(
/*additional_permissions*/ None,
environment,
),
environment.sandbox_context(/*additional_permissions*/ None),
)
})
.collect::<HashMap<_, _>>();
+11 -5
View File
@@ -97,7 +97,11 @@ pub(crate) struct SessionConfiguration {
pub(super) permission_profile_state: PermissionProfileState,
pub(super) allow_login_shell: bool,
pub(super) shell_environment_policy: ShellEnvironmentPolicy,
// TODO(anp): Reconcile these legacy thread defaults with TurnEnvironment::sandbox_context;
// internal sandbox decisions should use the selected environment's configuration.
pub(super) windows_sandbox_level: WindowsSandboxLevel,
pub(super) windows_sandbox_private_desktop: bool,
pub(super) use_legacy_landlock: bool,
/// Legacy thread cwd used when a turn does not select an environment.
pub(super) legacy_fallback_cwd: AbsolutePathBuf,
@@ -144,6 +148,9 @@ impl SessionConfiguration {
allow_login_shell: self.allow_login_shell,
permission_profile: self.permission_profile_state.snapshot(),
shell_environment_policy: self.shell_environment_policy.clone(),
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
use_legacy_landlock: self.use_legacy_landlock,
exec_policy: None,
mcp_policy: None,
network_policy: None,
@@ -293,6 +300,9 @@ impl SessionConfiguration {
service_tier: Some(self.service_tier.clone()),
collaboration_mode: Some(self.collaboration_mode.clone()),
personality: self.personality,
// TODO(anp): Include the runtime Windows sandbox override in restored settings.
// This omission predates TurnEnvironment::sandbox_context; rebuilding FromThread
// environments falls back to Config instead of retaining that override.
..Default::default()
}
}
@@ -1219,11 +1229,7 @@ impl Session {
otel.name = "session_init.thread_name_lookup",
));
let (agents_md_result, plugin_skill_errors, thread_name) = tokio::join!(
agents_md_manager.refresh(
config.as_ref(),
&resolved_environments,
session_configuration.windows_sandbox_level,
),
agents_md_manager.refresh(config.as_ref(), &resolved_environments),
plugin_skill_warmup,
thread_name_lookup,
);
+43 -5
View File
@@ -32,9 +32,11 @@ use codex_config::loader::project_trust_key;
use codex_config::types::McpServerConfig;
use codex_config::types::McpServerTransportConfig;
use codex_config::types::ToolSuggestDisabledTool;
use codex_config::types::WindowsSandboxModeToml;
use core_test_support::test_codex::TurnInputRequest as ExternalTurnInputRequest;
use codex_features::Feature;
use codex_file_system::FileSystemSandboxContext;
use codex_http_client::ClientRouteClass;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
@@ -4340,6 +4342,8 @@ async fn set_rate_limits_retains_previous_credits() {
allow_login_shell: config.permissions.allow_login_shell,
shell_environment_policy: config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: config.features.use_legacy_landlock(),
legacy_fallback_cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
@@ -4451,6 +4455,8 @@ async fn set_rate_limits_updates_plan_type_when_present() {
allow_login_shell: config.permissions.allow_login_shell,
shell_environment_policy: config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: config.features.use_legacy_landlock(),
legacy_fallback_cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
@@ -5007,6 +5013,8 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
allow_login_shell: config.permissions.allow_login_shell,
shell_environment_policy: config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: config.features.use_legacy_landlock(),
legacy_fallback_cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
@@ -5794,6 +5802,8 @@ async fn session_new_fails_when_zsh_fork_enabled_without_packaged_zsh() {
allow_login_shell: config.permissions.allow_login_shell,
shell_environment_policy: config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: config.features.use_legacy_landlock(),
legacy_fallback_cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
@@ -5942,6 +5952,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
allow_login_shell: config.permissions.allow_login_shell,
shell_environment_policy: config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: config.features.use_legacy_landlock(),
legacy_fallback_cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
@@ -6230,6 +6242,8 @@ async fn make_session_with_config_and_rx(
allow_login_shell: config.permissions.allow_login_shell,
shell_environment_policy: config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: config.features.use_legacy_landlock(),
legacy_fallback_cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
@@ -6351,6 +6365,8 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
allow_login_shell: config.permissions.allow_login_shell,
shell_environment_policy: config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: config.features.use_legacy_landlock(),
legacy_fallback_cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
@@ -8153,6 +8169,8 @@ where
allow_login_shell: config.permissions.allow_login_shell,
shell_environment_policy: config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: config.features.use_legacy_landlock(),
legacy_fallback_cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
thread_name: None,
@@ -8844,13 +8862,23 @@ async fn conflicting_ready_environment_root_ids_keep_first_location() {
});
}
/// Capability discovery must use environment-owned permissions and sandbox backends,
/// even when the thread defaults differ.
#[tokio::test]
async fn capability_discovery_uses_environment_permission_profile() {
let (session, mut turn_context) = make_session_and_context().await;
Arc::make_mut(&mut turn_context.config)
let config = Arc::make_mut(&mut turn_context.config);
config
.permissions
.set_permission_profile(PermissionProfile::Disabled)
.expect("unrestricted permission profile should be allowed");
config.permissions.windows_sandbox_mode = Some(WindowsSandboxModeToml::Unelevated);
config.permissions.windows_sandbox_private_desktop = true;
config
.features
.disable(Feature::UseLegacyLandlock)
.expect("disable legacy Landlock");
turn_context.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken;
let mut environment = turn_context
.environments
.primary()
@@ -8864,13 +8892,24 @@ async fn capability_discovery_uses_environment_permission_profile() {
access: FileSystemAccessMode::Deny,
missing_path_behavior: None,
});
environment.config_mut().permission_profile =
let environment_config = environment.config_mut();
environment_config.permission_profile =
PermissionProfileSnapshot::legacy(PermissionProfile::from_runtime_permissions(
&file_system_policy,
NetworkSandboxPolicy::Restricted,
));
let expected_sandbox = turn_context
.file_system_sandbox_context(/*additional_permissions*/ None, &environment);
environment_config.windows_sandbox_level = WindowsSandboxLevel::Elevated;
environment_config.windows_sandbox_private_desktop = false;
environment_config.use_legacy_landlock = true;
let expected_sandbox = FileSystemSandboxContext {
permissions: environment.permission_profile().clone().into(),
cwd: Some(environment.cwd().clone()),
workspace_roots: environment.workspace_roots().to_vec(),
windows_sandbox_level: WindowsSandboxLevel::Elevated,
windows_sandbox_private_desktop: false,
windows_sandbox_proxy_settings_mode: None,
use_legacy_landlock: true,
};
let environment_id = environment.selection.environment_id.clone();
turn_context.environments.environments[0] = TurnEnvironmentState::Ready(environment);
@@ -8879,7 +8918,6 @@ async fn capability_discovery_uses_environment_permission_profile() {
&turn_context.config,
/*ready_selected_capability_roots*/ &[],
&turn_context.environments,
turn_context.windows_sandbox_level,
)
.await
.expect("restricted environment should trigger capability discovery");
+28 -26
View File
@@ -104,6 +104,32 @@ impl TurnEnvironment {
self.config().permission_profile.permission_profile()
}
/// Sandbox context for this environment, including any additional permission grants.
pub(crate) fn sandbox_context(
&self,
additional_permissions: Option<AdditionalPermissionProfile>,
) -> FileSystemSandboxContext {
let config = self.config();
// Grant-adjusted permissions take precedence over the environment's baseline;
// paths and sandbox backend settings remain environment-owned.
let permissions = effective_permission_profile(
self.permission_profile(),
additional_permissions.as_ref(),
);
FileSystemSandboxContext {
permissions: permissions.into(),
cwd: Some(self.cwd().clone()),
workspace_roots: self.workspace_roots().to_vec(),
windows_sandbox_level: executor_windows_sandbox_level(
config.windows_sandbox_level,
self.cwd(),
),
windows_sandbox_private_desktop: config.windows_sandbox_private_desktop,
windows_sandbox_proxy_settings_mode: None,
use_legacy_landlock: config.use_legacy_landlock,
}
}
pub(crate) fn active_permission_profile(&self) -> Option<ActivePermissionProfile> {
self.config().permission_profile.active_permission_profile()
}
@@ -178,6 +204,8 @@ pub struct TurnContext {
pub(crate) multi_agent_version: MultiAgentVersion,
pub(crate) personality: Option<Personality>,
pub(crate) network: Option<NetworkProxy>,
// TODO(anp): Reconcile this parallel turn snapshot with TurnEnvironment::sandbox_context
// so owner-provided environment settings govern the remaining sandbox decisions.
pub(crate) windows_sandbox_level: WindowsSandboxLevel,
pub(crate) available_models: Vec<ModelPreset>,
pub(crate) unified_exec_shell_mode: UnifiedExecShellMode,
@@ -449,32 +477,6 @@ impl TurnContext {
}
}
pub(crate) fn file_system_sandbox_context(
&self,
additional_permissions: Option<AdditionalPermissionProfile>,
environment: &TurnEnvironment,
) -> FileSystemSandboxContext {
let permissions = effective_permission_profile(
environment.permission_profile(),
additional_permissions.as_ref(),
);
FileSystemSandboxContext {
permissions: permissions.into(),
cwd: Some(environment.cwd().clone()),
workspace_roots: environment.workspace_roots().to_vec(),
windows_sandbox_level: executor_windows_sandbox_level(
self.windows_sandbox_level,
environment.cwd(),
),
windows_sandbox_private_desktop: self
.config
.permissions
.windows_sandbox_private_desktop,
windows_sandbox_proxy_settings_mode: None,
use_legacy_landlock: self.config.features.use_legacy_landlock(),
}
}
fn non_legacy_file_system_sandbox_policy(&self) -> Option<RawFileSystemSandboxPolicy> {
// Omit the derived split filesystem policy when it is equivalent to
// the legacy sandbox policy. This keeps turn-context payloads stable
@@ -11,6 +11,7 @@ use crate::session::tests::build_world_state_from_turn_context;
use crate::session::tests::make_session_and_context;
use crate::tasks::InterruptedTurnHistoryMarker;
use crate::tasks::interrupted_turn_history_marker;
use crate::windows_sandbox::WindowsSandboxLevelExt;
use codex_extension_api::empty_extension_registry;
use codex_history::InitialHistory;
use codex_history::ResumedHistory;
@@ -18,6 +19,7 @@ use codex_models_manager::manager::RefreshStrategy;
use codex_protocol::ResponseItemId;
use codex_protocol::capabilities::CapabilityRootLocation;
use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::mcp::ClientMcpExtensions;
use codex_protocol::mcp::MCP_APP_UI_EXTENSION_ID;
use codex_protocol::mcp::OPENAI_FORM_EXTENSION_ID;
@@ -1017,6 +1019,9 @@ async fn spawn_internal_session_preserves_parent_lineage_without_forking_history
allow_login_shell: true,
permission_profile: config.permissions.permission_profile_state().snapshot(),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
windows_sandbox_private_desktop: config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: config.features.use_legacy_landlock(),
exec_policy: Some(codex_execpolicy::RequirementsExecPolicy::new(
codex_execpolicy::Policy::empty(),
)),
@@ -406,8 +406,7 @@ impl ApplyPatchHandler {
));
};
let fs = turn_environment.environment.get_filesystem();
let sandbox = turn
.file_system_sandbox_context(/*additional_permissions*/ None, turn_environment);
let sandbox = turn_environment.sandbox_context(/*additional_permissions*/ None);
match codex_apply_patch::verify_apply_patch_args_with_mode(
args,
turn_environment.cwd(),
@@ -518,8 +517,7 @@ pub(crate) async fn intercept_apply_patch(
tool_name: &str,
) -> Result<Option<FunctionToolOutput>, FunctionCallError> {
let turn = &step_context.turn;
let sandbox =
turn.file_system_sandbox_context(/*additional_permissions*/ None, &turn_environment);
let sandbox = turn_environment.sandbox_context(/*additional_permissions*/ None);
match codex_apply_patch::maybe_parse_apply_patch_verified_with_mode(
command,
cwd,
@@ -182,9 +182,7 @@ async fn to_extension_call(invocation: &ToolInvocation) -> ExtensionToolCall {
)
.await
.additional_permissions;
let file_system_sandbox_context = invocation
.turn
.file_system_sandbox_context(additional_permissions, environment);
let file_system_sandbox_context = environment.sandbox_context(additional_permissions);
environments.push(ToolEnvironment {
environment_id: environment.selection.environment_id.clone(),
cwd: native_cwd,
@@ -161,6 +161,8 @@ impl ExecCommandHandler {
// Remote executors enforce URI-native sandbox policy themselves. Only a host-local
// sandbox needs a native cwd for resolving paths nested in the permissions config.
// TODO(anp): Reconcile this backend choice with TurnEnvironment::sandbox_context
// so the native-cwd requirement follows the selected environment's sandbox.
let requires_host_native_cwd = !environment.is_remote()
&& SandboxManager::new().select_initial(
turn_environment.permission_profile(),
@@ -151,8 +151,7 @@ impl ViewImageHandler {
))
})?;
let model_visible_path = path_uri.inferred_native_path_string();
let sandbox = turn
.file_system_sandbox_context(/*additional_permissions*/ None, turn_environment);
let sandbox = turn_environment.sandbox_context(/*additional_permissions*/ None);
let fs = turn_environment.environment.get_filesystem();
let metadata = fs
+2
View File
@@ -269,6 +269,8 @@ impl ToolOrchestrator {
managed_network_active,
),
};
// TODO(anp): Reconcile backend selection and both attempts with
// TurnEnvironment::sandbox_context; turn-wide inputs can differ from owner config.
let initial_sandbox = if sandbox_requested && !executor_managed_process_sandbox {
self.sandbox.select_initial(
&permissions,
+2
View File
@@ -485,6 +485,8 @@ impl ToolRegistry {
let call_id_owned = invocation.call_id.clone();
let otel = invocation.turn.session_telemetry.clone();
let permission_profile = invocation.turn.permission_profile();
// TODO(anp): Reconcile these tags with TurnEnvironment::sandbox_context
// instead of reporting the thread-wide backend for environment-scoped tools.
let base_tool_result_tags = [
(
"sandbox",
@@ -27,6 +27,9 @@ fn test_turn_environment(environment_id: &str) -> crate::session::turn_context::
workspace_roots: Vec::new(),
config: EnvironmentConfigState::Ready(EnvironmentConfig {
allow_login_shell: true,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::legacy(
PermissionProfile::read_only(),
),
@@ -530,6 +530,7 @@ mod tests {
use crate::tools::sandboxing::ToolRuntime;
use codex_exec_server::Environment;
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::EnvironmentConfig;
use codex_protocol::protocol::EnvironmentConfigState;
@@ -550,6 +551,9 @@ mod tests {
workspace_roots: Vec::new(),
config: EnvironmentConfigState::Ready(EnvironmentConfig {
allow_login_shell: true,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: true,
use_legacy_landlock: false,
permission_profile: PermissionProfileSnapshot::legacy(
PermissionProfile::read_only(),
),
@@ -532,6 +532,8 @@ fn evaluate_intercepted_exec_policy(
struct InterceptedExecPolicyContext {
approval_policy: AskForApproval,
permission_profile: PermissionProfile,
// TODO(anp): Reconcile this policy input with TurnEnvironment::sandbox_context
// so intercepted commands use the selected environment's Windows backend.
windows_sandbox_level: WindowsSandboxLevel,
sandbox_permissions: SandboxPermissions,
enable_shell_wrapper_parsing: bool,
@@ -557,6 +559,8 @@ fn commands_for_intercepted_exec_policy(
vec![join_program_and_argv(program, argv)]
}
// TODO(anp): Capture these Windows and Landlock settings from
// TurnEnvironment::sandbox_context when preparing this executor, preserving its snapshot.
struct CoreShellCommandExecutor {
command: Vec<String>,
cwd: AbsolutePathBuf,
+2
View File
@@ -395,6 +395,8 @@ pub(crate) struct SandboxAttempt<'a> {
pub(crate) sandbox_cwd: &'a PathUri,
pub(crate) workspace_roots: &'a [PathUri],
pub codex_linux_sandbox_exe: Option<&'a std::path::PathBuf>,
// TODO(anp): Reconcile these attempt settings with TurnEnvironment::sandbox_context
// so process execution and patch writes honor the selected environment's backend.
pub use_legacy_landlock: bool,
pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
pub windows_sandbox_private_desktop: bool,
@@ -1070,6 +1070,12 @@ async fn zsh_fork_unified_exec_keeps_shell_parameter_when_remote_environment_ava
config: EnvironmentConfigState::Ready(
codex_protocol::protocol::EnvironmentConfig {
allow_login_shell: true,
windows_sandbox_level: turn.windows_sandbox_level,
windows_sandbox_private_desktop: turn
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: turn.config.features.use_legacy_landlock(),
permission_profile: turn
.config
.permissions
+2
View File
@@ -114,6 +114,8 @@ pub(crate) struct TurnMetadataState {
subagent_kind: Option<String>,
thread_source: Option<ThreadSource>,
turn_id: String,
// TODO(anp): Derive this cached tag from TurnEnvironment::sandbox_context
// so metadata reflects the selected environment's backend.
sandbox: Option<String>,
sandbox_mode: Option<String>,
auto_review_enabled: bool,
@@ -1,6 +1,8 @@
use anyhow::Result;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_exec_server::CreateDirectoryOptions;
use codex_features::Feature;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::PermissionProfileSnapshot;
use codex_protocol::protocol::EnvironmentConfig;
@@ -312,6 +314,12 @@ async fn v2_residency_reload_preserves_inherited_environment_and_tools(
allow_login_shell: test.config.permissions.allow_login_shell,
permission_profile: PermissionProfileSnapshot::legacy(child_permissions),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
+20
View File
@@ -5,6 +5,7 @@ use codex_config::test_support::CloudConfigBundleFixture;
use codex_core::EnvironmentConfig;
use codex_core::TurnInputRequest;
use codex_core::config::Constrained;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_execpolicy::Decision;
use codex_execpolicy::Policy;
use codex_execpolicy::RequirementsExecPolicy;
@@ -13,6 +14,7 @@ use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Settings;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::error::CodexErrorDetails;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::PermissionProfileSnapshot;
@@ -716,6 +718,12 @@ async fn environment_command_restrictions_override_saved_prefix_approvals() -> R
allow_login_shell: true,
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: Some(RequirementsExecPolicy::new(invalid_policy)),
mcp_policy: None,
network_policy: None,
@@ -739,6 +747,12 @@ async fn environment_command_restrictions_override_saved_prefix_approvals() -> R
allow_login_shell: true,
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: Some(RequirementsExecPolicy::new(environment_policy)),
mcp_policy: None,
network_policy: None,
@@ -843,6 +857,12 @@ async fn environment_command_policy_changes_invalidate_session_approvals() -> Re
PermissionProfile::Disabled,
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: Some(RequirementsExecPolicy::new(policy)),
mcp_policy: None,
network_policy: None,
@@ -14,6 +14,7 @@ use codex_core::config::Config;
use codex_core::config::Constrained;
use codex_core::config::CurrentTimeReminderConfig;
use codex_core::sandboxing::SandboxPermissions;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::ToolLifecycleContributor;
use codex_extension_api::ToolLifecycleFuture;
@@ -25,6 +26,7 @@ use codex_history::RolloutLine;
use codex_login::CodexAuth;
use codex_protocol::ThreadId;
use codex_protocol::config_types::ApprovalsReviewer;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::PermissionProfileSnapshot;
use codex_protocol::openai_models::AutoReviewMessages;
@@ -768,6 +770,9 @@ async fn guardian_session_is_reused_for_consecutive_tool_reviews_without_prewarm
test.config.permissions.permission_profile().clone(),
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test.config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
@@ -2,11 +2,13 @@ use anyhow::Context;
use anyhow::Result;
use codex_config::McpServerConfig;
use codex_core::EnvironmentConfig;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::ExecServerRuntimePaths;
use codex_features::Feature;
use codex_protocol::capabilities::CapabilityRootLocation;
use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfileSnapshot;
use codex_protocol::protocol::EnvironmentConfigState;
use codex_protocol::protocol::ThreadSettingsOverrides;
@@ -400,6 +402,13 @@ impl ExecutorStopHookFixture {
self.test.config.permissions.permission_profile().clone(),
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&self.test.config),
windows_sandbox_private_desktop: self
.test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: self.test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
@@ -1,6 +1,7 @@
use codex_core::EnvironmentConfig;
use codex_core::EnvironmentMcpPolicy;
use codex_core::TurnInputRequest;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::time::Duration;
@@ -12,6 +13,7 @@ use codex_config::McpServerOAuthConfig;
use codex_config::McpServerTransportConfig;
use codex_exec_server::CreateDirectoryOptions;
use codex_features::Feature;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::mcp_policy::McpServerIdentity;
use codex_protocol::mcp_policy::McpServerRequirement;
use codex_protocol::models::PermissionProfile;
@@ -362,6 +364,9 @@ async fn skill_mcp_dependency_oauth_uses_configured_http_client() -> Result<()>
allow_login_shell: fixture.config.permissions.allow_login_shell,
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&fixture.config),
windows_sandbox_private_desktop: fixture.config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: fixture.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: Some(EnvironmentMcpPolicy {
servers: Some(BTreeMap::from([(
@@ -3,6 +3,7 @@ use codex_config::Constrained;
use codex_core::EnvironmentConfig;
use codex_core::TurnInputRequest;
use codex_core::config::Config;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_extension_api::ExtensionFuture;
use codex_extension_api::ExtensionRegistryBuilder;
use codex_extension_api::McpServerContribution;
@@ -17,6 +18,7 @@ use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
use codex_mcp::McpResourceClient;
use codex_protocol::capabilities::CapabilityRootLocation;
use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::PermissionProfileSnapshot;
use codex_protocol::protocol::AskForApproval;
@@ -491,6 +493,12 @@ async fn root_reconciliation_reuses_pending_apps_startup() -> Result<()> {
test.config.permissions.permission_profile().clone(),
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
@@ -8,6 +8,7 @@ use codex_core::config::Constrained;
use codex_core::config::NetworkProxySpec;
use codex_core::shell::ShellType;
use codex_core::shell::get_shell;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
use codex_exec_server::REMOTE_ENVIRONMENT_ID;
@@ -21,6 +22,7 @@ use codex_protocol::approvals::NetworkPolicyRuleAction;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Settings;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::NetworkPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::PermissionProfileSnapshot;
@@ -2024,6 +2026,12 @@ async fn owner_network_policy_follows_the_selected_remote_command() -> Result<()
test.config.permissions.permission_profile().clone()
}),
shell_environment_policy: test.config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: Some(EnvironmentNetworkPolicy::from_config(
+44
View File
@@ -13,6 +13,7 @@ use codex_core::WaitForEnvironmentToolConfig;
use codex_core::compact::SUMMARIZATION_PROMPT;
use codex_core::config::Config;
use codex_core::config::Constrained;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_exec_server::CopyOptions;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::EnvironmentReadyInfo;
@@ -46,6 +47,7 @@ use codex_protocol::capabilities::SelectedCapabilityRoot;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Settings;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::ActivePermissionProfile;
use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::PermissionProfile;
@@ -557,6 +559,12 @@ async fn environment_permissions_follow_configuration_ownership() -> Result<()>
allow_login_shell: test.config.permissions.allow_login_shell,
permission_profile: owner_permission_profile,
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
@@ -1246,6 +1254,12 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
allow_login_shell: false,
permission_profile: permission_profile.clone(),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
@@ -1284,6 +1298,12 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
allow_login_shell: true,
permission_profile: permission_profile.clone(),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
@@ -1306,6 +1326,12 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
allow_login_shell: false,
permission_profile: permission_profile.clone(),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
@@ -1367,6 +1393,12 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
allow_login_shell: false,
permission_profile: permission_profile.clone(),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
@@ -1436,6 +1468,9 @@ async fn owner_network_policy_rejects_unsupported_environment_authority() -> Res
allow_login_shell: test.config.permissions.allow_login_shell,
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
shell_environment_policy: test.config.permissions.shell_environment_policy.clone(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test.config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: Some(EnvironmentNetworkPolicy::from_config(
@@ -1520,6 +1555,9 @@ async fn pending_attachment_installs_configuration_before_waiting_turn_resumes()
allow_login_shell,
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test.config.permissions.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
@@ -2174,6 +2212,12 @@ async fn deferred_executor_spawn_agent_inherits_ready_step_environments(
vec![owner_profile_workspace_root.clone()],
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&test.config),
windows_sandbox_private_desktop: test
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: test.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
+8
View File
@@ -27,6 +27,7 @@ use codex_core::EnvironmentConfig;
use codex_core::EnvironmentMcpPolicy;
use codex_core::TurnInputRequest;
use codex_core::config::Config;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::Environment;
use codex_exec_server::HttpRedirectPolicy;
@@ -45,6 +46,7 @@ use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::Settings;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::mcp_policy::McpServerIdentity;
use codex_protocol::mcp_policy::McpServerRequirement;
use codex_protocol::mcp_policy::PluginMcpRequirements;
@@ -712,6 +714,12 @@ async fn environment_mcp_policy_filters_runtime_config_and_model_tools(
fixture.config.permissions.permission_profile().clone(),
),
shell_environment_policy: Default::default(),
windows_sandbox_level: WindowsSandboxLevel::from_config(&fixture.config),
windows_sandbox_private_desktop: fixture
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: fixture.config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: Some(mcp_policy),
network_policy: None,
@@ -1,5 +1,6 @@
use codex_core::EnvironmentConfig;
use codex_core::TurnInputRequest;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use core_test_support::test_codex::local_selections;
use std::collections::HashMap;
use std::ffi::OsStr;
@@ -15,6 +16,7 @@ use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Settings;
use codex_protocol::config_types::ShellEnvironmentPolicy;
use codex_protocol::config_types::ShellEnvironmentPolicyInherit;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::models::PermissionProfileSnapshot;
use codex_protocol::models::ResponseItem;
@@ -402,6 +404,13 @@ async fn exec_command_uses_installed_environment_shell_policy_with_explicit_over
.clone(),
..Default::default()
},
windows_sandbox_level: WindowsSandboxLevel::from_config(&harness.test().config),
windows_sandbox_private_desktop: harness
.test()
.config
.permissions
.windows_sandbox_private_desktop,
use_legacy_landlock: harness.test().config.features.use_legacy_landlock(),
exec_policy: None,
mcp_policy: None,
network_policy: None,
+13
View File
@@ -1,5 +1,6 @@
use crate::capabilities::SelectedCapabilityRoot;
use crate::config_types::ShellEnvironmentPolicy;
use crate::config_types::WindowsSandboxLevel;
use crate::mcp_policy::EnvironmentMcpPolicy;
use crate::models::PermissionProfileSnapshot;
use codex_execpolicy::RequirementsExecPolicy;
@@ -28,6 +29,12 @@ pub struct EnvironmentConfig {
pub permission_profile: PermissionProfileSnapshot,
/// Controls which environment variables shell commands may inherit.
pub shell_environment_policy: ShellEnvironmentPolicy,
/// Windows sandbox implementation for this environment attachment.
pub windows_sandbox_level: WindowsSandboxLevel,
/// Whether Windows sandbox processes use a private desktop.
pub windows_sandbox_private_desktop: bool,
/// Whether Linux sandbox processes use the legacy Landlock backend.
pub use_legacy_landlock: bool,
/// Additional managed command restrictions for this environment attachment.
pub exec_policy: Option<RequirementsExecPolicy>,
/// Additional managed MCP restrictions for this environment attachment.
@@ -45,6 +52,12 @@ impl std::fmt::Debug for EnvironmentConfig {
.field("allow_login_shell", &self.allow_login_shell)
.field("permission_profile", &self.permission_profile)
.field("shell_environment_policy", &"<redacted>")
.field("windows_sandbox_level", &self.windows_sandbox_level)
.field(
"windows_sandbox_private_desktop",
&self.windows_sandbox_private_desktop,
)
.field("use_legacy_landlock", &self.use_legacy_landlock)
.field("exec_policy", &self.exec_policy)
.field("mcp_policy", &self.mcp_policy)
.field("network_policy", &self.network_policy)
+4
View File
@@ -119,6 +119,8 @@ pub struct SandboxExecRequest {
pub network: Option<NetworkProxy>,
pub network_environment_id: Option<String>,
pub sandbox: SandboxType,
// TODO(anp): Reconcile these backend copies with the supplied sandbox context
// (TurnEnvironment::sandbox_context for turns), preserving this launch snapshot.
pub windows_sandbox_level: WindowsSandboxLevel,
pub windows_sandbox_private_desktop: bool,
pub permission_profile: PermissionProfile,
@@ -139,6 +141,8 @@ pub struct SandboxTransformRequest<'a> {
pub network: Option<&'a NetworkProxy>,
pub sandbox_policy_cwd: &'a PathUri,
pub codex_linux_sandbox_exe: Option<&'a Path>,
// TODO(anp): Reconcile these backend inputs with the supplied sandbox context
// (TurnEnvironment::sandbox_context for turns) so selection shares its authority.
pub use_legacy_landlock: bool,
pub windows_sandbox_level: WindowsSandboxLevel,
pub windows_sandbox_private_desktop: bool,
+2
View File
@@ -14,6 +14,8 @@ use crate::WindowsSandboxFilesystemOverrides;
use crate::WindowsSandboxProxySettingsMode;
/// Windows-specific inputs for an executor-native process spawn.
// TODO(anp): Reconcile Windows backend and desktop copies with the supplied sandbox
// context (TurnEnvironment::sandbox_context for turns), preserving this launch snapshot.
pub struct WindowsSandboxSpawnRequest<'a> {
pub permission_profile: &'a PermissionProfile,
pub workspace_roots: &'a [AbsolutePathBuf],
+2
View File
@@ -153,6 +153,8 @@ impl App {
approvals_reviewer,
permission_profile: _,
active_permission_profile,
// TODO(anp): Support Windows sandbox updates through environment configuration;
// thread/settings/update cannot currently represent this override.
windows_sandbox_level: _,
model,
effort,
@@ -13,6 +13,8 @@ pub struct ElevatedSandboxProfileCaptureRequest<'a> {
pub env_map: HashMap<String, String>,
pub timeout_ms: Option<u64>,
pub cancellation: Option<crate::WindowsSandboxCancellationToken>,
// TODO(anp): Reconcile this private-desktop copy with the supplied sandbox context
// (TurnEnvironment::sandbox_context for turns), preserving this launch snapshot.
pub use_private_desktop: bool,
pub proxy_enforced: bool,
pub network_proxy_restricting_sid: Option<String>,
@@ -23,6 +23,8 @@ use std::path::PathBuf;
///
/// Callers should parse their own input shape first, then use this request to
/// share the elevated-vs-legacy backend selection and session launch path.
// TODO(anp): Reconcile Windows backend and desktop copies with the supplied sandbox
// context (TurnEnvironment::sandbox_context for turns), preserving this launch snapshot.
pub struct WindowsSandboxSessionRequest<'a> {
pub permission_profile: &'a PermissionProfile,
pub workspace_roots: &'a [AbsolutePathBuf],