mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Honor per-environment shell variable policies (#38902)
## What changed - Carry `ShellEnvironmentPolicy` in each resolved `EnvironmentConfig` and use the selected turn environment's policy for shell commands, user shell tasks, and unified exec. - Infer the policy from the thread configuration when an environment does not provide its own resolved configuration. - Redact the policy from `EnvironmentConfig` debug output because it can contain explicit environment variable values. ## Testing - Verify shell handlers and unified exec filter inherited variables according to the selected environment while preserving its explicit overrides. GitOrigin-RevId: 9f6a52aa4af60e5f17251a0e1b11e1926b779055
This commit is contained in:
1 parent
f85e81d30b
commit
6c108912ee
22 files changed
+219
-52
No files matched your search
@@ -344,6 +344,7 @@ fn resolved_local_environments<const N: usize>(
|
|||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
PermissionProfile::read_only(),
|
PermissionProfile::read_only(),
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -786,6 +786,7 @@ mod tests {
|
|||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -957,6 +958,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
ActivePermissionProfile::read_only(),
|
ActivePermissionProfile::read_only(),
|
||||||
vec![cwd.join("profile-root")],
|
vec![cwd.join("profile-root")],
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
};
|
};
|
||||||
let turn_environments = ThreadEnvironments::new(
|
let turn_environments = ThreadEnvironments::new(
|
||||||
@@ -1158,6 +1160,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
ActivePermissionProfile::read_only(),
|
ActivePermissionProfile::read_only(),
|
||||||
vec![cwd.join("profile-root")],
|
vec![cwd.join("profile-root")],
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
};
|
};
|
||||||
let cwd = PathUri::from_abs_path(&cwd);
|
let cwd = PathUri::from_abs_path(&cwd);
|
||||||
@@ -1491,6 +1494,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
ActivePermissionProfile::read_only(),
|
ActivePermissionProfile::read_only(),
|
||||||
vec![cwd.join("child-profile-root")],
|
vec![cwd.join("child-profile-root")],
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
};
|
};
|
||||||
let environments = ThreadEnvironments::new(
|
let environments = ThreadEnvironments::new(
|
||||||
@@ -1534,6 +1538,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
let parent_owner_config = EnvironmentConfig {
|
let parent_owner_config = EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: vec![root("parent-root")],
|
selected_capability_roots: vec![root("parent-root")],
|
||||||
};
|
};
|
||||||
let mut owner_selection = selection.clone();
|
let mut owner_selection = selection.clone();
|
||||||
|
|||||||
@@ -1120,6 +1120,7 @@ async fn mcp_sandbox_cwd_uses_matching_server_environment_uri() -> anyhow::Resul
|
|||||||
.permissions
|
.permissions
|
||||||
.permission_profile_state()
|
.permission_profile_state()
|
||||||
.snapshot(),
|
.snapshot(),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ impl Session {
|
|||||||
self.mark_mcp_runtime_dirty();
|
self.mark_mcp_runtime_dirty();
|
||||||
self.services.turn_environments.update_selections(
|
self.services.turn_environments.update_selections(
|
||||||
&environments,
|
&environments,
|
||||||
&state.session_configuration.turn_environment_config(),
|
&state.session_configuration.inferred_environment_config(),
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -695,6 +695,8 @@ impl Session {
|
|||||||
approval_policy: config.permissions.approval_policy.clone(),
|
approval_policy: config.permissions.approval_policy.clone(),
|
||||||
approvals_reviewer: config.approvals_reviewer,
|
approvals_reviewer: config.approvals_reviewer,
|
||||||
permission_profile_state: session_permission_profile_state_from_config(&config)?,
|
permission_profile_state: session_permission_profile_state_from_config(&config)?,
|
||||||
|
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_level: WindowsSandboxLevel::from_config(&config),
|
||||||
legacy_fallback_cwd: config.cwd.clone(),
|
legacy_fallback_cwd: config.cwd.clone(),
|
||||||
codex_home: config.codex_home.clone(),
|
codex_home: config.codex_home.clone(),
|
||||||
@@ -1596,12 +1598,14 @@ impl Session {
|
|||||||
if mcp_inputs_changed {
|
if mcp_inputs_changed {
|
||||||
self.mark_mcp_runtime_dirty();
|
self.mark_mcp_runtime_dirty();
|
||||||
}
|
}
|
||||||
let environment_config = updated.turn_environment_config();
|
let environment_config = updated.inferred_environment_config();
|
||||||
if let Some(environments) = &updates.environments {
|
if let Some(environments) = &updates.environments {
|
||||||
self.services
|
self.services
|
||||||
.turn_environments
|
.turn_environments
|
||||||
.update_selections(&environments.environments, &environment_config);
|
.update_selections(&environments.environments, &environment_config);
|
||||||
} else if state.session_configuration.turn_environment_config() != environment_config {
|
} else if state.session_configuration.inferred_environment_config()
|
||||||
|
!= environment_config
|
||||||
|
{
|
||||||
self.services
|
self.services
|
||||||
.turn_environments
|
.turn_environments
|
||||||
.update_thread_config(&environment_config);
|
.update_thread_config(&environment_config);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use codex_protocol::SessionId;
|
|||||||
use codex_protocol::capabilities::SelectedCapabilityRoot;
|
use codex_protocol::capabilities::SelectedCapabilityRoot;
|
||||||
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
use codex_protocol::config_types::SERVICE_TIER_DEFAULT_REQUEST_VALUE;
|
||||||
use codex_protocol::config_types::ServiceTier;
|
use codex_protocol::config_types::ServiceTier;
|
||||||
|
use codex_protocol::config_types::ShellEnvironmentPolicy;
|
||||||
use codex_protocol::mcp::ClientMcpExtensions;
|
use codex_protocol::mcp::ClientMcpExtensions;
|
||||||
use codex_protocol::permissions::FileSystemPath;
|
use codex_protocol::permissions::FileSystemPath;
|
||||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||||
@@ -90,6 +91,8 @@ pub(crate) struct SessionConfiguration {
|
|||||||
/// active profile id, and profile-defined workspace roots in sync by using
|
/// active profile id, and profile-defined workspace roots in sync by using
|
||||||
/// the methods below instead of mutating the fields independently.
|
/// the methods below instead of mutating the fields independently.
|
||||||
pub(super) permission_profile_state: PermissionProfileState,
|
pub(super) permission_profile_state: PermissionProfileState,
|
||||||
|
pub(super) allow_login_shell: bool,
|
||||||
|
pub(super) shell_environment_policy: ShellEnvironmentPolicy,
|
||||||
pub(super) windows_sandbox_level: WindowsSandboxLevel,
|
pub(super) windows_sandbox_level: WindowsSandboxLevel,
|
||||||
|
|
||||||
/// Legacy thread cwd used when a turn does not select an environment.
|
/// Legacy thread cwd used when a turn does not select an environment.
|
||||||
@@ -136,13 +139,11 @@ impl SessionConfiguration {
|
|||||||
&self.permission_profile_state
|
&self.permission_profile_state
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn turn_environment_config(&self) -> EnvironmentConfig {
|
pub(super) fn inferred_environment_config(&self) -> EnvironmentConfig {
|
||||||
EnvironmentConfig {
|
EnvironmentConfig {
|
||||||
allow_login_shell: self
|
allow_login_shell: self.allow_login_shell,
|
||||||
.original_config_do_not_use
|
|
||||||
.permissions
|
|
||||||
.allow_login_shell,
|
|
||||||
permission_profile: self.permission_profile_state.snapshot(),
|
permission_profile: self.permission_profile_state.snapshot(),
|
||||||
|
shell_environment_policy: self.shell_environment_policy.clone(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1080,14 +1081,14 @@ impl Session {
|
|||||||
let turn_environments = Arc::new(ThreadEnvironments::new(
|
let turn_environments = Arc::new(ThreadEnvironments::new(
|
||||||
environment_manager,
|
environment_manager,
|
||||||
default_shell.clone(),
|
default_shell.clone(),
|
||||||
session_configuration.turn_environment_config(),
|
session_configuration.inferred_environment_config(),
|
||||||
shell_snapshot,
|
shell_snapshot,
|
||||||
inherited_environments.unwrap_or_default(),
|
inherited_environments.unwrap_or_default(),
|
||||||
config.features.enabled(Feature::DeferredExecutor),
|
config.features.enabled(Feature::DeferredExecutor),
|
||||||
));
|
));
|
||||||
turn_environments.update_selections(
|
turn_environments.update_selections(
|
||||||
environment_selections,
|
environment_selections,
|
||||||
&session_configuration.turn_environment_config(),
|
&session_configuration.inferred_environment_config(),
|
||||||
);
|
);
|
||||||
let resolved_environments = turn_environments.snapshot().await;
|
let resolved_environments = turn_environments.snapshot().await;
|
||||||
let agents_md_manager = Arc::new(AgentsMdManager::new(user_instructions));
|
let agents_md_manager = Arc::new(AgentsMdManager::new(user_instructions));
|
||||||
|
|||||||
@@ -4189,6 +4189,8 @@ async fn set_rate_limits_retains_previous_credits() {
|
|||||||
approval_policy: config.permissions.approval_policy.clone(),
|
approval_policy: config.permissions.approval_policy.clone(),
|
||||||
approvals_reviewer: config.approvals_reviewer,
|
approvals_reviewer: config.approvals_reviewer,
|
||||||
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
||||||
|
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_level: WindowsSandboxLevel::from_config(&config),
|
||||||
legacy_fallback_cwd: config.cwd.clone(),
|
legacy_fallback_cwd: config.cwd.clone(),
|
||||||
codex_home: config.codex_home.clone(),
|
codex_home: config.codex_home.clone(),
|
||||||
@@ -4298,6 +4300,8 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
|||||||
approval_policy: config.permissions.approval_policy.clone(),
|
approval_policy: config.permissions.approval_policy.clone(),
|
||||||
approvals_reviewer: config.approvals_reviewer,
|
approvals_reviewer: config.approvals_reviewer,
|
||||||
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
||||||
|
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_level: WindowsSandboxLevel::from_config(&config),
|
||||||
legacy_fallback_cwd: config.cwd.clone(),
|
legacy_fallback_cwd: config.cwd.clone(),
|
||||||
codex_home: config.codex_home.clone(),
|
codex_home: config.codex_home.clone(),
|
||||||
@@ -4852,6 +4856,8 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
|||||||
approval_policy: config.permissions.approval_policy.clone(),
|
approval_policy: config.permissions.approval_policy.clone(),
|
||||||
approvals_reviewer: config.approvals_reviewer,
|
approvals_reviewer: config.approvals_reviewer,
|
||||||
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
||||||
|
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_level: WindowsSandboxLevel::from_config(&config),
|
||||||
legacy_fallback_cwd: config.cwd.clone(),
|
legacy_fallback_cwd: config.cwd.clone(),
|
||||||
codex_home: config.codex_home.clone(),
|
codex_home: config.codex_home.clone(),
|
||||||
@@ -4963,14 +4969,14 @@ async fn resolved_environments_for_configuration(
|
|||||||
let turn_environments = ThreadEnvironments::new(
|
let turn_environments = ThreadEnvironments::new(
|
||||||
Arc::clone(&environment_manager),
|
Arc::clone(&environment_manager),
|
||||||
default_user_shell(),
|
default_user_shell(),
|
||||||
session_configuration.turn_environment_config(),
|
session_configuration.inferred_environment_config(),
|
||||||
ShellSnapshot::disabled(),
|
ShellSnapshot::disabled(),
|
||||||
TurnEnvironmentSnapshot::default(),
|
TurnEnvironmentSnapshot::default(),
|
||||||
/*non_blocking_snapshots*/ false,
|
/*non_blocking_snapshots*/ false,
|
||||||
);
|
);
|
||||||
turn_environments.update_selections(
|
turn_environments.update_selections(
|
||||||
environment_selections,
|
environment_selections,
|
||||||
&session_configuration.turn_environment_config(),
|
&session_configuration.inferred_environment_config(),
|
||||||
);
|
);
|
||||||
(environment_manager, turn_environments.snapshot().await)
|
(environment_manager, turn_environments.snapshot().await)
|
||||||
}
|
}
|
||||||
@@ -5641,6 +5647,8 @@ async fn session_new_fails_when_zsh_fork_enabled_without_packaged_zsh() {
|
|||||||
approval_policy: config.permissions.approval_policy.clone(),
|
approval_policy: config.permissions.approval_policy.clone(),
|
||||||
approvals_reviewer: config.approvals_reviewer,
|
approvals_reviewer: config.approvals_reviewer,
|
||||||
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
||||||
|
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_level: WindowsSandboxLevel::from_config(&config),
|
||||||
legacy_fallback_cwd: config.cwd.clone(),
|
legacy_fallback_cwd: config.cwd.clone(),
|
||||||
codex_home: config.codex_home.clone(),
|
codex_home: config.codex_home.clone(),
|
||||||
@@ -5787,6 +5795,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
|||||||
approval_policy: config.permissions.approval_policy.clone(),
|
approval_policy: config.permissions.approval_policy.clone(),
|
||||||
approvals_reviewer: config.approvals_reviewer,
|
approvals_reviewer: config.approvals_reviewer,
|
||||||
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
||||||
|
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_level: WindowsSandboxLevel::from_config(&config),
|
||||||
legacy_fallback_cwd: config.cwd.clone(),
|
legacy_fallback_cwd: config.cwd.clone(),
|
||||||
codex_home: config.codex_home.clone(),
|
codex_home: config.codex_home.clone(),
|
||||||
@@ -5820,7 +5830,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
|||||||
let turn_environments = Arc::new(ThreadEnvironments::new(
|
let turn_environments = Arc::new(ThreadEnvironments::new(
|
||||||
environment_manager,
|
environment_manager,
|
||||||
default_user_shell(),
|
default_user_shell(),
|
||||||
session_configuration.turn_environment_config(),
|
session_configuration.inferred_environment_config(),
|
||||||
ShellSnapshot::disabled(),
|
ShellSnapshot::disabled(),
|
||||||
resolved_environments,
|
resolved_environments,
|
||||||
/*non_blocking_snapshots*/ false,
|
/*non_blocking_snapshots*/ false,
|
||||||
@@ -6068,6 +6078,8 @@ async fn make_session_with_config_and_rx(
|
|||||||
approval_policy: config.permissions.approval_policy.clone(),
|
approval_policy: config.permissions.approval_policy.clone(),
|
||||||
approvals_reviewer: config.approvals_reviewer,
|
approvals_reviewer: config.approvals_reviewer,
|
||||||
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
||||||
|
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_level: WindowsSandboxLevel::from_config(&config),
|
||||||
legacy_fallback_cwd: config.cwd.clone(),
|
legacy_fallback_cwd: config.cwd.clone(),
|
||||||
codex_home: config.codex_home.clone(),
|
codex_home: config.codex_home.clone(),
|
||||||
@@ -6187,6 +6199,8 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
|
|||||||
approval_policy: config.permissions.approval_policy.clone(),
|
approval_policy: config.permissions.approval_policy.clone(),
|
||||||
approvals_reviewer: config.approvals_reviewer,
|
approvals_reviewer: config.approvals_reviewer,
|
||||||
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
||||||
|
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_level: WindowsSandboxLevel::from_config(&config),
|
||||||
legacy_fallback_cwd: config.cwd.clone(),
|
legacy_fallback_cwd: config.cwd.clone(),
|
||||||
codex_home: config.codex_home.clone(),
|
codex_home: config.codex_home.clone(),
|
||||||
@@ -7987,6 +8001,8 @@ where
|
|||||||
approval_policy: config.permissions.approval_policy.clone(),
|
approval_policy: config.permissions.approval_policy.clone(),
|
||||||
approvals_reviewer: config.approvals_reviewer,
|
approvals_reviewer: config.approvals_reviewer,
|
||||||
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
permission_profile_state: config.permissions.permission_profile_state().clone(),
|
||||||
|
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_level: WindowsSandboxLevel::from_config(&config),
|
||||||
legacy_fallback_cwd: config.cwd.clone(),
|
legacy_fallback_cwd: config.cwd.clone(),
|
||||||
codex_home: config.codex_home.clone(),
|
codex_home: config.codex_home.clone(),
|
||||||
@@ -8019,7 +8035,7 @@ where
|
|||||||
let turn_environments = Arc::new(ThreadEnvironments::new(
|
let turn_environments = Arc::new(ThreadEnvironments::new(
|
||||||
environment_manager,
|
environment_manager,
|
||||||
default_user_shell(),
|
default_user_shell(),
|
||||||
session_configuration.turn_environment_config(),
|
session_configuration.inferred_environment_config(),
|
||||||
ShellSnapshot::disabled(),
|
ShellSnapshot::disabled(),
|
||||||
resolved_turn_environments.clone(),
|
resolved_turn_environments.clone(),
|
||||||
/*non_blocking_snapshots*/ false,
|
/*non_blocking_snapshots*/ false,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use codex_file_system::FileSystemSandboxContext;
|
|||||||
use codex_model_provider::SharedModelProvider;
|
use codex_model_provider::SharedModelProvider;
|
||||||
use codex_protocol::SessionId;
|
use codex_protocol::SessionId;
|
||||||
use codex_protocol::ThreadId;
|
use codex_protocol::ThreadId;
|
||||||
|
use codex_protocol::config_types::ShellEnvironmentPolicy;
|
||||||
use codex_protocol::models::AdditionalPermissionProfile;
|
use codex_protocol::models::AdditionalPermissionProfile;
|
||||||
use codex_protocol::openai_models::MODEL_SPECIALTY_CYBER;
|
use codex_protocol::openai_models::MODEL_SPECIALTY_CYBER;
|
||||||
use codex_protocol::openai_models::ModelInfo;
|
use codex_protocol::openai_models::ModelInfo;
|
||||||
@@ -66,6 +67,10 @@ impl TurnEnvironment {
|
|||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn shell_environment_policy(&self) -> &ShellEnvironmentPolicy {
|
||||||
|
&self.config().shell_environment_policy
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn config_mut(&mut self) -> &mut EnvironmentConfig {
|
pub(crate) fn config_mut(&mut self) -> &mut EnvironmentConfig {
|
||||||
let EnvironmentConfigState::Ready(config) = &mut self.selection.config else {
|
let EnvironmentConfigState::Ready(config) = &mut self.selection.config else {
|
||||||
@@ -712,12 +717,12 @@ impl Session {
|
|||||||
previous_permission_profile != next_permission_profile;
|
previous_permission_profile != next_permission_profile;
|
||||||
let previous_config = notify_config_contributors
|
let previous_config = notify_config_contributors
|
||||||
.then(|| self.build_effective_session_config(&state.session_configuration));
|
.then(|| self.build_effective_session_config(&state.session_configuration));
|
||||||
let environment_config = next.turn_environment_config();
|
let environment_config = next.inferred_environment_config();
|
||||||
if let Some(environments) = &updates.environments {
|
if let Some(environments) = &updates.environments {
|
||||||
self.services
|
self.services
|
||||||
.turn_environments
|
.turn_environments
|
||||||
.update_selections(&environments.environments, &environment_config);
|
.update_selections(&environments.environments, &environment_config);
|
||||||
} else if state.session_configuration.turn_environment_config()
|
} else if state.session_configuration.inferred_environment_config()
|
||||||
!= environment_config
|
!= environment_config
|
||||||
{
|
{
|
||||||
self.services
|
self.services
|
||||||
|
|||||||
@@ -158,10 +158,8 @@ pub(crate) async fn execute_user_shell_command(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let shell_snapshot_location = turn_environment.shell_snapshot(&cwd);
|
let shell_snapshot_location = turn_environment.shell_snapshot(&cwd);
|
||||||
let mut exec_env_map = create_env(
|
let shell_environment_policy = turn_environment.shell_environment_policy();
|
||||||
&turn_context.config.permissions.shell_environment_policy,
|
let mut exec_env_map = create_env(shell_environment_policy, Some(session.thread_id));
|
||||||
Some(session.thread_id),
|
|
||||||
);
|
|
||||||
inject_session_id_env(&mut exec_env_map, session.session_id());
|
inject_session_id_env(&mut exec_env_map, session.session_id());
|
||||||
inject_apply_patch_env(&mut exec_env_map, &turn_context.config.features);
|
inject_apply_patch_env(&mut exec_env_map, &turn_context.config.features);
|
||||||
if exec_env_map.contains_key(PROXY_ACTIVE_ENV_KEY) {
|
if exec_env_map.contains_key(PROXY_ACTIVE_ENV_KEY) {
|
||||||
@@ -171,11 +169,7 @@ pub(crate) async fn execute_user_shell_command(
|
|||||||
&display_command,
|
&display_command,
|
||||||
environment_shell,
|
environment_shell,
|
||||||
shell_snapshot_location.as_ref(),
|
shell_snapshot_location.as_ref(),
|
||||||
&turn_context
|
&shell_environment_policy.r#set,
|
||||||
.config
|
|
||||||
.permissions
|
|
||||||
.shell_environment_policy
|
|
||||||
.r#set,
|
|
||||||
&mut exec_env_map,
|
&mut exec_env_map,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -81,12 +81,7 @@ async fn run_exec_like(args: RunExecLikeArgs) -> Result<FunctionToolOutput, Func
|
|||||||
|
|
||||||
let fs = turn_environment.environment.get_filesystem();
|
let fs = turn_environment.environment.get_filesystem();
|
||||||
|
|
||||||
let mut explicit_env_overrides = turn
|
let mut explicit_env_overrides = turn_environment.shell_environment_policy().r#set.clone();
|
||||||
.config
|
|
||||||
.permissions
|
|
||||||
.shell_environment_policy
|
|
||||||
.r#set
|
|
||||||
.clone();
|
|
||||||
let mut env = exec_params.env.clone();
|
let mut env = exec_params.env.clone();
|
||||||
strip_output_env(&mut env);
|
strip_output_env(&mut env);
|
||||||
strip_output_env(&mut explicit_env_overrides);
|
strip_output_env(&mut explicit_env_overrides);
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ impl ShellCommandHandler {
|
|||||||
let command = Self::base_command(shell, ¶ms.command, use_login_shell);
|
let command = Self::base_command(shell, ¶ms.command, use_login_shell);
|
||||||
|
|
||||||
let mut env = create_env(
|
let mut env = create_env(
|
||||||
&turn_context.config.permissions.shell_environment_policy,
|
turn_environment.shell_environment_policy(),
|
||||||
Some(session.thread_id),
|
Some(session.thread_id),
|
||||||
);
|
);
|
||||||
inject_session_id_env(&mut env, session.session_id());
|
inject_session_id_env(&mut env, session.session_id());
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use codex_protocol::config_types::EnvironmentVariablePattern;
|
||||||
|
use codex_protocol::config_types::ShellEnvironmentPolicy;
|
||||||
|
use codex_protocol::config_types::ShellEnvironmentPolicyInherit;
|
||||||
use codex_protocol::models::ActivePermissionProfile;
|
use codex_protocol::models::ActivePermissionProfile;
|
||||||
use codex_protocol::models::ShellCommandToolCallParams;
|
use codex_protocol::models::ShellCommandToolCallParams;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
@@ -82,7 +86,14 @@ fn assert_safe(shell: &Shell, command: &str) {
|
|||||||
async fn shell_command_handler_to_exec_params_uses_selected_environment() {
|
async fn shell_command_handler_to_exec_params_uses_selected_environment() {
|
||||||
let (session, mut turn_context) = make_session_and_context().await;
|
let (session, mut turn_context) = make_session_and_context().await;
|
||||||
let permission_profile = turn_context.config.permissions.permission_profile().clone();
|
let permission_profile = turn_context.config.permissions.permission_profile().clone();
|
||||||
Arc::make_mut(&mut turn_context.config)
|
let config = Arc::make_mut(&mut turn_context.config);
|
||||||
|
config.permissions.shell_environment_policy.r#set = HashMap::from([
|
||||||
|
("KEEP".to_string(), "from-thread".to_string()),
|
||||||
|
("DROP".to_string(), "from-thread".to_string()),
|
||||||
|
]);
|
||||||
|
config.permissions.shell_environment_policy.include_only =
|
||||||
|
vec![EnvironmentVariablePattern::new_case_insensitive("DROP")];
|
||||||
|
config
|
||||||
.permissions
|
.permissions
|
||||||
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
|
.set_permission_profile_from_session_snapshot(PermissionProfileSnapshot::active(
|
||||||
permission_profile.clone(),
|
permission_profile.clone(),
|
||||||
@@ -105,6 +116,17 @@ async fn shell_command_handler_to_exec_params_uses_selected_environment() {
|
|||||||
let selected_cwd = turn_context.config.cwd.join("selected-environment");
|
let selected_cwd = turn_context.config.cwd.join("selected-environment");
|
||||||
let expected_cwd = selected_cwd.join("subdir");
|
let expected_cwd = selected_cwd.join("subdir");
|
||||||
let active_permission_profile = ActivePermissionProfile::new("selected-profile");
|
let active_permission_profile = ActivePermissionProfile::new("selected-profile");
|
||||||
|
let selected_shell_environment_policy = ShellEnvironmentPolicy {
|
||||||
|
inherit: ShellEnvironmentPolicyInherit::None,
|
||||||
|
include_only: vec![EnvironmentVariablePattern::new_case_insensitive("KEEP")],
|
||||||
|
r#set: turn_context
|
||||||
|
.config
|
||||||
|
.permissions
|
||||||
|
.shell_environment_policy
|
||||||
|
.r#set
|
||||||
|
.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
let selected_environment = TurnEnvironment::new(
|
let selected_environment = TurnEnvironment::new(
|
||||||
TurnEnvironmentSelection {
|
TurnEnvironmentSelection {
|
||||||
environment_id: "selected-environment".to_string(),
|
environment_id: "selected-environment".to_string(),
|
||||||
@@ -116,6 +138,7 @@ async fn shell_command_handler_to_exec_params_uses_selected_environment() {
|
|||||||
permission_profile,
|
permission_profile,
|
||||||
active_permission_profile.clone(),
|
active_permission_profile.clone(),
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: selected_shell_environment_policy.clone(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -129,10 +152,7 @@ async fn shell_command_handler_to_exec_params_uses_selected_environment() {
|
|||||||
),
|
),
|
||||||
Some(selected_shell),
|
Some(selected_shell),
|
||||||
);
|
);
|
||||||
let mut expected_env = create_env(
|
let mut expected_env = create_env(&selected_shell_environment_policy, Some(session.thread_id));
|
||||||
&turn_context.config.permissions.shell_environment_policy,
|
|
||||||
Some(session.thread_id),
|
|
||||||
);
|
|
||||||
inject_session_id_env(&mut expected_env, session.session_id());
|
inject_session_id_env(&mut expected_env, session.session_id());
|
||||||
inject_permission_profile_env(&mut expected_env, Some(&active_permission_profile));
|
inject_permission_profile_env(&mut expected_env, Some(&active_permission_profile));
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ fn test_turn_environment(environment_id: &str) -> crate::session::turn_context::
|
|||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
PermissionProfile::read_only(),
|
PermissionProfile::read_only(),
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ async fn approval_key_uses_path_uri_and_includes_environment_id() {
|
|||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
PermissionProfile::read_only(),
|
PermissionProfile::read_only(),
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -542,6 +542,7 @@ mod tests {
|
|||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
PermissionProfile::read_only(),
|
PermissionProfile::read_only(),
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -916,6 +916,7 @@ async fn zsh_fork_unified_exec_keeps_shell_parameter_when_remote_environment_ava
|
|||||||
.permissions
|
.permissions
|
||||||
.permission_profile_state()
|
.permission_profile_state()
|
||||||
.snapshot(),
|
.snapshot(),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1217,10 +1217,8 @@ impl UnifiedExecProcessManager {
|
|||||||
context: &UnifiedExecContext,
|
context: &UnifiedExecContext,
|
||||||
) -> Result<(UnifiedExecAttempt, Option<DeferredNetworkApproval>), UnifiedExecError> {
|
) -> Result<(UnifiedExecAttempt, Option<DeferredNetworkApproval>), UnifiedExecError> {
|
||||||
let turn = &context.step_context.turn;
|
let turn = &context.step_context.turn;
|
||||||
let local_policy_env = create_env(
|
let shell_environment_policy = request.turn_environment.shell_environment_policy();
|
||||||
&turn.config.permissions.shell_environment_policy,
|
let local_policy_env = create_env(shell_environment_policy, /*thread_id*/ None);
|
||||||
/*thread_id*/ None,
|
|
||||||
);
|
|
||||||
let mut env = local_policy_env.clone();
|
let mut env = local_policy_env.clone();
|
||||||
env.insert(
|
env.insert(
|
||||||
CODEX_THREAD_ID_ENV_VAR.to_string(),
|
CODEX_THREAD_ID_ENV_VAR.to_string(),
|
||||||
@@ -1232,17 +1230,10 @@ impl UnifiedExecProcessManager {
|
|||||||
inject_permission_profile_env(&mut env, active_permission_profile.as_ref());
|
inject_permission_profile_env(&mut env, active_permission_profile.as_ref());
|
||||||
let mut env = apply_unified_exec_env(env);
|
let mut env = apply_unified_exec_env(env);
|
||||||
strip_output_env(&mut env);
|
strip_output_env(&mut env);
|
||||||
let mut explicit_env_overrides = turn
|
let mut explicit_env_overrides = shell_environment_policy.r#set.clone();
|
||||||
.config
|
|
||||||
.permissions
|
|
||||||
.shell_environment_policy
|
|
||||||
.r#set
|
|
||||||
.clone();
|
|
||||||
strip_output_env(&mut explicit_env_overrides);
|
strip_output_env(&mut explicit_env_overrides);
|
||||||
let exec_server_env_config = ExecServerEnvConfig {
|
let exec_server_env_config = ExecServerEnvConfig {
|
||||||
policy: exec_env_policy_from_shell_policy(
|
policy: exec_env_policy_from_shell_policy(shell_environment_policy),
|
||||||
&turn.config.permissions.shell_environment_policy,
|
|
||||||
),
|
|
||||||
local_policy_env,
|
local_policy_env,
|
||||||
};
|
};
|
||||||
let mut orchestrator = ToolOrchestrator::new();
|
let mut orchestrator = ToolOrchestrator::new();
|
||||||
|
|||||||
@@ -597,6 +597,7 @@ async fn guardian_session_is_reused_for_consecutive_tool_reviews_without_prewarm
|
|||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
test.config.permissions.permission_profile().clone(),
|
test.config.permissions.permission_profile().clone(),
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
};
|
};
|
||||||
parent_environments
|
parent_environments
|
||||||
|
|||||||
@@ -482,6 +482,7 @@ async fn root_reconciliation_reuses_pending_apps_startup() -> Result<()> {
|
|||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
test.config.permissions.permission_profile().clone(),
|
test.config.permissions.permission_profile().clone(),
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: vec![SelectedCapabilityRoot {
|
selected_capability_roots: vec![SelectedCapabilityRoot {
|
||||||
id: "calendar-root".to_string(),
|
id: "calendar-root".to_string(),
|
||||||
location: CapabilityRootLocation::Environment {
|
location: CapabilityRootLocation::Environment {
|
||||||
|
|||||||
@@ -550,6 +550,7 @@ async fn environment_permissions_follow_configuration_ownership() -> Result<()>
|
|||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
PermissionProfile::read_only(),
|
PermissionProfile::read_only(),
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
..selection
|
..selection
|
||||||
@@ -1171,6 +1172,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
EnvironmentConfigState::Ready(EnvironmentConfig {
|
EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: vec![root("duplicate"), root("duplicate")],
|
selected_capability_roots: vec![root("duplicate"), root("duplicate")],
|
||||||
}),
|
}),
|
||||||
] {
|
] {
|
||||||
@@ -1205,6 +1207,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: vec![root("startup-root"), root("second-root")],
|
selected_capability_roots: vec![root("startup-root"), root("second-root")],
|
||||||
}),
|
}),
|
||||||
..selection.clone()
|
..selection.clone()
|
||||||
@@ -1223,6 +1226,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: vec![root("first-root")],
|
selected_capability_roots: vec![root("first-root")],
|
||||||
}),
|
}),
|
||||||
..selection.clone()
|
..selection.clone()
|
||||||
@@ -1280,6 +1284,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
config: EnvironmentConfigState::Ready(EnvironmentConfig {
|
||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: vec![root("first-updated-root")],
|
selected_capability_roots: vec![root("first-updated-root")],
|
||||||
}),
|
}),
|
||||||
..selection.clone()
|
..selection.clone()
|
||||||
@@ -1375,6 +1380,7 @@ async fn pending_attachment_installs_configuration_before_waiting_turn_resumes()
|
|||||||
let owner_config = |id: &str, allow_login_shell: bool| EnvironmentConfig {
|
let owner_config = |id: &str, allow_login_shell: bool| EnvironmentConfig {
|
||||||
allow_login_shell,
|
allow_login_shell,
|
||||||
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::read_only()),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: vec![root(id)],
|
selected_capability_roots: vec![root(id)],
|
||||||
};
|
};
|
||||||
let start_pending_thread = || {
|
let start_pending_thread = || {
|
||||||
@@ -1690,6 +1696,7 @@ async fn ready_before_selection_exposes_remote_tools_and_capability_context_afte
|
|||||||
permission_profile: PermissionProfileSnapshot::legacy(
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
test.config.permissions.permission_profile().clone(),
|
test.config.permissions.permission_profile().clone(),
|
||||||
),
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
selected_capability_roots: vec![ready_root],
|
selected_capability_roots: vec![ready_root],
|
||||||
}),
|
}),
|
||||||
}]),
|
}]),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use codex_core::EnvironmentConfig;
|
||||||
use codex_core::TurnInputRequest;
|
use codex_core::TurnInputRequest;
|
||||||
use core_test_support::test_codex::local_selections;
|
use core_test_support::test_codex::local_selections;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -10,9 +11,13 @@ use anyhow::Result;
|
|||||||
use codex_exec_server::CreateDirectoryOptions;
|
use codex_exec_server::CreateDirectoryOptions;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
use codex_protocol::config_types::CollaborationMode;
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
|
use codex_protocol::config_types::EnvironmentVariablePattern;
|
||||||
use codex_protocol::config_types::ModeKind;
|
use codex_protocol::config_types::ModeKind;
|
||||||
use codex_protocol::config_types::Settings;
|
use codex_protocol::config_types::Settings;
|
||||||
|
use codex_protocol::config_types::ShellEnvironmentPolicy;
|
||||||
|
use codex_protocol::config_types::ShellEnvironmentPolicyInherit;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
|
use codex_protocol::models::PermissionProfileSnapshot;
|
||||||
use codex_protocol::models::ResponseItem;
|
use codex_protocol::models::ResponseItem;
|
||||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
@@ -296,6 +301,106 @@ async fn exec_command_hides_and_rejects_login_when_disabled() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn exec_command_uses_installed_environment_shell_policy_with_explicit_overrides() -> Result<()>
|
||||||
|
{
|
||||||
|
skip_if_wine_exec!(
|
||||||
|
Ok(()),
|
||||||
|
"basic PowerShell execution through Wine exec is not passing yet"
|
||||||
|
);
|
||||||
|
skip_if_no_network!(Ok(()));
|
||||||
|
|
||||||
|
let builder = test_codex().with_model("gpt-5.4").with_config(|config| {
|
||||||
|
config.use_experimental_unified_exec_tool = true;
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::UnifiedExec)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
config.permissions.shell_environment_policy = ShellEnvironmentPolicy {
|
||||||
|
inherit: ShellEnvironmentPolicyInherit::None,
|
||||||
|
include_only: vec![EnvironmentVariablePattern::new_case_insensitive("DROP")],
|
||||||
|
r#set: HashMap::from([
|
||||||
|
("KEEP".to_string(), "preserved".to_string()),
|
||||||
|
("DROP".to_string(), "filtered".to_string()),
|
||||||
|
]),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
let harness = TestCodexHarness::with_auto_env_builder(builder).await?;
|
||||||
|
let selection = harness
|
||||||
|
.test()
|
||||||
|
.codex
|
||||||
|
.environment_selections()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.context("thread should select its executor environment")?;
|
||||||
|
harness
|
||||||
|
.test()
|
||||||
|
.codex
|
||||||
|
.environment_ready(
|
||||||
|
&selection,
|
||||||
|
EnvironmentConfig {
|
||||||
|
allow_login_shell: true,
|
||||||
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
||||||
|
shell_environment_policy: ShellEnvironmentPolicy {
|
||||||
|
inherit: ShellEnvironmentPolicyInherit::None,
|
||||||
|
include_only: vec![EnvironmentVariablePattern::new_case_insensitive("KEEP")],
|
||||||
|
r#set: harness
|
||||||
|
.test()
|
||||||
|
.config
|
||||||
|
.permissions
|
||||||
|
.shell_environment_policy
|
||||||
|
.r#set
|
||||||
|
.clone(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
selected_capability_roots: Vec::new(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let call_id = "exec-command-environment-shell-policy";
|
||||||
|
let command = match core_test_support::test_target_os() {
|
||||||
|
core_test_support::TestTargetOs::Linux | core_test_support::TestTargetOs::MacOs => {
|
||||||
|
r#"printf '%s:%s:%s' "$KEEP" "${DROP:-missing}" "${OWNER_ONLY:-missing}""#
|
||||||
|
}
|
||||||
|
core_test_support::TestTargetOs::Windows => {
|
||||||
|
r#"if (Test-Path Env:DROP) { $drop = $env:DROP } else { $drop = 'missing' }; if (Test-Path Env:OWNER_ONLY) { $owner = $env:OWNER_ONLY } else { $owner = 'missing' }; Write-Output "${env:KEEP}:${drop}:${owner}""#
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let arguments = json!({
|
||||||
|
"cmd": command,
|
||||||
|
"login": false,
|
||||||
|
"yield_time_ms": 5_000,
|
||||||
|
});
|
||||||
|
mount_sse_sequence(
|
||||||
|
harness.server(),
|
||||||
|
vec![
|
||||||
|
sse(vec![
|
||||||
|
ev_response_created("resp-1"),
|
||||||
|
ev_function_call(call_id, "exec_command", &serde_json::to_string(&arguments)?),
|
||||||
|
ev_completed("resp-1"),
|
||||||
|
]),
|
||||||
|
sse(vec![
|
||||||
|
ev_assistant_message("msg-1", "done"),
|
||||||
|
ev_completed("resp-2"),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
harness
|
||||||
|
.submit_with_permission_profile(
|
||||||
|
"inspect the shell environment",
|
||||||
|
PermissionProfile::Disabled,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let output = parse_unified_exec_output(&harness.function_call_stdout(call_id).await)?;
|
||||||
|
assert_eq!(output.output.trim(), "preserved:missing:missing");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn unified_exec_intercepts_apply_patch_exec_command() -> Result<()> {
|
async fn unified_exec_intercepts_apply_patch_exec_command() -> Result<()> {
|
||||||
skip_if_no_network!(Ok(()));
|
skip_if_no_network!(Ok(()));
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use crate::capabilities::SelectedCapabilityRoot;
|
use crate::capabilities::SelectedCapabilityRoot;
|
||||||
|
use crate::config_types::ShellEnvironmentPolicy;
|
||||||
use crate::models::PermissionProfileSnapshot;
|
use crate::models::PermissionProfileSnapshot;
|
||||||
|
|
||||||
/// Configuration supplied for a thread's selected environment.
|
/// Configuration supplied for a thread's selected environment.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum EnvironmentConfigState {
|
pub enum EnvironmentConfigState {
|
||||||
/// Preserve the existing thread-derived environment configuration.
|
/// Preserve the existing thread-derived environment configuration.
|
||||||
FromThread,
|
FromThread,
|
||||||
@@ -15,12 +17,26 @@ pub enum EnvironmentConfigState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolved configuration for a thread/environment attachment.
|
/// Resolved configuration for a thread/environment attachment.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct EnvironmentConfig {
|
pub struct EnvironmentConfig {
|
||||||
/// Whether shell tools may start login shells in this environment.
|
/// Whether shell tools may start login shells in this environment.
|
||||||
pub allow_login_shell: bool,
|
pub allow_login_shell: bool,
|
||||||
/// Resolved permissions for this thread's environment attachment.
|
/// Resolved permissions for this thread's environment attachment.
|
||||||
pub permission_profile: PermissionProfileSnapshot,
|
pub permission_profile: PermissionProfileSnapshot,
|
||||||
|
/// Controls which environment variables shell commands may inherit.
|
||||||
|
pub shell_environment_policy: ShellEnvironmentPolicy,
|
||||||
/// Capability roots selected for this thread's environment attachment.
|
/// Capability roots selected for this thread's environment attachment.
|
||||||
pub selected_capability_roots: Vec<SelectedCapabilityRoot>,
|
pub selected_capability_roots: Vec<SelectedCapabilityRoot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for EnvironmentConfig {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
formatter
|
||||||
|
.debug_struct("EnvironmentConfig")
|
||||||
|
.field("allow_login_shell", &self.allow_login_shell)
|
||||||
|
.field("permission_profile", &self.permission_profile)
|
||||||
|
.field("shell_environment_policy", &"<redacted>")
|
||||||
|
.field("selected_capability_roots", &self.selected_capability_roots)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user