mirror of
https://github.com/openai/codex.git
synced 2026-11-08 08:37:30 +00:00
Enforce environment-specific command policies (#38942)
## What changed - Add an optional restrictive execution policy to `EnvironmentConfig` and merge it over the active command policy for shell execution and approval decisions. - Reject environment policies that contain allow rules, so environment configuration can only tighten command access. - Include the environment policy fingerprint in cached approval keys so policy changes require fresh session approvals. ## Testing - Cover restrictions overriding saved prefix approvals, policy changes invalidating session approvals, and environment overlays remaining effective when model-specific prefix filtering is active. GitOrigin-RevId: e94f877549ed5e0f577b8cb52f3c1872c58f029d
This commit is contained in:
1 parent
89e297729e
commit
e38290846c
27 files changed
+364
-17
No files matched your search
@@ -345,6 +345,7 @@ fn resolved_local_environments<const N: usize>(
|
|||||||
PermissionProfile::read_only(),
|
PermissionProfile::read_only(),
|
||||||
),
|
),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -787,6 +787,7 @@ mod tests {
|
|||||||
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(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -959,6 +960,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
vec![cwd.join("profile-root")],
|
vec![cwd.join("profile-root")],
|
||||||
),
|
),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
};
|
};
|
||||||
let turn_environments = ThreadEnvironments::new(
|
let turn_environments = ThreadEnvironments::new(
|
||||||
@@ -1161,6 +1163,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
vec![cwd.join("profile-root")],
|
vec![cwd.join("profile-root")],
|
||||||
),
|
),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
};
|
};
|
||||||
let cwd = PathUri::from_abs_path(&cwd);
|
let cwd = PathUri::from_abs_path(&cwd);
|
||||||
@@ -1495,6 +1498,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
vec![cwd.join("child-profile-root")],
|
vec![cwd.join("child-profile-root")],
|
||||||
),
|
),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
};
|
};
|
||||||
let environments = ThreadEnvironments::new(
|
let environments = ThreadEnvironments::new(
|
||||||
@@ -1539,6 +1543,7 @@ url = "ws://127.0.0.1:8765"
|
|||||||
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(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
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();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use codex_execpolicy::MatchOptions;
|
|||||||
use codex_execpolicy::NetworkRuleProtocol;
|
use codex_execpolicy::NetworkRuleProtocol;
|
||||||
use codex_execpolicy::Policy;
|
use codex_execpolicy::Policy;
|
||||||
use codex_execpolicy::PolicyParser;
|
use codex_execpolicy::PolicyParser;
|
||||||
|
use codex_execpolicy::RequirementsExecPolicy;
|
||||||
use codex_execpolicy::RuleMatch;
|
use codex_execpolicy::RuleMatch;
|
||||||
use codex_execpolicy::blocking_append_allow_prefix_rule;
|
use codex_execpolicy::blocking_append_allow_prefix_rule;
|
||||||
use codex_execpolicy::blocking_append_network_rule;
|
use codex_execpolicy::blocking_append_network_rule;
|
||||||
@@ -282,6 +283,7 @@ pub(crate) struct ExecApprovalRequest<'a> {
|
|||||||
pub(crate) command: &'a [String],
|
pub(crate) command: &'a [String],
|
||||||
pub(crate) approval_policy: AskForApproval,
|
pub(crate) approval_policy: AskForApproval,
|
||||||
pub(crate) permission_profile: PermissionProfile,
|
pub(crate) permission_profile: PermissionProfile,
|
||||||
|
pub(crate) environment_policy: Option<&'a RequirementsExecPolicy>,
|
||||||
pub(crate) windows_sandbox_level: WindowsSandboxLevel,
|
pub(crate) windows_sandbox_level: WindowsSandboxLevel,
|
||||||
pub(crate) sandbox_permissions: SandboxPermissions,
|
pub(crate) sandbox_permissions: SandboxPermissions,
|
||||||
pub(crate) prefix_rule: Option<Vec<String>>,
|
pub(crate) prefix_rule: Option<Vec<String>>,
|
||||||
@@ -317,12 +319,13 @@ impl ExecPolicyManager {
|
|||||||
command,
|
command,
|
||||||
approval_policy,
|
approval_policy,
|
||||||
permission_profile,
|
permission_profile,
|
||||||
|
environment_policy,
|
||||||
windows_sandbox_level,
|
windows_sandbox_level,
|
||||||
sandbox_permissions,
|
sandbox_permissions,
|
||||||
prefix_rule,
|
prefix_rule,
|
||||||
allow_prefix_rules,
|
allow_prefix_rules,
|
||||||
} = req;
|
} = req;
|
||||||
let exec_policy = self.current_for_prefix_rules(allow_prefix_rules);
|
let exec_policy = self.current_for_environment(environment_policy, allow_prefix_rules);
|
||||||
let ExecPolicyCommands {
|
let ExecPolicyCommands {
|
||||||
commands,
|
commands,
|
||||||
used_complex_parsing,
|
used_complex_parsing,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use super::ExecPolicyManager;
|
|||||||
use codex_execpolicy::Decision;
|
use codex_execpolicy::Decision;
|
||||||
use codex_execpolicy::Policy;
|
use codex_execpolicy::Policy;
|
||||||
use codex_execpolicy::PrefixRule;
|
use codex_execpolicy::PrefixRule;
|
||||||
|
use codex_execpolicy::RequirementsExecPolicy;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
@@ -11,6 +12,18 @@ pub(crate) enum AllowPrefixRules {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ExecPolicyManager {
|
impl ExecPolicyManager {
|
||||||
|
pub(crate) fn current_for_environment(
|
||||||
|
&self,
|
||||||
|
environment_policy: Option<&RequirementsExecPolicy>,
|
||||||
|
allow_prefix_rules: AllowPrefixRules,
|
||||||
|
) -> Arc<Policy> {
|
||||||
|
let policy = self.current_for_prefix_rules(allow_prefix_rules);
|
||||||
|
match environment_policy {
|
||||||
|
Some(environment_policy) => Arc::new(policy.merge_overlay(environment_policy.as_ref())),
|
||||||
|
None => policy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn current_for_prefix_rules(
|
pub(crate) fn current_for_prefix_rules(
|
||||||
&self,
|
&self,
|
||||||
allow_prefix_rules: AllowPrefixRules,
|
allow_prefix_rules: AllowPrefixRules,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use codex_execpolicy::Decision;
|
|||||||
use codex_execpolicy::MatchOptions;
|
use codex_execpolicy::MatchOptions;
|
||||||
use codex_execpolicy::Policy;
|
use codex_execpolicy::Policy;
|
||||||
use codex_execpolicy::PolicyParser;
|
use codex_execpolicy::PolicyParser;
|
||||||
|
use codex_execpolicy::RequirementsExecPolicy;
|
||||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
@@ -86,6 +87,35 @@ fn cyber_policy_filters_allow_prefixes_but_preserves_restrictive_and_network_rul
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn environment_restrictions_apply_after_model_prefix_filtering() {
|
||||||
|
let (thread_policy, _) = policy_with_broad_allow_prefix();
|
||||||
|
let manager = ExecPolicyManager::new(thread_policy);
|
||||||
|
let mut environment_policy = Policy::empty();
|
||||||
|
environment_policy
|
||||||
|
.add_prefix_rule(
|
||||||
|
&["cargo".to_string(), "install".to_string()],
|
||||||
|
Decision::Forbidden,
|
||||||
|
)
|
||||||
|
.expect("add environment restriction");
|
||||||
|
let environment_policy = RequirementsExecPolicy::new(environment_policy);
|
||||||
|
let command = vec!["cargo".to_string(), "install".to_string()];
|
||||||
|
|
||||||
|
for allow_prefix_rules in [
|
||||||
|
AllowPrefixRules::Honor,
|
||||||
|
AllowPrefixRules::IgnoreForCyberModel,
|
||||||
|
] {
|
||||||
|
let policy = manager.current_for_environment(Some(&environment_policy), allow_prefix_rules);
|
||||||
|
assert_eq!(
|
||||||
|
policy.check(&command, &|_| Decision::Allow).decision,
|
||||||
|
Decision::Forbidden,
|
||||||
|
);
|
||||||
|
if allow_prefix_rules == AllowPrefixRules::IgnoreForCyberModel {
|
||||||
|
assert!(policy.get_allowed_prefixes().is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn cyber_policy_requires_approval_for_broad_wrapped_and_resolved_prefixes() {
|
async fn cyber_policy_requires_approval_for_broad_wrapped_and_resolved_prefixes() {
|
||||||
let (policy, program_path) = policy_with_broad_allow_prefix();
|
let (policy, program_path) = policy_with_broad_allow_prefix();
|
||||||
@@ -110,6 +140,7 @@ async fn cyber_policy_requires_approval_for_broad_wrapped_and_resolved_prefixes(
|
|||||||
command: &command,
|
command: &command,
|
||||||
approval_policy: AskForApproval::OnRequest,
|
approval_policy: AskForApproval::OnRequest,
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: SandboxPermissions::RequireEscalated,
|
sandbox_permissions: SandboxPermissions::RequireEscalated,
|
||||||
prefix_rule: Some(vec!["cargo".to_string(), "install".to_string()]),
|
prefix_rule: Some(vec!["cargo".to_string(), "install".to_string()]),
|
||||||
@@ -142,6 +173,7 @@ async fn cyber_policy_keeps_heuristically_safe_commands_inside_the_sandbox() {
|
|||||||
command: &command,
|
command: &command,
|
||||||
approval_policy: AskForApproval::OnRequest,
|
approval_policy: AskForApproval::OnRequest,
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
@@ -161,6 +193,7 @@ async fn cyber_policy_keeps_heuristically_safe_commands_inside_the_sandbox() {
|
|||||||
command: &command,
|
command: &command,
|
||||||
approval_policy: AskForApproval::OnRequest,
|
approval_policy: AskForApproval::OnRequest,
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
|
|||||||
@@ -1389,6 +1389,7 @@ async fn mixed_rule_and_sandbox_prompt_prioritizes_rule_for_rejection_decision()
|
|||||||
mcp_elicitations: true,
|
mcp_elicitations: true,
|
||||||
}),
|
}),
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: SandboxPermissions::RequireEscalated,
|
sandbox_permissions: SandboxPermissions::RequireEscalated,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
@@ -1427,6 +1428,7 @@ async fn forced_rm_preserves_rule_rejection_when_granular_rules_are_disabled() {
|
|||||||
mcp_elicitations: true,
|
mcp_elicitations: true,
|
||||||
}),
|
}),
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: SandboxPermissions::RequireEscalated,
|
sandbox_permissions: SandboxPermissions::RequireEscalated,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
@@ -1452,6 +1454,7 @@ async fn exec_approval_requirement_falls_back_to_heuristics() {
|
|||||||
command: &command,
|
command: &command,
|
||||||
approval_policy: AskForApproval::UnlessTrusted,
|
approval_policy: AskForApproval::UnlessTrusted,
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
@@ -1478,6 +1481,7 @@ async fn empty_bash_lc_script_falls_back_to_original_command() {
|
|||||||
command: &command,
|
command: &command,
|
||||||
approval_policy: AskForApproval::UnlessTrusted,
|
approval_policy: AskForApproval::UnlessTrusted,
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
@@ -1508,6 +1512,7 @@ async fn whitespace_bash_lc_script_falls_back_to_original_command() {
|
|||||||
command: &command,
|
command: &command,
|
||||||
approval_policy: AskForApproval::UnlessTrusted,
|
approval_policy: AskForApproval::UnlessTrusted,
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
@@ -1538,6 +1543,7 @@ async fn request_rule_uses_prefix_rule() {
|
|||||||
command: &command,
|
command: &command,
|
||||||
approval_policy: AskForApproval::OnRequest,
|
approval_policy: AskForApproval::OnRequest,
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: SandboxPermissions::RequireEscalated,
|
sandbox_permissions: SandboxPermissions::RequireEscalated,
|
||||||
prefix_rule: Some(vec!["cargo".to_string(), "install".to_string()]),
|
prefix_rule: Some(vec!["cargo".to_string(), "install".to_string()]),
|
||||||
@@ -1571,6 +1577,7 @@ async fn request_rule_falls_back_when_prefix_rule_does_not_approve_all_commands(
|
|||||||
command: &command,
|
command: &command,
|
||||||
approval_policy: AskForApproval::OnRequest,
|
approval_policy: AskForApproval::OnRequest,
|
||||||
permission_profile: PermissionProfile::Disabled,
|
permission_profile: PermissionProfile::Disabled,
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: SandboxPermissions::RequireEscalated,
|
sandbox_permissions: SandboxPermissions::RequireEscalated,
|
||||||
prefix_rule: Some(vec!["cargo".to_string(), "install".to_string()]),
|
prefix_rule: Some(vec!["cargo".to_string(), "install".to_string()]),
|
||||||
@@ -1611,6 +1618,7 @@ async fn heuristics_apply_when_other_commands_match_policy() {
|
|||||||
command: &command,
|
command: &command,
|
||||||
approval_policy: AskForApproval::UnlessTrusted,
|
approval_policy: AskForApproval::UnlessTrusted,
|
||||||
permission_profile: PermissionProfile::Disabled,
|
permission_profile: PermissionProfile::Disabled,
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
@@ -2090,6 +2098,7 @@ async fn forced_rm_requires_approval_or_specific_rejection_on_all_platforms() {
|
|||||||
command: &dangerous_command,
|
command: &dangerous_command,
|
||||||
approval_policy: AskForApproval::OnRequest,
|
approval_policy: AskForApproval::OnRequest,
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: permissions,
|
sandbox_permissions: permissions,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
@@ -2112,6 +2121,7 @@ async fn forced_rm_requires_approval_or_specific_rejection_on_all_platforms() {
|
|||||||
command: &dangerous_command,
|
command: &dangerous_command,
|
||||||
approval_policy: AskForApproval::Never,
|
approval_policy: AskForApproval::Never,
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: permissions,
|
sandbox_permissions: permissions,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
@@ -2171,6 +2181,7 @@ async fn verify_approval_requirement_for_unsafe_powershell_command() {
|
|||||||
command: &sneaky_command,
|
command: &sneaky_command,
|
||||||
approval_policy: AskForApproval::OnRequest,
|
approval_policy: AskForApproval::OnRequest,
|
||||||
permission_profile: PermissionProfile::read_only(),
|
permission_profile: PermissionProfile::read_only(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||||
sandbox_permissions: permissions,
|
sandbox_permissions: permissions,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
@@ -2264,6 +2275,7 @@ async fn exec_approval_requirement_for_command(
|
|||||||
command: &command,
|
command: &command,
|
||||||
approval_policy,
|
approval_policy,
|
||||||
permission_profile,
|
permission_profile,
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
|
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
|
||||||
sandbox_permissions,
|
sandbox_permissions,
|
||||||
prefix_rule,
|
prefix_rule,
|
||||||
|
|||||||
@@ -1121,6 +1121,7 @@ async fn mcp_sandbox_cwd_uses_matching_server_environment_uri() -> anyhow::Resul
|
|||||||
.permission_profile_state()
|
.permission_profile_state()
|
||||||
.snapshot(),
|
.snapshot(),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,6 +39,15 @@ fn validate_environment_config(
|
|||||||
"environment readiness contains more than {MAX_SELECTED_CAPABILITY_ROOTS} selected capability roots"
|
"environment readiness contains more than {MAX_SELECTED_CAPABILITY_ROOTS} selected capability roots"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
if config
|
||||||
|
.exec_policy
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|policy| !policy.as_ref().get_allowed_prefixes().is_empty())
|
||||||
|
{
|
||||||
|
return Err(CodexErr::InvalidRequest(
|
||||||
|
"environment command policy cannot contain allow rules".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let mut root_ids = HashSet::with_capacity(config.selected_capability_roots.len());
|
let mut root_ids = HashSet::with_capacity(config.selected_capability_roots.len());
|
||||||
for root in &config.selected_capability_roots {
|
for root in &config.selected_capability_roots {
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ impl SessionConfiguration {
|
|||||||
allow_login_shell: self.allow_login_shell,
|
allow_login_shell: self.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(),
|
shell_environment_policy: self.shell_environment_policy.clone(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11328,6 +11328,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
|
|||||||
command: &command,
|
command: &command,
|
||||||
approval_policy: turn_context.approval_policy(),
|
approval_policy: turn_context.approval_policy(),
|
||||||
permission_profile: turn_context.permission_profile(),
|
permission_profile: turn_context.permission_profile(),
|
||||||
|
environment_policy: None,
|
||||||
windows_sandbox_level: turn_context.windows_sandbox_level,
|
windows_sandbox_level: turn_context.windows_sandbox_level,
|
||||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||||
prefix_rule: None,
|
prefix_rule: None,
|
||||||
|
|||||||
@@ -703,7 +703,19 @@ impl Session {
|
|||||||
.clone()
|
.clone()
|
||||||
.or_else(|| ctx.approval_reason.clone())
|
.or_else(|| ctx.approval_reason.clone())
|
||||||
.or_else(|| justification.clone());
|
.or_else(|| justification.clone());
|
||||||
with_cached_approval(&self.services, tool_name, action.cache_keys(), || async {
|
let policy_fingerprint = ctx
|
||||||
|
.review_context
|
||||||
|
.environments()
|
||||||
|
.turn_environments()
|
||||||
|
.find(|environment| environment.selection.environment_id == *environment_id)
|
||||||
|
.and_then(|environment| environment.config().exec_policy.as_ref())
|
||||||
|
.map(codex_execpolicy::RequirementsExecPolicy::fingerprint);
|
||||||
|
let cache_keys = action
|
||||||
|
.cache_keys()
|
||||||
|
.into_iter()
|
||||||
|
.map(|key| (key, &policy_fingerprint))
|
||||||
|
.collect();
|
||||||
|
with_cached_approval(&self.services, tool_name, cache_keys, || async {
|
||||||
self.request_command_approval(
|
self.request_command_approval(
|
||||||
ctx.review_context.turn(),
|
ctx.review_context.turn(),
|
||||||
ctx.call_id.clone(),
|
ctx.call_id.clone(),
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ async fn run_exec_like(args: RunExecLikeArgs) -> Result<FunctionToolOutput, Func
|
|||||||
command: &exec_params.command,
|
command: &exec_params.command,
|
||||||
approval_policy: turn.approval_policy(),
|
approval_policy: turn.approval_policy(),
|
||||||
permission_profile: turn_environment.permission_profile().clone(),
|
permission_profile: turn_environment.permission_profile().clone(),
|
||||||
|
environment_policy: turn_environment.config().exec_policy.as_ref(),
|
||||||
windows_sandbox_level: turn.windows_sandbox_level,
|
windows_sandbox_level: turn.windows_sandbox_level,
|
||||||
sandbox_permissions: if effective_additional_permissions.permissions_preapproved {
|
sandbox_permissions: if effective_additional_permissions.permissions_preapproved {
|
||||||
codex_protocol::models::SandboxPermissions::UseDefault
|
codex_protocol::models::SandboxPermissions::UseDefault
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ async fn shell_command_handler_to_exec_params_uses_selected_environment() {
|
|||||||
active_permission_profile.clone(),
|
active_permission_profile.clone(),
|
||||||
),
|
),
|
||||||
shell_environment_policy: selected_shell_environment_policy.clone(),
|
shell_environment_policy: selected_shell_environment_policy.clone(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ fn test_turn_environment(environment_id: &str) -> crate::session::turn_context::
|
|||||||
PermissionProfile::read_only(),
|
PermissionProfile::read_only(),
|
||||||
),
|
),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -183,7 +183,10 @@ pub(super) async fn try_run_zsh_fork(
|
|||||||
ctx.session
|
ctx.session
|
||||||
.services
|
.services
|
||||||
.exec_policy
|
.exec_policy
|
||||||
.current_for_prefix_rules(ctx.step_context.turn.allow_prefix_rules())
|
.current_for_environment(
|
||||||
|
req.turn_environment.config().exec_policy.as_ref(),
|
||||||
|
ctx.step_context.turn.allow_prefix_rules(),
|
||||||
|
)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.clone(),
|
.clone(),
|
||||||
));
|
));
|
||||||
@@ -297,7 +300,10 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
|
|||||||
ctx.session
|
ctx.session
|
||||||
.services
|
.services
|
||||||
.exec_policy
|
.exec_policy
|
||||||
.current_for_prefix_rules(ctx.step_context.turn.allow_prefix_rules())
|
.current_for_environment(
|
||||||
|
req.turn_environment.config().exec_policy.as_ref(),
|
||||||
|
ctx.step_context.turn.allow_prefix_rules(),
|
||||||
|
)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.clone(),
|
.clone(),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ async fn approval_key_uses_path_uri_and_includes_environment_id() {
|
|||||||
PermissionProfile::read_only(),
|
PermissionProfile::read_only(),
|
||||||
),
|
),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -543,6 +543,7 @@ mod tests {
|
|||||||
PermissionProfile::read_only(),
|
PermissionProfile::read_only(),
|
||||||
),
|
),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -917,6 +917,7 @@ async fn zsh_fork_unified_exec_keeps_shell_parameter_when_remote_environment_ava
|
|||||||
.permission_profile_state()
|
.permission_profile_state()
|
||||||
.snapshot(),
|
.snapshot(),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1246,6 +1246,7 @@ impl UnifiedExecProcessManager {
|
|||||||
command: &request.command,
|
command: &request.command,
|
||||||
approval_policy: turn.approval_policy(),
|
approval_policy: turn.approval_policy(),
|
||||||
permission_profile: request.turn_environment.permission_profile().clone(),
|
permission_profile: request.turn_environment.permission_profile().clone(),
|
||||||
|
environment_policy: request.turn_environment.config().exec_policy.as_ref(),
|
||||||
windows_sandbox_level: turn.windows_sandbox_level,
|
windows_sandbox_level: turn.windows_sandbox_level,
|
||||||
sandbox_permissions: if request.additional_permissions_preapproved {
|
sandbox_permissions: if request.additional_permissions_preapproved {
|
||||||
crate::sandboxing::SandboxPermissions::UseDefault
|
crate::sandboxing::SandboxPermissions::UseDefault
|
||||||
|
|||||||
@@ -2,12 +2,20 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use codex_config::test_support::CloudConfigBundleFixture;
|
use codex_config::test_support::CloudConfigBundleFixture;
|
||||||
|
use codex_core::EnvironmentConfig;
|
||||||
use codex_core::TurnInputRequest;
|
use codex_core::TurnInputRequest;
|
||||||
|
use codex_core::config::Constrained;
|
||||||
|
use codex_execpolicy::Decision;
|
||||||
|
use codex_execpolicy::Policy;
|
||||||
|
use codex_execpolicy::RequirementsExecPolicy;
|
||||||
use codex_features::Feature;
|
use codex_features::Feature;
|
||||||
|
use codex_protocol::config_types::ApprovalsReviewer;
|
||||||
use codex_protocol::config_types::CollaborationMode;
|
use codex_protocol::config_types::CollaborationMode;
|
||||||
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::error::CodexErrorDetails;
|
||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
|
use codex_protocol::models::PermissionProfileSnapshot;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::GranularApprovalConfig;
|
use codex_protocol::protocol::GranularApprovalConfig;
|
||||||
@@ -524,6 +532,227 @@ prefix_rules = [
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn environment_command_restrictions_override_saved_prefix_approvals() -> Result<()> {
|
||||||
|
skip_if_target_windows!(
|
||||||
|
Ok(()),
|
||||||
|
"managed prefix fixture uses POSIX executable semantics"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut builder = test_codex().with_config(|config| {
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::UnifiedExec)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
let policy_path = config.codex_home.join("rules").join("approved.rules");
|
||||||
|
fs::create_dir_all(policy_path.parent().expect("rules directory"))
|
||||||
|
.expect("create rules directory");
|
||||||
|
fs::write(
|
||||||
|
policy_path,
|
||||||
|
r#"prefix_rule(pattern=["echo"], decision="allow")"#,
|
||||||
|
)
|
||||||
|
.expect("write approved command prefix");
|
||||||
|
});
|
||||||
|
let server = start_mock_server().await;
|
||||||
|
let test = builder.build_with_auto_env(&server).await?;
|
||||||
|
let selection = test
|
||||||
|
.codex
|
||||||
|
.environment_selections()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.expect("thread should select its executor environment");
|
||||||
|
let mut invalid_policy = Policy::empty();
|
||||||
|
invalid_policy.add_prefix_rule(&["echo".to_string()], Decision::Allow)?;
|
||||||
|
let error = test
|
||||||
|
.codex
|
||||||
|
.environment_ready(
|
||||||
|
&selection,
|
||||||
|
EnvironmentConfig {
|
||||||
|
allow_login_shell: true,
|
||||||
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: Some(RequirementsExecPolicy::new(invalid_policy)),
|
||||||
|
selected_capability_roots: Vec::new(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect_err("environment policies must not introduce command allowances");
|
||||||
|
assert!(matches!(
|
||||||
|
error.details(),
|
||||||
|
CodexErrorDetails::InvalidRequest(message)
|
||||||
|
if message == "environment command policy cannot contain allow rules"
|
||||||
|
));
|
||||||
|
|
||||||
|
let mut environment_policy = Policy::empty();
|
||||||
|
environment_policy.add_prefix_rule(&["echo".to_string()], Decision::Forbidden)?;
|
||||||
|
test.codex
|
||||||
|
.environment_ready(
|
||||||
|
&selection,
|
||||||
|
EnvironmentConfig {
|
||||||
|
allow_login_shell: true,
|
||||||
|
permission_profile: PermissionProfileSnapshot::legacy(PermissionProfile::Disabled),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: Some(RequirementsExecPolicy::new(environment_policy)),
|
||||||
|
selected_capability_roots: Vec::new(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let call_id = "environment-managed-command";
|
||||||
|
let args = json!({
|
||||||
|
"cmd": "echo blocked",
|
||||||
|
"yield_time_ms": 1_000,
|
||||||
|
});
|
||||||
|
mount_sse_once(
|
||||||
|
&server,
|
||||||
|
sse(vec![
|
||||||
|
ev_response_created("resp-environment-managed-1"),
|
||||||
|
ev_function_call(call_id, "exec_command", &serde_json::to_string(&args)?),
|
||||||
|
ev_completed("resp-environment-managed-1"),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let results_mock = mount_sse_once(
|
||||||
|
&server,
|
||||||
|
sse(vec![
|
||||||
|
ev_assistant_message("msg-environment-managed", "done"),
|
||||||
|
ev_completed("resp-environment-managed-2"),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
test.submit_turn_with_approval_and_permission_profile(
|
||||||
|
"run shell command",
|
||||||
|
AskForApproval::Never,
|
||||||
|
PermissionProfile::Disabled,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let output_item = results_mock.single_request().function_call_output(call_id);
|
||||||
|
let output = output_item
|
||||||
|
.get("output")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.expect("function call output should include a string output payload");
|
||||||
|
assert!(
|
||||||
|
output.contains("policy forbids commands starting with `echo`"),
|
||||||
|
"unexpected output: {output}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn environment_command_policy_changes_invalidate_session_approvals() -> Result<()> {
|
||||||
|
skip_if_target_windows!(
|
||||||
|
Ok(()),
|
||||||
|
"managed prefix fixture uses POSIX executable semantics"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut builder = test_codex().with_config(|config| {
|
||||||
|
config
|
||||||
|
.features
|
||||||
|
.enable(Feature::UnifiedExec)
|
||||||
|
.expect("test config should allow feature update");
|
||||||
|
config.permissions.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||||
|
config
|
||||||
|
.permissions
|
||||||
|
.set_permission_profile(PermissionProfile::Disabled)
|
||||||
|
.expect("test config should allow unrestricted permissions");
|
||||||
|
config.approvals_reviewer = ApprovalsReviewer::User;
|
||||||
|
let policy_path = config.codex_home.join("rules").join("prompt.rules");
|
||||||
|
fs::create_dir_all(policy_path.parent().expect("rules directory"))
|
||||||
|
.expect("create rules directory");
|
||||||
|
fs::write(
|
||||||
|
policy_path,
|
||||||
|
r#"prefix_rule(pattern=["echo"], decision="prompt")"#,
|
||||||
|
)
|
||||||
|
.expect("write prompt command prefix");
|
||||||
|
});
|
||||||
|
let server = start_mock_server().await;
|
||||||
|
let test = builder.build_with_auto_env(&server).await?;
|
||||||
|
let selection = test
|
||||||
|
.codex
|
||||||
|
.environment_selections()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.expect("thread should select its executor environment");
|
||||||
|
|
||||||
|
for (attempt, decision) in [
|
||||||
|
("before-owner-policy", ReviewDecision::ApprovedForSession),
|
||||||
|
("after-owner-policy", ReviewDecision::Approved),
|
||||||
|
] {
|
||||||
|
if attempt == "after-owner-policy" {
|
||||||
|
let mut policy = Policy::empty();
|
||||||
|
policy.add_prefix_rule(&["echo".to_string()], Decision::Prompt)?;
|
||||||
|
test.codex
|
||||||
|
.environment_ready(
|
||||||
|
&selection,
|
||||||
|
EnvironmentConfig {
|
||||||
|
allow_login_shell: true,
|
||||||
|
permission_profile: PermissionProfileSnapshot::legacy(
|
||||||
|
PermissionProfile::Disabled,
|
||||||
|
),
|
||||||
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: Some(RequirementsExecPolicy::new(policy)),
|
||||||
|
selected_capability_roots: Vec::new(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = json!({ "cmd": "echo approval", "yield_time_ms": 1_000 });
|
||||||
|
mount_sse_once(
|
||||||
|
&server,
|
||||||
|
sse(vec![
|
||||||
|
ev_response_created(attempt),
|
||||||
|
ev_function_call(attempt, "exec_command", &serde_json::to_string(&args)?),
|
||||||
|
ev_completed(attempt),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
mount_sse_once(
|
||||||
|
&server,
|
||||||
|
sse(vec![
|
||||||
|
ev_assistant_message(attempt, "done"),
|
||||||
|
ev_completed(attempt),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
test.codex
|
||||||
|
.start_or_steer_turn(TurnInputRequest::user_input(vec![UserInput::Text {
|
||||||
|
text: attempt.into(),
|
||||||
|
text_elements: Vec::new(),
|
||||||
|
}]))
|
||||||
|
.await?;
|
||||||
|
let event = wait_for_event(&test.codex, |event| {
|
||||||
|
matches!(
|
||||||
|
event,
|
||||||
|
EventMsg::ExecApprovalRequest(_) | EventMsg::TurnComplete(_)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
let EventMsg::ExecApprovalRequest(approval) = event else {
|
||||||
|
panic!("expected a fresh command approval {attempt}");
|
||||||
|
};
|
||||||
|
test.codex
|
||||||
|
.submit(Op::ExecApproval {
|
||||||
|
id: approval.effective_approval_id(),
|
||||||
|
turn_id: None,
|
||||||
|
decision,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
wait_for_event(&test.codex, |event| {
|
||||||
|
matches!(event, EventMsg::TurnComplete(_))
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn shell_command_empty_script_with_collaboration_mode_does_not_panic() -> Result<()> {
|
async fn shell_command_empty_script_with_collaboration_mode_does_not_panic() -> Result<()> {
|
||||||
let server = start_mock_server().await;
|
let server = start_mock_server().await;
|
||||||
|
|||||||
@@ -598,6 +598,7 @@ async fn guardian_session_is_reused_for_consecutive_tool_reviews_without_prewarm
|
|||||||
test.config.permissions.permission_profile().clone(),
|
test.config.permissions.permission_profile().clone(),
|
||||||
),
|
),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
};
|
};
|
||||||
parent_environments
|
parent_environments
|
||||||
|
|||||||
@@ -483,6 +483,7 @@ async fn root_reconciliation_reuses_pending_apps_startup() -> Result<()> {
|
|||||||
test.config.permissions.permission_profile().clone(),
|
test.config.permissions.permission_profile().clone(),
|
||||||
),
|
),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
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 {
|
||||||
|
|||||||
@@ -551,6 +551,7 @@ async fn environment_permissions_follow_configuration_ownership() -> Result<()>
|
|||||||
PermissionProfile::read_only(),
|
PermissionProfile::read_only(),
|
||||||
),
|
),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
}),
|
}),
|
||||||
..selection
|
..selection
|
||||||
@@ -1173,6 +1174,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: vec![root("duplicate"), root("duplicate")],
|
selected_capability_roots: vec![root("duplicate"), root("duplicate")],
|
||||||
}),
|
}),
|
||||||
] {
|
] {
|
||||||
@@ -1208,6 +1210,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
allow_login_shell: true,
|
allow_login_shell: true,
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: vec![root("startup-root"), root("second-root")],
|
selected_capability_roots: vec![root("startup-root"), root("second-root")],
|
||||||
}),
|
}),
|
||||||
..selection.clone()
|
..selection.clone()
|
||||||
@@ -1227,6 +1230,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: vec![root("first-root")],
|
selected_capability_roots: vec![root("first-root")],
|
||||||
}),
|
}),
|
||||||
..selection.clone()
|
..selection.clone()
|
||||||
@@ -1285,6 +1289,7 @@ async fn shared_executor_keeps_ready_capability_roots_scoped_to_each_attachment(
|
|||||||
allow_login_shell: false,
|
allow_login_shell: false,
|
||||||
permission_profile: permission_profile.clone(),
|
permission_profile: permission_profile.clone(),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: vec![root("first-updated-root")],
|
selected_capability_roots: vec![root("first-updated-root")],
|
||||||
}),
|
}),
|
||||||
..selection.clone()
|
..selection.clone()
|
||||||
@@ -1381,6 +1386,7 @@ async fn pending_attachment_installs_configuration_before_waiting_turn_resumes()
|
|||||||
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(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: vec![root(id)],
|
selected_capability_roots: vec![root(id)],
|
||||||
};
|
};
|
||||||
let start_pending_thread = || {
|
let start_pending_thread = || {
|
||||||
@@ -1697,6 +1703,7 @@ async fn ready_before_selection_exposes_remote_tools_and_capability_context_afte
|
|||||||
test.config.permissions.permission_profile().clone(),
|
test.config.permissions.permission_profile().clone(),
|
||||||
),
|
),
|
||||||
shell_environment_policy: Default::default(),
|
shell_environment_policy: Default::default(),
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: vec![ready_root],
|
selected_capability_roots: vec![ready_root],
|
||||||
}),
|
}),
|
||||||
}]),
|
}]),
|
||||||
|
|||||||
@@ -355,6 +355,7 @@ async fn exec_command_uses_installed_environment_shell_policy_with_explicit_over
|
|||||||
.clone(),
|
.clone(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
exec_policy: None,
|
||||||
selected_capability_roots: Vec::new(),
|
selected_capability_roots: Vec::new(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -41,11 +41,22 @@ impl RequirementsExecPolicy {
|
|||||||
pub fn new(policy: Policy) -> Self {
|
pub fn new(policy: Policy) -> Self {
|
||||||
Self { policy }
|
Self { policy }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fingerprint(&self) -> Vec<String> {
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
for (program, rules) in self.policy.rules().iter_all() {
|
||||||
|
for rule in rules {
|
||||||
|
entries.push(format!("{program}:{rule:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entries.sort();
|
||||||
|
entries
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for RequirementsExecPolicy {
|
impl PartialEq for RequirementsExecPolicy {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
policy_fingerprint(&self.policy) == policy_fingerprint(&other.policy)
|
self.fingerprint() == other.fingerprint()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,17 +68,6 @@ impl AsRef<Policy> for RequirementsExecPolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn policy_fingerprint(policy: &Policy) -> Vec<String> {
|
|
||||||
let mut entries = Vec::new();
|
|
||||||
for (program, rules) in policy.rules().iter_all() {
|
|
||||||
for rule in rules {
|
|
||||||
entries.push(format!("{program}:{rule:?}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
entries.sort();
|
|
||||||
entries
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Policy {
|
impl Policy {
|
||||||
pub fn new(rules_by_program: MultiMap<String, RuleRef>) -> Self {
|
pub fn new(rules_by_program: MultiMap<String, RuleRef>) -> Self {
|
||||||
Self::from_parts(rules_by_program, Vec::new(), HashMap::new())
|
Self::from_parts(rules_by_program, Vec::new(), HashMap::new())
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ workspace = true
|
|||||||
chardetng = { workspace = true }
|
chardetng = { workspace = true }
|
||||||
chrono = { workspace = true, features = ["serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
codex-async-utils = { workspace = true }
|
codex-async-utils = { workspace = true }
|
||||||
|
codex-execpolicy = { workspace = true }
|
||||||
codex-extension-items = { workspace = true }
|
codex-extension-items = { workspace = true }
|
||||||
codex-http-client = { workspace = true }
|
codex-http-client = { workspace = true }
|
||||||
codex-network-proxy = { workspace = true }
|
codex-network-proxy = { workspace = true }
|
||||||
@@ -54,7 +55,6 @@ seccompiler = { workspace = true }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
codex-execpolicy = { workspace = true }
|
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::capabilities::SelectedCapabilityRoot;
|
use crate::capabilities::SelectedCapabilityRoot;
|
||||||
use crate::config_types::ShellEnvironmentPolicy;
|
use crate::config_types::ShellEnvironmentPolicy;
|
||||||
use crate::models::PermissionProfileSnapshot;
|
use crate::models::PermissionProfileSnapshot;
|
||||||
|
use codex_execpolicy::RequirementsExecPolicy;
|
||||||
|
|
||||||
/// Configuration supplied for a thread's selected environment.
|
/// Configuration supplied for a thread's selected environment.
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
@@ -25,6 +26,8 @@ pub struct EnvironmentConfig {
|
|||||||
pub permission_profile: PermissionProfileSnapshot,
|
pub permission_profile: PermissionProfileSnapshot,
|
||||||
/// Controls which environment variables shell commands may inherit.
|
/// Controls which environment variables shell commands may inherit.
|
||||||
pub shell_environment_policy: ShellEnvironmentPolicy,
|
pub shell_environment_policy: ShellEnvironmentPolicy,
|
||||||
|
/// Additional managed command restrictions for this environment attachment.
|
||||||
|
pub exec_policy: Option<RequirementsExecPolicy>,
|
||||||
/// 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>,
|
||||||
}
|
}
|
||||||
@@ -36,6 +39,7 @@ impl std::fmt::Debug for EnvironmentConfig {
|
|||||||
.field("allow_login_shell", &self.allow_login_shell)
|
.field("allow_login_shell", &self.allow_login_shell)
|
||||||
.field("permission_profile", &self.permission_profile)
|
.field("permission_profile", &self.permission_profile)
|
||||||
.field("shell_environment_policy", &"<redacted>")
|
.field("shell_environment_policy", &"<redacted>")
|
||||||
|
.field("exec_policy", &self.exec_policy)
|
||||||
.field("selected_capability_roots", &self.selected_capability_roots)
|
.field("selected_capability_roots", &self.selected_capability_roots)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user