Refactor execpolicy fallback evaluation (#7544)

## Refactor of the `execpolicy` crate

To illustrate why we need this refactor, consider an agent attempting to
run `apple | rm -rf ./`. Suppose `apple` is allowed by `execpolicy`.
Before this PR, `execpolicy` would consider `apple` and `pear` and only
render one rule match: `Allow`. We would skip any heuristics checks on
`rm -rf ./` and immediately approve `apple | rm -rf ./` to run.

To fix this, we now thread a `fallback` evaluation function into
`execpolicy` that runs when no `execpolicy` rules match a given command.
In our example, we would run `fallback` on `rm -rf ./` and prevent
`apple | rm -rf ./` from being run without approval.
This commit is contained in:
zhao-oai
2025-12-04 02:39:48 -05:00
committed by GitHub
parent e925a380dc
commit 3d35cb4619
27 changed files with 538 additions and 257 deletions

View File

@@ -23,6 +23,7 @@ use codex_core::protocol::ExecApprovalRequestEvent;
use codex_core::protocol::ExecCommandBeginEvent;
use codex_core::protocol::ExecCommandEndEvent;
use codex_core::protocol::ExecCommandSource;
use codex_core::protocol::ExecPolicyAmendment;
use codex_core::protocol::ExitedReviewModeEvent;
use codex_core::protocol::FileChange;
use codex_core::protocol::Op;
@@ -688,7 +689,7 @@ fn exec_approval_emits_proposed_command_and_decision_history() {
"this is a test reason such as one that would be produced by the model".into(),
),
risk: None,
allow_prefix: None,
proposed_execpolicy_amendment: None,
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
@@ -733,7 +734,7 @@ fn exec_approval_decision_truncates_multiline_and_long_commands() {
"this is a test reason such as one that would be produced by the model".into(),
),
risk: None,
allow_prefix: None,
proposed_execpolicy_amendment: None,
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
@@ -784,7 +785,7 @@ fn exec_approval_decision_truncates_multiline_and_long_commands() {
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
reason: None,
risk: None,
allow_prefix: None,
proposed_execpolicy_amendment: None,
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
@@ -1993,7 +1994,11 @@ fn approval_modal_exec_snapshot() {
"this is a test reason such as one that would be produced by the model".into(),
),
risk: None,
allow_prefix: Some(vec!["echo".into(), "hello".into(), "world".into()]),
proposed_execpolicy_amendment: Some(ExecPolicyAmendment::new(vec![
"echo".into(),
"hello".into(),
"world".into(),
])),
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
@@ -2040,7 +2045,11 @@ fn approval_modal_exec_without_reason_snapshot() {
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
reason: None,
risk: None,
allow_prefix: Some(vec!["echo".into(), "hello".into(), "world".into()]),
proposed_execpolicy_amendment: Some(ExecPolicyAmendment::new(vec![
"echo".into(),
"hello".into(),
"world".into(),
])),
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {
@@ -2254,7 +2263,10 @@ fn status_widget_and_approval_modal_snapshot() {
"this is a test reason such as one that would be produced by the model".into(),
),
risk: None,
allow_prefix: Some(vec!["echo".into(), "hello world".into()]),
proposed_execpolicy_amendment: Some(ExecPolicyAmendment::new(vec![
"echo".into(),
"hello world".into(),
])),
parsed_cmd: vec![],
};
chat.handle_codex_event(Event {