mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(sandbox): add Windows deny-read parity (#18202)
## Why The split filesystem policy stack already supports exact and glob `access = none` read restrictions on macOS and Linux. Windows still needed subprocess handling for those deny-read policies without claiming enforcement from a backend that cannot provide it. ## Key finding The unelevated restricted-token backend cannot safely enforce deny-read overlays. Its `WRITE_RESTRICTED` token model is authoritative for write checks, not read denials, so this PR intentionally fails that backend closed when deny-read overrides are present instead of claiming unsupported enforcement. ## What changed This PR adds the Windows deny-read enforcement layer and makes the backend split explicit: - Resolves Windows deny-read filesystem policy entries into concrete ACL targets. - Preserves exact missing paths so they can be materialized and denied before an enforceable sandboxed process starts. - Snapshot-expands existing glob matches into ACL targets for Windows subprocess enforcement. - Honors `glob_scan_max_depth` when expanding Windows deny-read globs. - Plans both the configured lexical path and the canonical target for existing paths so reparse-point aliases are covered. - Threads deny-read overrides through the elevated/logon-user Windows sandbox backend and unified exec. - Applies elevated deny-read ACLs synchronously before command launch rather than delegating them to the background read-grant helper. - Reconciles persistent deny-read ACEs per sandbox principal so policy changes do not leave stale deny-read ACLs behind. - Fails closed on the unelevated restricted-token backend when deny-read overrides are present, because its `WRITE_RESTRICTED` token model is not authoritative for read denials. ## Landed prerequisites These prerequisite PRs are already on `main`: 1. #15979 `feat(permissions): add glob deny-read policy support` 2. #18096 `feat(sandbox): add glob deny-read platform enforcement` 3. #17740 `feat(config): support managed deny-read requirements` This PR targets `main` directly and contains only the Windows deny-read enforcement layer. ## Implementation notes - Exact deny-read paths remain enforceable on the elevated path even when they do not exist yet: Windows materializes the missing path before applying the deny ACE, so the sandboxed command cannot create and read it during the same run. - Existing exact deny paths are preserved lexically until the ACL planner, which then adds the canonical target as a second ACL target when needed. That keeps both the configured alias and the resolved object covered. - Windows ACLs do not consume Codex glob syntax directly, so glob deny-read entries are expanded to the concrete matches that exist before process launch. - Glob traversal deduplicates directory visits within each pattern walk to avoid cycles, without collapsing distinct lexical roots that happen to resolve to the same target. - Persistent deny-read ACL state is keyed by sandbox principal SID, so cleanup only removes ACEs owned by the same backend principal. - Deny-read ACEs are fail-closed on the elevated path: setup aborts if mandatory deny-read ACL application fails. - Unelevated restricted-token sessions reject deny-read overrides early instead of running with a silently unenforceable read policy. ## Verification - `cargo test -p codex-core windows_restricted_token_rejects_unreadable_split_carveouts` - `just fmt` - `just fix -p codex-core` - `just fix -p codex-windows-sandbox` - GitHub Actions rerun is in progress on the pushed head. --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
c9e46ed639
commit
46f30d0282
@@ -875,6 +875,28 @@ impl UnifiedExecProcessManager {
|
||||
"windows sandbox: failed to resolve codex_home: {err}"
|
||||
))
|
||||
})?;
|
||||
let additional_deny_write_paths = request
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.as_ref()
|
||||
.map(|overrides| overrides.additional_deny_write_paths.clone())
|
||||
.unwrap_or_default();
|
||||
let additional_deny_read_paths = request
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.as_ref()
|
||||
.map(|overrides| overrides.additional_deny_read_paths.clone())
|
||||
.unwrap_or_default();
|
||||
let elevated_read_roots_override = request
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.as_ref()
|
||||
.and_then(|overrides| overrides.read_roots_override.clone());
|
||||
let elevated_read_roots_include_platform_defaults = request
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.as_ref()
|
||||
.is_some_and(|overrides| overrides.read_roots_include_platform_defaults);
|
||||
let elevated_write_roots_override = request
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.as_ref()
|
||||
.and_then(|overrides| overrides.write_roots_override.clone());
|
||||
let spawned = match request.windows_sandbox_level {
|
||||
codex_protocol::config_types::WindowsSandboxLevel::Elevated => {
|
||||
codex_windows_sandbox::spawn_windows_sandbox_session_elevated(
|
||||
@@ -885,6 +907,11 @@ impl UnifiedExecProcessManager {
|
||||
request.cwd.as_path(),
|
||||
request.env.clone(),
|
||||
None,
|
||||
elevated_read_roots_override.as_deref(),
|
||||
elevated_read_roots_include_platform_defaults,
|
||||
elevated_write_roots_override.as_deref(),
|
||||
&additional_deny_read_paths,
|
||||
&additional_deny_write_paths,
|
||||
tty,
|
||||
tty,
|
||||
request.windows_sandbox_private_desktop,
|
||||
@@ -901,6 +928,8 @@ impl UnifiedExecProcessManager {
|
||||
request.cwd.as_path(),
|
||||
request.env.clone(),
|
||||
None,
|
||||
&additional_deny_read_paths,
|
||||
&additional_deny_write_paths,
|
||||
tty,
|
||||
tty,
|
||||
request.windows_sandbox_private_desktop,
|
||||
|
||||
Reference in New Issue
Block a user