Skip to content

sandboxing: materialize cwd-relative permission globs#18867

Merged
bolinfest merged 1 commit intomainfrom
pr18867
Apr 22, 2026
Merged

sandboxing: materialize cwd-relative permission globs#18867
bolinfest merged 1 commit intomainfrom
pr18867

Conversation

@bolinfest
Copy link
Copy Markdown
Collaborator

@bolinfest bolinfest commented Apr 21, 2026

Why

#18275 anchors session-scoped :cwd and :project_roots grants to the request cwd before recording them for reuse. Relative deny glob entries need the same treatment. Without anchoring, a stored session permission can keep a pattern such as **/*.env relative, then reinterpret that deny against a later turn cwd. That makes the persisted profile depend on the cwd at reuse time instead of the cwd that was reviewed and approved.

What changed

intersect_permission_profiles now materializes retained FileSystemPath::GlobPattern entries against the request cwd, matching the existing materialization for cwd-sensitive special paths.

Materialized accepted grants are now deduplicated before deny retention runs. This keeps the sticky-grant preapproval shape stable when a repeated request is merged with the stored grant and both :cwd = write and the materialized absolute cwd write are present.

The preapproval check compares against the same materialized form, so a later request for the same cwd-relative deny glob still matches the stored anchored grant instead of re-prompting or rejecting.

Tests cover both the storage path and the preapproval path: a session-scoped :cwd = write grant with **/*.env = none is stored with both the cwd write and deny glob anchored to the original request cwd, cannot be reused from a later cwd, and remains preapproved when re-requested from the original cwd after merging with the stored grant.

Verification

  • cargo test -p codex-sandboxing policy_transforms
  • cargo test -p codex-core --lib relative_deny_glob_grants_remain_preapproved_after_materialization
  • cargo clippy -p codex-sandboxing --tests -- -D clippy::redundant_clone
  • cargo clippy -p codex-core --lib -- -D clippy::redundant_clone

Stack created with Sapling. Best reviewed with ReviewStack.

Copy link
Copy Markdown
Contributor

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f863363a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/sandboxing/src/policy_transforms.rs
cwd,
);
intersect_permission_profiles(effective_permissions.clone(), granted_permissions, cwd)
== materialized_effective_permissions
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comparison still fails for the real sticky-grant shape this PR is trying to preserve. After a prior approval stores :cwd write plus **/*.env deny as /repo write plus /repo/**/*.env, a later command that repeats the original relative request gets merged with the stored grant before this helper runs. intersect_permission_profiles(effective, effective, cwd) then materializes both :cwd and /repo into duplicate /repo write entries, while intersect_permission_profiles(effective, granted, cwd) only has one. The equality check returns false, so an already-approved permission profile is no longer treated as preapproved and can be rejected when only request-permissions preapproval is enabled. De-duplicate after materializing entries, or compare normalized canonical profiles.

Copy link
Copy Markdown
Collaborator

@viyatb-oai viyatb-oai Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let mut accepted_entries = Vec::new();
for entry in granted_file_system.entries.iter().filter(|entry| {
    granted_file_system_entry_within_request(
        &requested_file_system,
        &requested_policy,
        requested_read_deny_matcher.as_ref(),
        entry,
        cwd,
    )
}) {
    let entry = materialize_cwd_dependent_entry(entry, cwd);
    if !accepted_entries.contains(&entry) {
        accepted_entries.push(entry);
    }
}
let mut entries = accepted_entries.clone();

Copy link
Copy Markdown
Collaborator

@viyatb-oai viyatb-oai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good - one small fix needed!

@bolinfest bolinfest merged commit 799e504 into main Apr 22, 2026
39 of 50 checks passed
@bolinfest bolinfest deleted the pr18867 branch April 22, 2026 00:29
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants