permissions: make legacy profile conversion cwd-free (#19414)

## Why

The profile conversion path still required a `cwd` even when it was only
translating a legacy `SandboxPolicy` into a `PermissionProfile`. That
made profile producers invent an ambient `cwd`, which is exactly the
anchoring we are trying to remove from permission-profile data. A legacy
workspace-write policy can be represented symbolically instead: `:cwd =
write` plus read-only `:project_roots` metadata subpaths.

This PR creates that cwd-free base so the rest of the stack can stop
threading cwd through profile construction. Callers that actually need a
concrete runtime filesystem policy for a specific cwd still have an
explicitly named cwd-bound conversion.

## What Changed

- `PermissionProfile::from_legacy_sandbox_policy` now takes only
`&SandboxPolicy`.
- `FileSystemSandboxPolicy::from_legacy_sandbox_policy` is now the
symbolic, cwd-free projection for profiles.
- The old concrete projection is retained as
`FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd` for
runtime/boundary code that must materialize legacy cwd behavior.
- Workspace-write profiles preserve `CurrentWorkingDirectory` and
`ProjectRoots` special entries instead of materializing cwd into
absolute paths.

## Verification

- `cargo check -p codex-protocol -p codex-core -p
codex-app-server-protocol -p codex-app-server -p codex-exec -p
codex-exec-server -p codex-tui -p codex-sandboxing -p
codex-linux-sandbox -p codex-analytics --tests`
- `just fix -p codex-protocol -p codex-core -p codex-app-server-protocol
-p codex-app-server -p codex-exec -p codex-exec-server -p codex-tui -p
codex-sandboxing -p codex-linux-sandbox -p codex-analytics`




---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19414).
* #19395
* #19394
* #19393
* #19392
* #19391
* __->__ #19414
This commit is contained in:
Michael Bolin
2026-04-24 13:42:05 -07:00
committed by GitHub
Unverified
parent 7262c0c450
commit 13e0ec1614
29 changed files with 281 additions and 139 deletions
+2 -2
View File
@@ -121,7 +121,7 @@ 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(
== FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
self.sandbox_policy.get(),
&self.cwd,
);
@@ -201,7 +201,7 @@ impl SessionConfiguration {
// 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(
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
next_configuration.sandbox_policy.get(),
&next_configuration.cwd,
);
+14 -13
View File
@@ -1496,7 +1496,6 @@ async fn session_configured_reports_permission_profile_for_external_sandbox() ->
let expected_permission_profile =
codex_protocol::models::PermissionProfile::from_legacy_sandbox_policy(
&expected_sandbox_policy,
test.session_configured.cwd.as_path(),
);
assert_eq!(
test.session_configured.permission_profile,
@@ -2886,15 +2885,16 @@ async fn session_configuration_apply_permission_profile_preserves_existing_deny_
},
access: FileSystemAccessMode::None,
};
let mut existing_file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
&workspace_policy,
session_configuration.cwd.as_path(),
);
let mut existing_file_system_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
&workspace_policy,
session_configuration.cwd.as_path(),
);
existing_file_system_policy.glob_scan_max_depth = Some(2);
existing_file_system_policy.entries.push(deny_entry.clone());
session_configuration.file_system_sandbox_policy = existing_file_system_policy;
let requested_file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
let requested_file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
&workspace_policy,
session_configuration.cwd.as_path(),
);
@@ -3027,7 +3027,7 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
exclude_slash_tmp: true,
});
session_configuration.file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy(
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
session_configuration.sandbox_policy.get(),
&session_configuration.cwd,
);
@@ -3041,7 +3041,7 @@ async fn session_configuration_apply_rederives_legacy_file_system_policy_on_cwd_
assert_eq!(
updated.file_system_sandbox_policy,
FileSystemSandboxPolicy::from_legacy_sandbox_policy(
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
updated.sandbox_policy.get(),
&project_root,
)
@@ -5460,7 +5460,7 @@ async fn build_initial_context_restates_realtime_start_when_reference_context_is
}
fn file_system_policy_with_unreadable_glob(turn_context: &TurnContext) -> FileSystemSandboxPolicy {
let mut policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
let mut policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
turn_context.sandbox_policy.get(),
&turn_context.cwd,
);
@@ -5476,10 +5476,11 @@ fn file_system_policy_with_unreadable_glob(turn_context: &TurnContext) -> FileSy
#[tokio::test]
async fn turn_context_item_omits_legacy_equivalent_file_system_sandbox_policy() {
let (_session, mut turn_context) = make_session_and_context().await;
turn_context.file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
turn_context.sandbox_policy.get(),
&turn_context.cwd,
);
turn_context.file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
turn_context.sandbox_policy.get(),
&turn_context.cwd,
);
let item = turn_context.to_turn_context_item();
+5 -4
View File
@@ -280,10 +280,11 @@ impl TurnContext {
// the legacy sandbox policy. This keeps turn-context payloads stable
// while both fields exist; once callers consume only the split policy,
// this comparison and the legacy projection should go away.
let legacy_file_system_sandbox_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy(
self.sandbox_policy.get(),
&self.cwd,
);
let legacy_file_system_sandbox_policy =
FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
self.sandbox_policy.get(),
&self.cwd,
);
(self.file_system_sandbox_policy != legacy_file_system_sandbox_policy)
.then(|| self.file_system_sandbox_policy.clone())
}