mirror of
https://github.com/openai/codex.git
synced 2026-09-10 20:26:47 +00:00
Merge aabfed7d15 into sapling-pr-archive-bolinfest
This commit is contained in:
@@ -3235,6 +3235,151 @@ async fn approving_fallback_rule_for_compound_command_works() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
#[cfg(unix)]
|
||||
async fn permissions_scoped_prefix_rule_uses_active_profile_in_shell_flow() -> Result<()> {
|
||||
let server = start_mock_server().await;
|
||||
let home = Arc::new(TempDir::new()?);
|
||||
fs::write(
|
||||
home.path().join("config.toml"),
|
||||
r#"default_permissions = "project_profile"
|
||||
|
||||
[permissions.project_profile.filesystem]
|
||||
":minimal" = "read"
|
||||
|
||||
[permissions.project_profile.filesystem.":workspace_roots"]
|
||||
"." = "write"
|
||||
|
||||
[permissions.other_profile.filesystem]
|
||||
":minimal" = "read"
|
||||
|
||||
[permissions.other_profile.filesystem.":workspace_roots"]
|
||||
"." = "write"
|
||||
"#,
|
||||
)?;
|
||||
let rules_dir = home.path().join("rules");
|
||||
fs::create_dir_all(&rules_dir)?;
|
||||
fs::write(
|
||||
rules_dir.join("default.rules"),
|
||||
r#"prefix_rule(pattern=["touch", "scoped-allow.txt"], decision="allow", permissions="project_profile")
|
||||
prefix_rule(pattern=["touch", "scoped-prompt.txt"], decision="allow", permissions="other_profile")
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let approval_policy = AskForApproval::OnRequest;
|
||||
let mut builder = test_codex().with_home(home).with_config(move |config| {
|
||||
config.permissions.approval_policy = Constrained::allow_any(approval_policy);
|
||||
});
|
||||
let test = builder.build(&server).await?;
|
||||
assert_eq!(
|
||||
test.session_configured
|
||||
.active_permission_profile
|
||||
.as_ref()
|
||||
.map(|profile| profile.id.as_str()),
|
||||
Some("project_profile")
|
||||
);
|
||||
|
||||
let allow_call_id = "scoped-profile-allow";
|
||||
let allow_command = "touch scoped-allow.txt";
|
||||
let allow_event = shell_event(
|
||||
allow_call_id,
|
||||
allow_command,
|
||||
/*timeout_ms*/ 1_000,
|
||||
SandboxPermissions::RequireEscalated,
|
||||
)?;
|
||||
let _ = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_response_created("resp-scoped-profile-allow-1"),
|
||||
allow_event,
|
||||
ev_completed("resp-scoped-profile-allow-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
let allow_results = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-scoped-profile-allow", "done"),
|
||||
ev_completed("resp-scoped-profile-allow-2"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
submit_turn_preserving_active_permission_profile(
|
||||
&test,
|
||||
"run matching scoped rule",
|
||||
approval_policy,
|
||||
)
|
||||
.await?;
|
||||
wait_for_completion_without_approval(&test).await;
|
||||
|
||||
let allow_output = parse_result(
|
||||
&allow_results
|
||||
.single_request()
|
||||
.function_call_output(allow_call_id),
|
||||
);
|
||||
assert_eq!(
|
||||
allow_output.exit_code,
|
||||
Some(0),
|
||||
"expected matching scoped rule to run without approval: {}",
|
||||
allow_output.stdout
|
||||
);
|
||||
let allow_path = test.cwd.path().join("scoped-allow.txt");
|
||||
assert!(
|
||||
allow_path.exists(),
|
||||
"expected matching scoped rule to create {allow_path:?}"
|
||||
);
|
||||
fs::remove_file(allow_path)?;
|
||||
|
||||
let prompt_call_id = "scoped-profile-prompt";
|
||||
let prompt_command = "touch scoped-prompt.txt";
|
||||
let prompt_event = shell_event(
|
||||
prompt_call_id,
|
||||
prompt_command,
|
||||
/*timeout_ms*/ 1_000,
|
||||
SandboxPermissions::RequireEscalated,
|
||||
)?;
|
||||
let _ = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_response_created("resp-scoped-profile-prompt-1"),
|
||||
prompt_event,
|
||||
ev_completed("resp-scoped-profile-prompt-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
let _ = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-scoped-profile-prompt", "done"),
|
||||
ev_completed("resp-scoped-profile-prompt-2"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
submit_turn_preserving_active_permission_profile(
|
||||
&test,
|
||||
"run non-matching scoped rule",
|
||||
approval_policy,
|
||||
)
|
||||
.await?;
|
||||
let approval = expect_exec_approval(&test, prompt_command).await;
|
||||
test.codex
|
||||
.submit(Op::ExecApproval {
|
||||
id: approval.effective_approval_id(),
|
||||
turn_id: None,
|
||||
decision: ReviewDecision::Denied,
|
||||
})
|
||||
.await?;
|
||||
wait_for_completion(&test).await;
|
||||
assert!(
|
||||
!test.cwd.path().join("scoped-prompt.txt").exists(),
|
||||
"non-matching scoped rule should not bypass approval"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn denying_network_policy_amendment_persists_policy_and_skips_future_network_prompt()
|
||||
-> Result<()> {
|
||||
|
||||
Reference in New Issue
Block a user