Preserve permissions when updating session metadata (#41464)

## What changed

- Defer legacy sandbox policy projection until a working-directory change may require rebinding project-root writes.
- Keep client name and version updates from resolving filesystem paths or altering the existing permission snapshot.

## Testing

- Add regression coverage for updating client metadata with a managed, restricted filesystem policy that includes both a writable path and a nested denied path.

GitOrigin-RevId: d84dbcd09b9d1961a11b2889d79479e30eb54149
This commit is contained in:
Charlie Marsh
2026-08-29 03:59:21 +00:00
committed by copyberry
parent 3c062df036
commit 0b45b171ca
2 changed files with 74 additions and 25 deletions
+25 -25
View File
@@ -351,10 +351,8 @@ impl SessionConfiguration {
current_environments: &[TurnEnvironmentSelection],
) -> ConstraintResult<Self> {
let mut next_configuration = self.clone();
let current_sandbox_policy = self.sandbox_policy(current_environments);
let current_file_system_sandbox_policy =
self.file_system_sandbox_policy(current_environments);
let current_network_sandbox_policy = self.network_sandbox_policy();
let file_system_policy_has_rebindable_project_root_write =
current_file_system_sandbox_policy
.entries
@@ -437,35 +435,37 @@ impl SessionConfiguration {
network_sandbox_policy,
),
)?;
} else if cwd_changed
&& file_system_policy_has_rebindable_project_root_write
&& current_file_system_sandbox_policy.is_semantically_equivalent_to(
} else if cwd_changed && file_system_policy_has_rebindable_project_root_write {
// Compatibility projection can resolve filesystem paths. Only compute it
// when a cwd-bound legacy policy might need rebinding.
let current_sandbox_policy = self.sandbox_policy(current_environments);
if current_file_system_sandbox_policy.is_semantically_equivalent_to(
&FileSystemSandboxPolicy::from_legacy_sandbox_policy_preserving_deny_entries(
&current_sandbox_policy,
self.cwd(),
&current_file_system_sandbox_policy,
),
self.cwd(),
)
{
// Preserve richer split policies across cwd-only updates; only
// 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(
&current_sandbox_policy,
next_configuration.cwd(),
&current_file_system_sandbox_policy,
);
next_configuration
.permission_profile_state
.set_legacy_permission_profile(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&current_sandbox_policy),
&file_system_sandbox_policy,
current_network_sandbox_policy,
),
)?;
) {
// Preserve richer split policies across cwd-only updates; only
// 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(
&current_sandbox_policy,
next_configuration.cwd(),
&current_file_system_sandbox_policy,
);
next_configuration
.permission_profile_state
.set_legacy_permission_profile(
PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::from_legacy_sandbox_policy(&current_sandbox_policy),
&file_system_sandbox_policy,
self.network_sandbox_policy(),
),
)?;
}
}
if let Some(app_server_client_name) = updates.app_server_client_name.clone() {
next_configuration.app_server_client_name = Some(app_server_client_name);
+49
View File
@@ -5328,6 +5328,55 @@ async fn resolved_environments_for_configuration(
(environment_manager, turn_environments.snapshot().await)
}
#[tokio::test]
async fn session_configuration_apply_client_metadata_preserves_permissions() {
let mut configuration = make_session_configuration_for_tests().await;
let workspace = tempfile::tempdir().expect("create workspace");
let cwd = workspace.path().abs();
configuration.legacy_fallback_cwd = cwd.clone();
let permission_profile = PermissionProfile::from_runtime_permissions_with_enforcement(
SandboxEnforcement::Managed,
&FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry::new(
FileSystemPath::Path {
path: cwd.join("writable").into(),
},
FileSystemAccessMode::Write,
),
FileSystemSandboxEntry::new(
FileSystemPath::Path {
path: cwd.join("writable/private").into(),
},
FileSystemAccessMode::Deny,
),
]),
NetworkSandboxPolicy::Restricted,
);
configuration
.set_permission_profile_for_tests(permission_profile)
.expect("set custom permission profile");
let expected = configuration.thread_settings_snapshot(&[]);
let updated = configuration
.apply(
&SessionSettingsUpdate {
app_server_client_name: Some("codex-tui".to_string()),
app_server_client_version: Some("1.0.0".to_string()),
..Default::default()
},
&[],
)
.expect("update client metadata");
assert_eq!(updated.thread_settings_snapshot(&[]), expected);
assert_eq!(
(
updated.app_server_client_name,
updated.app_server_client_version
),
(Some("codex-tui".to_string()), Some("1.0.0".to_string())),
);
}
#[tokio::test]
async fn session_configuration_apply_preserves_profile_file_system_policy_on_cwd_only_update() {
let mut session_configuration = make_session_configuration_for_tests().await;