permissions: make runtime config profile-backed (#19606)

## Why

This supersedes #19391. During stack repair, GitHub marked #19391 as
merged into a temporary stack branch rather than into `main`, so the
runtime-config change needed a fresh PR.

`PermissionProfile` is now the canonical permissions shape after #19231
because it can distinguish `Managed`, `Disabled`, and `External`
enforcement while also carrying filesystem rules that legacy
`SandboxPolicy` cannot represent cleanly. Core config and session state
still needed to accept profile-backed permissions without forcing every
profile through the strict legacy bridge, which rejected valid runtime
profiles such as direct write roots.

The unrelated CI/test hardening that previously rode along with this PR
has been split into #19683 so this PR stays focused on the permissions
model migration.

## What Changed

- Adds `Permissions.permission_profile` and
`SessionConfiguration.permission_profile` as constrained runtime state,
while keeping `sandbox_policy` as a legacy compatibility projection.
- Introduces profile setters that keep `PermissionProfile`, split
filesystem/network policies, and legacy `SandboxPolicy` projections
synchronized.
- Uses a compatibility projection for requirement checks and legacy
consumers instead of rejecting profiles that cannot round-trip through
`SandboxPolicy` exactly.
- Updates config loading, config overrides, session updates, turn
context plumbing, prompt permission text, sandbox tags, and exec request
construction to carry profile-backed runtime permissions.
- Preserves configured deny-read entries and `glob_scan_max_depth` when
command/session profiles are narrowed.
- Adds `PermissionProfile::read_only()` and
`PermissionProfile::workspace_write()` presets that match legacy
defaults.

## 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/19606).
* #19395
* #19394
* #19393
* #19392
* __->__ #19606
This commit is contained in:
Michael Bolin authored and GitHub committed 2026-04-26 13:29:54 -07:00
1 parent fed0a8f4fa
commit 4d7ce3447d
62 files changed
+1601 -671

No files matched your search

+13 -9
View File
@@ -4,13 +4,13 @@ use codex_features::Features;
use codex_protocol::config_types::WebSearchConfig;
use codex_protocol::config_types::WebSearchMode;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ApplyPatchToolType;
use codex_protocol::openai_models::ConfigShellToolType;
use codex_protocol::openai_models::InputModality;
use codex_protocol::openai_models::ModelInfo;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::WebSearchToolType;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SubAgentSource;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -121,7 +121,7 @@ pub struct ToolsConfigParams<'a> {
pub image_generation_tool_auth_allowed: bool,
pub web_search_mode: Option<WebSearchMode>,
pub session_source: SessionSource,
pub sandbox_policy: &'a SandboxPolicy,
pub permission_profile: &'a PermissionProfile,
pub windows_sandbox_level: WindowsSandboxLevel,
}
@@ -134,7 +134,7 @@ impl ToolsConfig {
image_generation_tool_auth_allowed,
web_search_mode,
session_source,
sandbox_policy,
permission_profile,
windows_sandbox_level,
} = params;
let include_apply_patch_tool = features.enabled(Feature::ApplyPatchFreeform);
@@ -167,7 +167,7 @@ impl ToolsConfig {
};
let unified_exec_allowed = unified_exec_allowed_in_environment(
cfg!(target_os = "windows"),
sandbox_policy,
permission_profile,
*windows_sandbox_level,
);
let shell_type = if !features.enabled(Feature::ShellTool) {
@@ -322,15 +322,19 @@ fn supports_image_generation(model_info: &ModelInfo) -> bool {
fn unified_exec_allowed_in_environment(
is_windows: bool,
sandbox_policy: &SandboxPolicy,
permission_profile: &PermissionProfile,
windows_sandbox_level: WindowsSandboxLevel,
) -> bool {
let managed_sandbox_required = match permission_profile {
PermissionProfile::Managed {
file_system,
network,
} => !file_system.to_sandbox_policy().has_full_disk_write_access() || !network.is_enabled(),
PermissionProfile::Disabled | PermissionProfile::External { .. } => false,
};
!(is_windows
&& windows_sandbox_level != WindowsSandboxLevel::Disabled
&& !matches!(
sandbox_policy,
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. }
))
&& managed_sandbox_required)
}
#[cfg(test)]