Skip to content

permissions: derive snapshot sandbox projections#19775

Open
bolinfest wants to merge 1 commit intopr19774from
pr19775
Open

permissions: derive snapshot sandbox projections#19775
bolinfest wants to merge 1 commit intopr19774from
pr19775

Conversation

@bolinfest
Copy link
Copy Markdown
Collaborator

@bolinfest bolinfest commented Apr 27, 2026

Why

ThreadConfigSnapshot is used by app-server and thread metadata code as a stable view of active runtime settings. Keeping both sandbox_policy and permission_profile in the snapshot duplicates permission state and makes it possible for the legacy projection to drift from the canonical profile.

The legacy sandbox value is still needed at app-server compatibility boundaries, so this PR derives it on demand from the snapshot profile and cwd instead of storing it.

What Changed

  • Removes ThreadConfigSnapshot.sandbox_policy.
  • Adds ThreadConfigSnapshot::sandbox_policy() as a compatibility projection from permission_profile plus cwd.
  • Updates app-server response/metadata code and tests to call the projection only where legacy fields still exist.
  • Keeps snapshot construction profile-only so split filesystem rules, disabled enforcement, and external enforcement remain represented by the canonical profile.

Verification

  • cargo test -p codex-app-server thread_response_permission_profile_preserves_enforcement --lib
  • cargo test -p codex-core dispatch_reclaims_stale_global_lock_and_starts_consolidation --lib

Stack created with Sapling. Best reviewed with ReviewStack.

@bolinfest bolinfest requested a review from a team as a code owner April 27, 2026 05:49
@bolinfest bolinfest force-pushed the pr19774 branch 2 times, most recently from 1e864f3 to f1f1786 Compare April 27, 2026 06:03
@bolinfest bolinfest force-pushed the pr19775 branch 2 times, most recently from 6487630 to 54173b9 Compare April 27, 2026 06:09
@bolinfest bolinfest force-pushed the pr19774 branch 2 times, most recently from c247fed to ec9a1d7 Compare April 27, 2026 06:15
@bolinfest bolinfest force-pushed the pr19775 branch 3 times, most recently from eddf071 to c9269a0 Compare April 27, 2026 07:06
@bolinfest bolinfest force-pushed the pr19774 branch 2 times, most recently from 72111af to cac068a Compare April 27, 2026 07:14
@bolinfest bolinfest force-pushed the pr19775 branch 2 times, most recently from 6c92cc4 to 4e3c8ec Compare April 27, 2026 16:30
@bolinfest bolinfest force-pushed the pr19774 branch 2 times, most recently from 347c6f7 to cb0186b Compare April 27, 2026 16:46
@bolinfest bolinfest force-pushed the pr19775 branch 2 times, most recently from b392db1 to b77b4d5 Compare April 27, 2026 20:42
@bolinfest bolinfest force-pushed the pr19774 branch 2 times, most recently from 53baea0 to 4b9080c Compare April 27, 2026 23:02
@bolinfest bolinfest force-pushed the pr19775 branch 2 times, most recently from 09d5507 to 41d0199 Compare April 27, 2026 23:12
bolinfest added a commit that referenced this pull request Apr 27, 2026
## Why

This continues the permissions migration by making legacy config default
resolution produce the canonical `PermissionProfile` first. The legacy
`SandboxPolicy` projection should stay available at compatibility
boundaries, but config loading should not create a legacy policy just to
immediately convert it back into a profile.

Specifically, when `default_permissions` is not specified in
`config.toml`, instead of creating a `SandboxPolicy` in
`codex-rs/core/src/config/mod.rs` and then trying to derive a
`PermissionProfile` from it, we use `derive_permission_profile()` to
create a more faithful `PermissionProfile` using the values of
`ConfigToml` directly.

This also keeps the existing behavior of `sandbox_workspace_write` and
extra writable roots after #19841 replaced `:cwd` with `:project_roots`.
Legacy workspace-write defaults are represented as symbolic
`:project_roots` write access plus symbolic project-root metadata
carveouts. Extra absolute writable roots are still added directly and
continue to get concrete metadata protections for paths that exist under
those roots.

The platform sandboxes differ when a symbolic project-root subpath does
not exist yet.

* **Seatbelt** can encode literal/subpath exclusions directly, so macOS
emits project-root metadata subpath policies even if `.git`, `.agents`,
or `.codex` do not exist.
* **bwrap** has to materialize bind-mount targets. Binding `/dev/null`
to a missing `.git` can create a host-visible placeholder that changes
Git repo discovery. Binding missing `.agents` would not affect Git
discovery, but it would still create a host-visible project metadata
placeholder from an automatic compatibility carveout. Linux therefore
skips only missing automatic `.git` and `.agents` read-only metadata
masks; missing `.codex` remains protected so first-time project config
creation goes through the protected-path approval flow. User-authored
`read` and `none` subpath rules keep normal bwrap behavior, and `none`
can still mask the first missing component to prevent creation under
writable roots.

## What Changed

- Adds profile-native helpers for legacy workspace-write semantics,
including `PermissionProfile::workspace_write_with()`,
`FileSystemSandboxPolicy::workspace_write()`, and
`FileSystemSandboxPolicy::with_additional_legacy_workspace_writable_roots()`.
- Makes `FileSystemSandboxPolicy::workspace_write()` the single legacy
workspace-write constructor so both `from_legacy_sandbox_policy()` and
`From<&SandboxPolicy>` include the project-root metadata carveouts.
- Removes the no-carveout `legacy_workspace_write_base_policy()` path
and the `prune_read_entries_under_writable_roots()` cleanup that was
only needed by that split construction.
- Adds `ConfigToml::derive_permission_profile()` for legacy sandbox-mode
fallback resolution; named `default_permissions` profiles continue
through the permissions profile pipeline instead of being reconstructed
from `sandbox_mode`.
- Updates `Config::load()` to start from the derived profile, validate
that it still has a legacy compatibility projection, and apply
additional writable roots directly to managed workspace-write filesystem
policies.
- Updates Linux bwrap argument construction so missing automatic
`.git`/`.agents` symbolic project-root read-only carveouts are skipped
before emitting bind args; missing `.codex`, user-authored `read`/`none`
subpath rules, and existing missing writable-root behavior are
preserved.
- Adds coverage that legacy workspace-write config produces symbolic
project-root metadata carveouts, extra legacy workspace writable roots
still protect existing metadata paths such as `.git`, and bwrap skips
missing `.git`/`.agents` project-root carveouts while preserving missing
`.codex` and user-authored missing subpath rules.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/19772).
* #19776
* #19775
* #19774
* #19773
* __->__ #19772
@bolinfest bolinfest force-pushed the pr19774 branch 2 times, most recently from 1e8a11f to 0676603 Compare April 28, 2026 00:04
@bolinfest bolinfest force-pushed the pr19775 branch 2 times, most recently from 5772009 to 7a2d356 Compare April 28, 2026 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant