Fix macOS system-alias matching in patch permission checks (#47879)

## Why

Local `apply_patch` permission checks can treat a macOS system alias and its canonical spelling as different locations, sending writes covered by temporary-directory grants for unnecessary approval.

## What changed

- Normalize trusted top-level aliases in local policy roots and patch targets, reusing a prepared matcher for permission requests and patch safety checks.
- Share alias normalization with Seatbelt and the macOS executor sandbox while preserving mutable symlinks and missing descendants.
- Preserve read-only and deny precedence, protected metadata restrictions, and remote URI matching. Propagate normalization failures from fallible permission checks.

## Testing

Add macOS regressions for multi-file patches with missing parent directories through both direct `apply_patch` calls and shell interception, asserting automatic approval and file contents. Add unit coverage for alias restriction precedence, mutable symlink preservation, missing descendants, and remote permission isolation.

GitOrigin-RevId: 0822ff951eaab728cb1ad91281745fa1b8809f15
This commit is contained in:
felixxia-oai
2026-09-24 16:36:57 +00:00
committed by copyberry
parent 4021746a15
commit 6f51c65958
17 changed files with 792 additions and 304 deletions
+20 -5
View File
@@ -18,7 +18,7 @@ use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxTransformRequest;
use codex_sandboxing::SandboxType;
use codex_utils_absolute_path::AbsolutePathBuf;
#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
use codex_utils_absolute_path::canonicalize_preserving_symlinks;
#[cfg(any(windows, test))]
use codex_utils_path_uri::LegacyAppPathString;
@@ -130,7 +130,7 @@ impl FileSystemSandboxRunner {
// Linux resolves aliases in the sandbox helper. Doing it here also probes
// unrelated permission roots synchronously on the executor's runtime thread.
#[cfg(not(target_os = "linux"))]
normalize_file_system_policy_root_aliases(&mut file_system_policy);
normalize_file_system_policy_root_aliases(&mut file_system_policy)?;
#[cfg(windows)]
bind_windows_cwd_relative_deny_read_globs(&mut file_system_policy, &cwd.uri)?;
let network_policy = NetworkSandboxPolicy::Restricted;
@@ -296,19 +296,34 @@ fn bind_windows_cwd_relative_deny_read_globs(
}
#[cfg(not(target_os = "linux"))]
fn normalize_file_system_policy_root_aliases(file_system_policy: &mut FileSystemSandboxPolicy) {
fn normalize_file_system_policy_root_aliases(
file_system_policy: &mut FileSystemSandboxPolicy,
) -> Result<(), JSONRPCErrorError> {
for entry in &mut file_system_policy.entries {
// Alias normalization uses this executor's filesystem; leave foreign
// or opaque PathUris unchanged.
if let FileSystemPath::Path { path } = &mut entry.path
&& let Ok(native_path) = path.to_abs_path()
{
*path = normalize_top_level_alias(native_path).into();
#[cfg(target_os = "macos")]
{
*path = native_path
.normalize_system_aliases()
.map_err(|error| {
invalid_request(format!("failed to normalize {path}: {error}"))
})?
.into();
}
#[cfg(not(target_os = "macos"))]
{
*path = normalize_top_level_alias(native_path).into();
}
}
}
Ok(())
}
#[cfg(not(target_os = "linux"))]
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
fn normalize_top_level_alias(path: AbsolutePathBuf) -> AbsolutePathBuf {
let raw_path = path.to_path_buf();
for ancestor in raw_path.ancestors() {