protocol: canonicalize file system permissions (#18274)

## Why

`PermissionProfile` needs stable, canonical file-system semantics before
it can become the primary runtime permissions abstraction. Without a
canonical form, callers have to keep re-deriving legacy sandbox maps and
profile comparisons remain lossy or order-dependent.

## What changed

This adds canonicalization helpers for `FileSystemPermissions` and
`PermissionProfile`, expands special paths into explicit sandbox
entries, and updates permission request/conversion paths to consume
those canonical entries. It also tightens the legacy bridge so root-wide
write profiles with narrower carveouts are not silently projected as
full-disk legacy access.

## Verification

- `cargo test -p codex-protocol
root_write_with_read_only_child_is_not_full_disk_write -- --nocapture`
- `cargo test -p codex-sandboxing permission -- --nocapture`
- `cargo test -p codex-tui permissions -- --nocapture`
This commit is contained in:
Michael Bolin
2026-04-20 09:57:03 -07:00
committed by GitHub
parent ac7c9a685f
commit dcec516313
41 changed files with 2076 additions and 385 deletions

View File

@@ -791,7 +791,7 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result
let override_retained_call_id = "override-retained-call";
let override_trailing_call_id = "override-trailing-call";
let retained_command = "printf retained-shell-output";
let trailing_command = "printf trailing-shell-output";
let trailing_command = "printf '%020000d' 0";
let baseline_harness = TestCodexHarness::with_builder(
test_codex()
@@ -880,9 +880,12 @@ async fn remote_compact_trim_estimate_uses_session_base_instructions() -> Result
let baseline_input_tokens = estimate_compact_input_tokens(&baseline_compact_request);
let baseline_payload_tokens = estimate_compact_payload_tokens(&baseline_compact_request);
let override_base_instructions =
format!("REMOTE_BASE_INSTRUCTIONS_OVERRIDE {}", "x".repeat(120_000));
let override_context_window = baseline_payload_tokens.saturating_add(1_000);
let override_base_instructions = format!(
"{}\nREMOTE_BASE_INSTRUCTIONS_OVERRIDE {}",
baseline_compact_request.instructions_text(),
"x".repeat(4_000)
);
let override_context_window = baseline_payload_tokens.saturating_add(500);
let pretrim_override_estimate =
baseline_input_tokens.saturating_add(approx_token_count(&override_base_instructions));
assert!(