mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
permissions: derive compatibility policies from profiles (#19392)
## Why After #19391, `PermissionProfile` and the split filesystem/network policies could still be stored in parallel. That creates drift risk: a profile can preserve deny globs, external enforcement, or split filesystem entries while a cached projection silently loses those details. This PR makes the profile the runtime source and derives compatibility views from it. ## What Changed - Removes stored filesystem/network sandbox projections from `Permissions` and `SessionConfiguration`; their accessors now derive from the canonical `PermissionProfile`. - Derives legacy `SandboxPolicy` snapshots from profiles only where an older API still needs that field. - Updates MCP connection and elicitation state to track `PermissionProfile` instead of `SandboxPolicy` for auto-approval decisions. - Adds semantic filesystem-policy comparison so cwd changes can preserve richer profiles while still recognizing equivalent legacy projections independent of entry ordering. - Updates config/session tests to assert profile-derived projections instead of parallel stored fields. ## Verification - `cargo test -p codex-core direct_write_roots` - `cargo test -p codex-core runtime_roots_to_legacy_projection` - `cargo test -p codex-app-server requested_permissions_trust_project_uses_permission_profile_intent` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19392). * #19395 * #19394 * #19393 * __->__ #19392
This commit is contained in:
@@ -272,8 +272,9 @@ async fn effective_patch_permissions(
|
||||
session.granted_session_permissions().await.as_ref(),
|
||||
session.granted_turn_permissions().await.as_ref(),
|
||||
);
|
||||
let base_file_system_sandbox_policy = turn.file_system_sandbox_policy();
|
||||
let file_system_sandbox_policy = effective_file_system_sandbox_policy(
|
||||
&turn.file_system_sandbox_policy,
|
||||
&base_file_system_sandbox_policy,
|
||||
granted_permissions.as_ref(),
|
||||
);
|
||||
let effective_additional_permissions = apply_granted_turn_permissions(
|
||||
|
||||
@@ -99,7 +99,8 @@ impl ToolHandler for ListDirHandler {
|
||||
"dir_path must be an absolute path".to_string(),
|
||||
));
|
||||
}
|
||||
let read_deny_matcher = ReadDenyMatcher::new(&turn.file_system_sandbox_policy, &turn.cwd);
|
||||
let file_system_sandbox_policy = turn.file_system_sandbox_policy();
|
||||
let read_deny_matcher = ReadDenyMatcher::new(&file_system_sandbox_policy, &turn.cwd);
|
||||
if read_deny_matcher
|
||||
.as_ref()
|
||||
.is_some_and(|matcher| matcher.is_read_denied(&path))
|
||||
|
||||
@@ -37,6 +37,9 @@ use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::AgentStatus;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::FileSystemAccessMode;
|
||||
use codex_protocol::protocol::FileSystemPath;
|
||||
use codex_protocol::protocol::FileSystemSandboxEntry;
|
||||
use codex_protocol::protocol::FileSystemSandboxPolicy;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
use codex_protocol::protocol::InterAgentCommunication;
|
||||
@@ -2074,21 +2077,6 @@ async fn multi_agent_v2_spawn_surfaces_task_name_validation_errors() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
fn pick_allowed_sandbox_policy(
|
||||
constraint: &crate::config::Constrained<SandboxPolicy>,
|
||||
base: SandboxPolicy,
|
||||
) -> SandboxPolicy {
|
||||
let candidates = [
|
||||
SandboxPolicy::DangerFullAccess,
|
||||
SandboxPolicy::new_workspace_write_policy(),
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
];
|
||||
candidates
|
||||
.into_iter()
|
||||
.find(|candidate| *candidate != base && constraint.can_set(candidate).is_ok())
|
||||
.unwrap_or(base)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SpawnAgentResult {
|
||||
agent_id: String,
|
||||
@@ -2098,12 +2086,17 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
let (mut session, mut turn) = make_session_and_context().await;
|
||||
let manager = thread_manager();
|
||||
session.services.agent_control = manager.agent_control();
|
||||
let expected_sandbox = pick_allowed_sandbox_policy(
|
||||
&turn.config.permissions.sandbox_policy,
|
||||
turn.config.permissions.sandbox_policy.get().clone(),
|
||||
);
|
||||
let expected_file_system_sandbox_policy =
|
||||
let expected_sandbox = turn.config.permissions.sandbox_policy.get().clone();
|
||||
let mut expected_file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(&expected_sandbox, &turn.cwd);
|
||||
expected_file_system_sandbox_policy
|
||||
.entries
|
||||
.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "**/.env".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::None,
|
||||
});
|
||||
let expected_network_sandbox_policy = NetworkSandboxPolicy::from(&expected_sandbox);
|
||||
let expected_permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&expected_sandbox),
|
||||
@@ -2113,16 +2106,11 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
turn.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("approval policy should be set");
|
||||
turn.sandbox_policy
|
||||
.set(expected_sandbox.clone())
|
||||
.expect("sandbox policy should be set");
|
||||
turn.file_system_sandbox_policy = expected_file_system_sandbox_policy.clone();
|
||||
turn.network_sandbox_policy = expected_network_sandbox_policy;
|
||||
turn.permission_profile = expected_permission_profile.clone();
|
||||
assert_ne!(
|
||||
expected_sandbox,
|
||||
turn.config.permissions.sandbox_policy.get().clone(),
|
||||
"test requires a runtime sandbox override that differs from base config"
|
||||
expected_permission_profile,
|
||||
turn.config.permissions.permission_profile(),
|
||||
"test requires a runtime profile override that differs from base config"
|
||||
);
|
||||
|
||||
let invocation = invocation(
|
||||
@@ -2164,11 +2152,11 @@ async fn spawn_agent_reapplies_runtime_sandbox_after_role_config() {
|
||||
.expect("spawned agent thread should exist");
|
||||
let child_turn = child_thread.codex.session.new_default_turn().await;
|
||||
assert_eq!(
|
||||
child_turn.file_system_sandbox_policy,
|
||||
child_turn.file_system_sandbox_policy(),
|
||||
expected_file_system_sandbox_policy
|
||||
);
|
||||
assert_eq!(
|
||||
child_turn.network_sandbox_policy,
|
||||
child_turn.network_sandbox_policy(),
|
||||
expected_network_sandbox_policy
|
||||
);
|
||||
assert_eq!(child_turn.permission_profile(), expected_permission_profile);
|
||||
@@ -3637,11 +3625,6 @@ async fn build_agent_spawn_config_uses_turn_context_values() {
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
turn.sandbox_policy
|
||||
.set(sandbox_policy)
|
||||
.expect("sandbox policy set");
|
||||
turn.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
turn.network_sandbox_policy = network_sandbox_policy;
|
||||
turn.permission_profile = permission_profile.clone();
|
||||
turn.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
@@ -3718,7 +3701,7 @@ async fn build_agent_resume_config_clears_base_instructions() {
|
||||
expected
|
||||
.permissions
|
||||
.sandbox_policy
|
||||
.set(turn.sandbox_policy.get().clone())
|
||||
.set(turn.sandbox_policy())
|
||||
.expect("sandbox policy set");
|
||||
assert_eq!(config, expected);
|
||||
}
|
||||
|
||||
@@ -513,14 +513,16 @@ impl ShellHandler {
|
||||
);
|
||||
emitter.begin(event_ctx).await;
|
||||
|
||||
let file_system_sandbox_policy = turn.file_system_sandbox_policy();
|
||||
let sandbox_policy = turn.sandbox_policy();
|
||||
let exec_approval_requirement = session
|
||||
.services
|
||||
.exec_policy
|
||||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &exec_params.command,
|
||||
approval_policy: turn.approval_policy.value(),
|
||||
sandbox_policy: turn.sandbox_policy.get(),
|
||||
file_system_sandbox_policy: &turn.file_system_sandbox_policy,
|
||||
sandbox_policy: &sandbox_policy,
|
||||
file_system_sandbox_policy: &file_system_sandbox_policy,
|
||||
sandbox_permissions: if effective_additional_permissions.permissions_preapproved {
|
||||
codex_protocol::models::SandboxPermissions::UseDefault
|
||||
} else {
|
||||
|
||||
@@ -359,7 +359,8 @@ impl NetworkApprovalService {
|
||||
.await;
|
||||
return NetworkDecision::deny(REASON_NOT_ALLOWED);
|
||||
};
|
||||
if !sandbox_policy_allows_network_approval_flow(turn_context.sandbox_policy.get()) {
|
||||
let sandbox_policy = turn_context.sandbox_policy();
|
||||
if !sandbox_policy_allows_network_approval_flow(&sandbox_policy) {
|
||||
pending.set_decision(PendingApprovalDecision::Deny).await;
|
||||
self.pending_host_approvals.lock().await.remove(&key);
|
||||
self.record_outcome_for_single_active_call(NetworkApprovalOutcome::DeniedByPolicy(
|
||||
|
||||
@@ -122,8 +122,10 @@ impl ToolOrchestrator {
|
||||
// 1) Approval
|
||||
let mut already_approved = false;
|
||||
|
||||
let file_system_sandbox_policy = turn_ctx.file_system_sandbox_policy();
|
||||
let network_sandbox_policy = turn_ctx.network_sandbox_policy();
|
||||
let requirement = tool.exec_approval_requirement(req).unwrap_or_else(|| {
|
||||
default_exec_approval_requirement(approval_policy, &turn_ctx.file_system_sandbox_policy)
|
||||
default_exec_approval_requirement(approval_policy, &file_system_sandbox_policy)
|
||||
});
|
||||
match requirement {
|
||||
ExecApprovalRequirement::Skip { .. } => {
|
||||
@@ -194,8 +196,8 @@ impl ToolOrchestrator {
|
||||
let initial_sandbox = match tool.sandbox_mode_for_first_attempt(req) {
|
||||
SandboxOverride::BypassSandboxFirstAttempt => SandboxType::None,
|
||||
SandboxOverride::NoOverride => self.sandbox.select_initial(
|
||||
&turn_ctx.file_system_sandbox_policy,
|
||||
turn_ctx.network_sandbox_policy,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
tool.sandbox_preference(),
|
||||
turn_ctx.windows_sandbox_level,
|
||||
managed_network_active,
|
||||
@@ -268,7 +270,7 @@ impl ToolOrchestrator {
|
||||
&& matches!(
|
||||
default_exec_approval_requirement(
|
||||
approval_policy,
|
||||
&turn_ctx.file_system_sandbox_policy
|
||||
&file_system_sandbox_policy
|
||||
),
|
||||
ExecApprovalRequirement::NeedsApproval { .. }
|
||||
);
|
||||
|
||||
@@ -390,11 +390,12 @@ async fn execve_permission_request_hook_short_circuits_prompt() -> anyhow::Resul
|
||||
..HooksConfig::default()
|
||||
});
|
||||
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
turn_context.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
|
||||
turn_context.sandbox_policy = Constrained::allow_any(sandbox_policy.clone());
|
||||
turn_context.file_system_sandbox_policy = read_only_file_system_sandbox_policy();
|
||||
turn_context.network_sandbox_policy = NetworkSandboxPolicy::Restricted;
|
||||
turn_context.permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&read_only_file_system_sandbox_policy(),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
|
||||
let workdir = AbsolutePathBuf::try_from(std::env::current_dir()?)?;
|
||||
let target = std::env::temp_dir().join("execve-hook-short-circuit.txt");
|
||||
|
||||
Reference in New Issue
Block a user