mirror of
https://github.com/openai/codex.git
synced 2026-11-06 00:30:29 +00: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:
1 parent
4d7ce3447d
commit
deaa307fb2
39 files changed
+568
-439
No files matched your search
@@ -1,5 +1,7 @@
|
||||
use super::*;
|
||||
use crate::goals::GoalRuntimeState;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
/// Context for an initialized model agent
|
||||
@@ -58,12 +60,6 @@ pub(crate) struct SessionConfiguration {
|
||||
pub(super) approvals_reviewer: ApprovalsReviewer,
|
||||
/// Canonical permission profile for the session.
|
||||
pub(super) permission_profile: Constrained<PermissionProfile>,
|
||||
/// Legacy sandbox projection retained while lower-level callers migrate.
|
||||
pub(super) sandbox_policy: Constrained<SandboxPolicy>,
|
||||
/// Filesystem sandbox projection of `permission_profile`.
|
||||
pub(super) file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
/// Network sandbox projection of `permission_profile`.
|
||||
pub(super) network_sandbox_policy: NetworkSandboxPolicy,
|
||||
pub(super) windows_sandbox_level: WindowsSandboxLevel,
|
||||
|
||||
/// Absolute working directory that should be treated as the *root* of the
|
||||
@@ -102,12 +98,25 @@ impl SessionConfiguration {
|
||||
}
|
||||
|
||||
pub(super) fn sandbox_policy(&self) -> SandboxPolicy {
|
||||
self.sandbox_policy.get().clone()
|
||||
self.permission_profile()
|
||||
.to_legacy_sandbox_policy(&self.cwd)
|
||||
.unwrap_or_else(|_| {
|
||||
let file_system_sandbox_policy = self.file_system_sandbox_policy();
|
||||
codex_sandboxing::compatibility_sandbox_policy_for_permission_profile(
|
||||
self.permission_profile.get(),
|
||||
&file_system_sandbox_policy,
|
||||
self.network_sandbox_policy(),
|
||||
&self.cwd,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy {
|
||||
self.file_system_sandbox_policy.clone()
|
||||
self.permission_profile.get().file_system_sandbox_policy()
|
||||
}
|
||||
|
||||
pub(super) fn network_sandbox_policy(&self) -> NetworkSandboxPolicy {
|
||||
self.permission_profile.get().network_sandbox_policy()
|
||||
}
|
||||
|
||||
pub(super) fn thread_config_snapshot(&self) -> ThreadConfigSnapshot {
|
||||
@@ -129,11 +138,30 @@ impl SessionConfiguration {
|
||||
|
||||
pub(crate) fn apply(&self, updates: &SessionSettingsUpdate) -> ConstraintResult<Self> {
|
||||
let mut next_configuration = self.clone();
|
||||
let file_system_policy_matches_legacy = self.file_system_sandbox_policy
|
||||
== FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
self.sandbox_policy.get(),
|
||||
let current_sandbox_policy = self.sandbox_policy();
|
||||
let current_file_system_sandbox_policy = self.file_system_sandbox_policy();
|
||||
let current_network_sandbox_policy = self.network_sandbox_policy();
|
||||
let legacy_file_system_projection =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
¤t_sandbox_policy,
|
||||
&self.cwd,
|
||||
¤t_file_system_sandbox_policy,
|
||||
);
|
||||
let file_system_policy_matches_legacy = current_file_system_sandbox_policy
|
||||
.is_semantically_equivalent_to(&legacy_file_system_projection, &self.cwd);
|
||||
let file_system_policy_has_rebindable_cwd_write = current_file_system_sandbox_policy
|
||||
.entries
|
||||
.iter()
|
||||
.any(|entry| {
|
||||
entry.access.can_write()
|
||||
&& matches!(
|
||||
&entry.path,
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::CurrentWorkingDirectory
|
||||
| FileSystemSpecialPath::ProjectRoots { subpath: None },
|
||||
}
|
||||
)
|
||||
});
|
||||
if let Some(collaboration_mode) = updates.collaboration_mode.clone() {
|
||||
next_configuration.collaboration_mode = collaboration_mode;
|
||||
}
|
||||
@@ -181,42 +209,41 @@ impl SessionConfiguration {
|
||||
if let Some(permission_profile) = updates.permission_profile.clone() {
|
||||
next_configuration.set_permission_profile_projection(
|
||||
permission_profile,
|
||||
Some(&self.file_system_sandbox_policy),
|
||||
Some(¤t_file_system_sandbox_policy),
|
||||
)?;
|
||||
} else if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
|
||||
next_configuration.sandbox_policy.set(sandbox_policy)?;
|
||||
next_configuration.file_system_sandbox_policy =
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
next_configuration.sandbox_policy.get(),
|
||||
&sandbox_policy,
|
||||
&next_configuration.cwd,
|
||||
&self.file_system_sandbox_policy,
|
||||
¤t_file_system_sandbox_policy,
|
||||
);
|
||||
next_configuration.network_sandbox_policy =
|
||||
NetworkSandboxPolicy::from(next_configuration.sandbox_policy.get());
|
||||
let network_sandbox_policy = NetworkSandboxPolicy::from(&sandbox_policy);
|
||||
next_configuration.permission_profile.set(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(
|
||||
next_configuration.sandbox_policy.get(),
|
||||
),
|
||||
&next_configuration.file_system_sandbox_policy,
|
||||
next_configuration.network_sandbox_policy,
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(&sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
),
|
||||
)?;
|
||||
} else if cwd_changed && file_system_policy_matches_legacy {
|
||||
} else if cwd_changed
|
||||
&& file_system_policy_matches_legacy
|
||||
&& file_system_policy_has_rebindable_cwd_write
|
||||
{
|
||||
// Preserve richer split policies across cwd-only updates; only
|
||||
// rederive when the session is already using the legacy bridge.
|
||||
next_configuration.file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
|
||||
next_configuration.sandbox_policy.get(),
|
||||
// rederive when the session is already using a structurally
|
||||
// cwd-bound legacy bridge.
|
||||
let file_system_sandbox_policy =
|
||||
FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
|
||||
¤t_sandbox_policy,
|
||||
&next_configuration.cwd,
|
||||
¤t_file_system_sandbox_policy,
|
||||
);
|
||||
next_configuration.permission_profile.set(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(
|
||||
next_configuration.sandbox_policy.get(),
|
||||
),
|
||||
&next_configuration.file_system_sandbox_policy,
|
||||
next_configuration.network_sandbox_policy,
|
||||
SandboxEnforcement::from_legacy_sandbox_policy(¤t_sandbox_policy),
|
||||
&file_system_sandbox_policy,
|
||||
current_network_sandbox_policy,
|
||||
),
|
||||
)?;
|
||||
}
|
||||
@@ -247,16 +274,7 @@ impl SessionConfiguration {
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
);
|
||||
let sandbox_policy = compatibility_sandbox_policy_for_permission_profile(
|
||||
&effective_permission_profile,
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
self.cwd.as_path(),
|
||||
);
|
||||
self.permission_profile.set(effective_permission_profile)?;
|
||||
self.sandbox_policy.set(sandbox_policy)?;
|
||||
self.file_system_sandbox_policy = file_system_sandbox_policy;
|
||||
self.network_sandbox_policy = network_sandbox_policy;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -487,7 +505,7 @@ impl Session {
|
||||
model: session_configuration.collaboration_mode.model().to_string(),
|
||||
provider_name: config.model_provider_id.clone(),
|
||||
approval_policy: session_configuration.approval_policy.value().to_string(),
|
||||
sandbox_policy: format!("{:?}", session_configuration.sandbox_policy.get()),
|
||||
sandbox_policy: format!("{:?}", session_configuration.sandbox_policy()),
|
||||
};
|
||||
let rollout_thread_trace = if matches!(
|
||||
session_configuration.session_source,
|
||||
@@ -768,7 +786,7 @@ impl Session {
|
||||
// setup is straightforward enough and performs well.
|
||||
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new_uninitialized(
|
||||
&config.permissions.approval_policy,
|
||||
&config.permissions.sandbox_policy,
|
||||
&config.permissions.permission_profile,
|
||||
))),
|
||||
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
|
||||
unified_exec_manager: UnifiedExecProcessManager::new(
|
||||
@@ -847,6 +865,7 @@ impl Session {
|
||||
// Dispatch the SessionConfiguredEvent first and then report any errors.
|
||||
// If resuming, include converted initial messages in the payload so UIs can render them immediately.
|
||||
let initial_messages = initial_history.get_event_msgs();
|
||||
let session_sandbox_policy = session_configuration.sandbox_policy();
|
||||
let events = std::iter::once(Event {
|
||||
id: INITIAL_SUBMIT_ID.to_owned(),
|
||||
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
|
||||
@@ -858,7 +877,7 @@ impl Session {
|
||||
service_tier: session_configuration.service_tier,
|
||||
approval_policy: session_configuration.approval_policy.value(),
|
||||
approvals_reviewer: session_configuration.approvals_reviewer,
|
||||
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
|
||||
sandbox_policy: session_sandbox_policy.clone(),
|
||||
permission_profile: Some(session_configuration.permission_profile()),
|
||||
cwd: session_configuration.cwd.clone(),
|
||||
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
|
||||
@@ -867,7 +886,7 @@ impl Session {
|
||||
initial_messages,
|
||||
network_proxy: session_network_proxy.filter(|_| {
|
||||
Self::managed_network_proxy_active_for_sandbox_policy(
|
||||
session_configuration.sandbox_policy.get(),
|
||||
&session_sandbox_policy,
|
||||
)
|
||||
}),
|
||||
rollout_path,
|
||||
@@ -901,7 +920,7 @@ impl Session {
|
||||
&session_configuration.approval_policy,
|
||||
INITIAL_SUBMIT_ID.to_owned(),
|
||||
tx_event.clone(),
|
||||
session_configuration.sandbox_policy.get().clone(),
|
||||
session_configuration.permission_profile(),
|
||||
McpRuntimeEnvironment::new(
|
||||
sess.services
|
||||
.environment_manager
|
||||
|
||||
Reference in new issue
Block a user